commit b46426abe0b7e3622ab1d7bfbba407daf8a0c0bd Author: yumoqing Date: Wed Jul 16 14:19:12 2025 +0800 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..1c04cf8 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# rbac + diff --git a/json/build.sh b/json/build.sh new file mode 100755 index 0000000..86a8ad9 --- /dev/null +++ b/json/build.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +xls2ui -m ../models -o ../wwwroot rbac *.json diff --git a/json/organization.json b/json/organization.json new file mode 100644 index 0000000..66e312a --- /dev/null +++ b/json/organization.json @@ -0,0 +1,51 @@ +{ + "models_dir": "${HOME}$/py/rbac/models", + "output_dir": "${HOME}$/py/sage/wwwroot/_a/organization", + "dbname": "sage", + "tblname": "organization", + "title":"Organization", + "params": { + "sortby":"orgname", + "editor":{ + "binds":[ + { + "wid":"province_id", + "event":"changed", + "actiontype":"script", + "target":"city_id", + "script":"this.loadData({cond:'parentid=\\''+params.province_id+'\\''})" + }, + { + "wid":"city_id", + "event":"changed", + "actiontype":"script", + "target":"distinct_id", + "script":"this.loadData({cond:'parentid=\\'' + params.city_id+ '\\''})" + } + ] + }, + "browserfields": { + "exclouded": ["id"], + "alters": { + } + }, + "editexclouded": [ + "id" + ], + "subtables":[ + { + "field":"orgid", + "title":"Org. type", + "url":"../orgtypes", + "subtable":"orgtypes" + }, + { + "field":"orgid", + "title":"Users", + "url":"../users", + "subtable":"users" + } + ], + "record_toolbar": null + } +} diff --git a/json/orgtypes.json b/json/orgtypes.json new file mode 100644 index 0000000..e6778da --- /dev/null +++ b/json/orgtypes.json @@ -0,0 +1,18 @@ +{ + "models_dir": "${HOME}$/py/rbac/models", + "output_dir": "${HOME}$/py/sage/wwwroot/_a/orgtypes", + "dbname": "sage", + "tblname": "orgtypes", + "title":"Org. type", + "params": { + "browserfields": { + "exclouded": ["id", "orgid"], + "cwidth": {} + }, + "editexclouded": [ + "id", + "orgid" + ], + "record_toolbar": null + } +} diff --git a/json/permission.json b/json/permission.json new file mode 100644 index 0000000..cb4d36b --- /dev/null +++ b/json/permission.json @@ -0,0 +1,21 @@ +{ + "tblname": "permission", + "uitype":"tree", + "title":"权限", + "params":{ + "idField":"id", + "textField":"path", + "sortby":"path", + "editable":true, + "browserfields":{ + "alters":{} + }, + "edit_exclouded_fields":[], + "parentField":"parentid", + "toolbar":{ + }, + "binds":[ + ] + } + +} diff --git a/json/role.json b/json/role.json new file mode 100644 index 0000000..3c52962 --- /dev/null +++ b/json/role.json @@ -0,0 +1,31 @@ +{ + "models_dir": "${HOME}$/py/rbac/models", + "output_dir": "${HOME}$/py/sage/wwwroot/_a/role", + "dbname": "sage", + "tblname": "role", + "title":"角色", + "params": { + "sortby":"name", + "browserfields": { + "exclouded": ["id"], + "cwidth": {} + }, + "editexclouded": [ + "id" + ], + "subtables":[ + { + "field":"roleid", + "title":"角色权限", + "url":"../rolepermission", + "subtable":"rolepermission" + }, + { + "field":"roleid", + "title":"用户", + "url":"../users", + "subtable":"users" + } + ] + } +} diff --git a/json/rolepermission.json b/json/rolepermission.json new file mode 100644 index 0000000..49bf3fd --- /dev/null +++ b/json/rolepermission.json @@ -0,0 +1,26 @@ +{ + "models_dir": "${HOME}$/py/rbac/models", + "output_dir": "${HOME}$/py/sage/wwwroot/_a/rolepermission", + "dbname": "sage", + "tblname": "rolepermission", + "title":"用户", + "params": { + "relation":{ + "outter_field":"permid", + "param_field":"roleid" + }, + "noedit":true, + "browserfields": { + "exclouded": ["id", "roleid"], + "alters":{ + "permid":{ + "cwidth":60 + } + } + }, + "editexclouded": [ + "id", "roleid" + ], + "record_toolbar": null + } +} diff --git a/json/userapp.json b/json/userapp.json new file mode 100644 index 0000000..a2da0c7 --- /dev/null +++ b/json/userapp.json @@ -0,0 +1,16 @@ +{ + "tblname": "userapp", + "title":"用户", + "params": { + "confidential_fields":["apikey"], + "sortby":"appname", + "browserfields": { + "exclouded": ["id", "userid"], + "alters": {} + }, + "editexclouded": [ + "id", "userid" + ], + "record_toolbar": null + } +} diff --git a/json/userdepartment.json b/json/userdepartment.json new file mode 100644 index 0000000..91f409e --- /dev/null +++ b/json/userdepartment.json @@ -0,0 +1,17 @@ +{ + "models_dir": "${HOME}$/py/rbac/models", + "output_dir": "${HOME}$/py/sage/wwwroot/_a/userdepartment", + "dbname": "sage", + "tblname": "userdepartment", + "title":"用户", + "params": { + "browserfields": { + "exclouded": ["id", "userid"], + "cwidth": {} + }, + "editexclouded": [ + "id", "userid" + ], + "record_toolbar": null + } +} diff --git a/json/userrole.json b/json/userrole.json new file mode 100644 index 0000000..4363c3e --- /dev/null +++ b/json/userrole.json @@ -0,0 +1,14 @@ +{ + "tblname": "userrole", + "title":"用户角色管理", + "params": { + "browserfields": { + "exclouded": ["id", "orgid"], + "alters": {} + }, + "editexclouded": [ + "id", + "userid" + ] + } +} diff --git a/json/users.json b/json/users.json new file mode 100644 index 0000000..1af9cbd --- /dev/null +++ b/json/users.json @@ -0,0 +1,28 @@ +{ + "tblname": "users", + "title":"用户", + "params": { + "sortby":"username", + "confidential_fields":["password"], + "logined_userorgid":"orgid", + "browserfields": { + "exclouded": ["id", "password", "orgid", "nick_name" ], + "cwidth": {} + }, + "editexclouded": [ + "id", "nick_name", "orgid" + ], + "subtables": [ + { + "field":"userid", + "title":"用户角色", + "subtable":"userrole" + }, + { + "field":"userid", + "title":"APIKEY", + "subtable":"userapp" + } + ] + } +} diff --git a/models/audit_log.xlsx b/models/audit_log.xlsx new file mode 100644 index 0000000..144bc7a Binary files /dev/null and b/models/audit_log.xlsx differ diff --git a/models/organization.xlsx b/models/organization.xlsx new file mode 100644 index 0000000..b43bd8b Binary files /dev/null and b/models/organization.xlsx differ diff --git a/models/orgtypes.xlsx b/models/orgtypes.xlsx new file mode 100644 index 0000000..d77a47a Binary files /dev/null and b/models/orgtypes.xlsx differ diff --git a/models/permission.xlsx b/models/permission.xlsx new file mode 100644 index 0000000..4dcb839 Binary files /dev/null and b/models/permission.xlsx differ diff --git a/models/role.xlsx b/models/role.xlsx new file mode 100644 index 0000000..be7d9f6 Binary files /dev/null and b/models/role.xlsx differ diff --git a/models/rolepermission.xlsx b/models/rolepermission.xlsx new file mode 100644 index 0000000..ee2ff14 Binary files /dev/null and b/models/rolepermission.xlsx differ diff --git a/models/userapp.xlsx b/models/userapp.xlsx new file mode 100644 index 0000000..c03a1bb Binary files /dev/null and b/models/userapp.xlsx differ diff --git a/models/userdepartment.xlsx b/models/userdepartment.xlsx new file mode 100644 index 0000000..0e4df52 Binary files /dev/null and b/models/userdepartment.xlsx differ diff --git a/models/userrole.xlsx b/models/userrole.xlsx new file mode 100644 index 0000000..4d98458 Binary files /dev/null and b/models/userrole.xlsx differ diff --git a/models/users.xlsx b/models/users.xlsx new file mode 100644 index 0000000..5b5cc0e Binary files /dev/null and b/models/users.xlsx differ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..59514a1 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,4 @@ +[build-system] +requires = ["setuptools>=61", "wheel"] +build-backend = "setuptools.build_meta" + diff --git a/rbac/README.md b/rbac/README.md new file mode 100644 index 0000000..e69de29 diff --git a/rbac/__init__.py b/rbac/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rbac/audit_log.py b/rbac/audit_log.py new file mode 100644 index 0000000..7c20d03 --- /dev/null +++ b/rbac/audit_log.py @@ -0,0 +1,9 @@ +from sqlor.dbpools import DBPools +from appPublic.dictObject import DictObject +from appPublic.uniqueID import getID +import json + +def write_audit_log(sor, request): + id = getID() + params_kw = get_params_kw(request) + diff --git a/rbac/check_perm.py b/rbac/check_perm.py new file mode 100644 index 0000000..c0d9ab0 --- /dev/null +++ b/rbac/check_perm.py @@ -0,0 +1,176 @@ +import time + +from aiohttp import BasicAuth +from sqlor.dbpools import DBPools +from appPublic.registerfunction import RegisterFunction +from appPublic.rc4 import password, unpassword +from appPublic.jsonConfig import getConfig +from appPublic.log import debug, exception +from appPublic.dictObject import DictObject +from appPublic.timeUtils import curDateString +from appPublic.uniqueID import getID +from ahserver.auth_api import AuthAPI, user_login +from ahserver.globalEnv import password_encode +from ahserver.serverenv import ServerEnv, get_serverenv, set_serverenv + +async def get_user_roles(userid): + sql = "select concat(b.orgtypeid, '.', b.name) as name from userrole a, role b where a.userid=${userid}$ and a.roleid = b.id" + db = DBPools() + roles = [] + dbname = await get_dbname() + async with db.sqlorContext(dbname) as sor: + recs = await sor.sqlExe(sql, {'userid':userid}) + if len(recs) < 1: + return roles + for r in recs: + roles.append(r.name) + return roles + +async def create_org(sor, ns, orgtypes=[]): + await sor.C('organization', ns) + if orgtypes == []: + orgtypes = ['customer'] + if 'customer' not in orgtypes: + orgtypes.append('customer') + for ot in orgtypes: + otns = { + 'id':getID(), + 'orgid':ns.id, + 'orgtypeid':ot + } + await sor.C('orgtypes', otns) + +async def create_user(sor, ns, roles=[]): + """ + role format: + { + orgtypeid: rr, + roles: ['ee', 'bb'] + } + """ + await sor.C('users', ns) + if roles == []: + roles = [ + { + 'orgtypeid': 'customer', + 'roles': [ 'customer'] + } + ] + for rt in roles: + sql = "select * from role where orgtypeid = ${otid}$ and name in ${roles}$)" + recs = await sor.sqlExe(sql, { + 'otid': rt['orgtypeid'], + 'roles': rt['roles'] + }) + for r in recs: + await sor.C('userrole', { + 'id':getID(), + 'userid':ns.id, + 'roleid':r.id + }) + +async def register_user(sor, ns): + if ns.password != ns.cfm_password: + debug('password not match') + return False + ns.password = password_encode(ns.password) + id = getID() + ns.id = id + ns.orgid = id + ns1 = DictObject(id=id, orgname=ns.username) + await create_org(sor, ns1) + await create_user(sor, ns) + return id + +async def get_dbname(): + rf = RegisterFunction() + dbname = await rf.exe('get_module_dbname', 'rbac') + return dbname + +async def checkUserPassword(request, username, password): + db = DBPools() + dbname = await get_dbname() + async with db.sqlorContext(dbname) as sor: + sql = "select * from users where username=${username}$ and password=${password}$" + recs = await sor.sqlExe(sql, {'username':username, 'password':password}) + if len(recs) < 1: + return False + await user_login(request, recs[0].id, + username=recs[0].username, + userorgid=recs[0].orgid) + return True + return False + +async def basic_auth(sor, auth): + auther = BasicAuth('x') + m = auther.decode(auth) + username = m.login + password = password_encode(m.password) + sql = "select * from users where username=${username}$ and password=${password}$" + recs = await sor.sqlExe(sql, {'username':username,'password':password}) + if len(recs) < 1: + return None + return recs[0].id + +async def bearer_auth(sor, auth): + # apikey = get_apikey_from_token(auth[7:]) + apikey = auth[7:] + if apikey is None: + return None + sql = "select * from userapp where apikey=${apikey}$ and expired_date > ${today}$" + recs = await sor.sqlExe(sql, {"apikey":apikey, 'today': curDateString()}) + if len(recs) < 1: + return None + return recs[0].userid + +async def getAuthenticationUserid(sor, request): + auth = request.headers.get('Authentication') + if auth is None: + return None + for h,f in registered_auth_methods.items(): + if auth.startswith(h): + return await f(auth) + return None + +async def objcheckperm(obj, request, userid, path): + debug(f'check permission: {userid=}, {path=}') + sql = """select distinct a.*, c.userid from +(select id, path from permission where path=${path}$) a +right join + rolepermission b on a.id = b.permid +right join userrole c on b.roleid = c.roleid +where c.userid = ${userid}$ +""" + + dbname = await get_dbname() + db = DBPools() + async with db.sqlorContext(dbname) as sor: + if userid is None: + userid = await getAuthenticationUserid(sor, request) + perms = await sor.R('permission', {'path':path}) + if len(perms) == 0: + debug(f'{path=} not found in permission, can access') + return True + if userid is None: + debug(f'{userid=} is None, can not access {path=}') + return False + + recs = await sor.sqlExe(sql, {'path':path, 'userid':userid}) + for r in recs: + id = r['id'] + if id is not None: + debug(f'{userid=} can access {path=}') + return True + debug(f'{userid=} has not permission to call {path=}') + return False + debug(f'error happened {userid}, {path}') + return False + +registered_auth_methods = { + "Basic ": basic_auth, + "Bearer ": bearer_auth +} + +def register_auth_method(heading, func): + registered_auth_methods[heading] = func + diff --git a/rbac/init.py b/rbac/init.py new file mode 100644 index 0000000..0b9a4ff --- /dev/null +++ b/rbac/init.py @@ -0,0 +1,18 @@ +from ahserver.auth_api import AuthAPI +from ahserver.serverenv import ServerEnv +from rbac.check_perm import objcheckperm, get_user_roles, checkUserPassword, register_user, register_auth_method, create_org, create_user +from rbac.set_role_perms import set_role_perm, set_role_perms + +def load_rbac(): + AuthAPI.checkUserPermission = objcheckperm + env = ServerEnv() + env.create_org = create_org + env.create_user = create_user + env.get_user_roles = get_user_roles + env.check_user_password = checkUserPassword + env.register_user = register_user + env.set_role_perm = set_role_perm + env.set_role_perms = set_role_perms + env.register_auth_method = register_auth_method + + diff --git a/rbac/set_role_perms.py b/rbac/set_role_perms.py new file mode 100644 index 0000000..48cacac --- /dev/null +++ b/rbac/set_role_perms.py @@ -0,0 +1,71 @@ +import sys +import os +import asyncio +from sqlor.dbpools import DBPools +from appPublic.jsonConfig import getConfig +from appPublic.uniqueID import getID +from appPublic.asynciorun import run + +async def set_role_perm(dbname, module, orgtype, role, tblname): + db = DBPools() + async with db.sqlorContext(dbname) as sor: + if '/' in dbname: + path = [f'/{module}/{dbname}'] + else: + paths = [ + f'/{module}/{tblname}', + f'/{module}/{tblname}/index.ui', + f'/{module}/{tblname}/get_{tblname}.dspy', + f'/{module}/{tblname}/add_{tblname}.dspy', + f'/{module}/{tblname}/delete_{tblname}.dspy', + f'/{module}/{tblname}/update_{tblname}.dspy' + ] + for pat in paths: + recs = await sor.R('permission', {'path': pat}) + if len(recs) == 0: + permid = getID() + await sor.C('permission', {'id':permid, 'path':pat}) + else: + permid = recs[0].id + recs = await sor.R('role', {'orgtypeid':orgtype, 'name':role}) + if len(recs) == 0: + roleid = getID() + await sor.C('role', { + 'id':roleid, + 'name':role, + 'orgtypeid':orgtype + }) + else: + roleid = recs[0].id + await sor.C('rolepermission', { + 'id':getID(), + 'roleid':roleid, + 'permid':permid + }) + print(f'{orgtype=}, {role=}, {tblname=} permission configured') + +async def set_role_perms(dbname, module, orgtype, role, items): + for tblname in items: + await set_role_perm(dbname, module, orgtype, role, tblname) + +if __name__ == '__main__': + async def main(): + if len(sys.argv) < 6: + print(f'{sys.argv[0]} dbname module orgtype role tblname ...\n') + sys.exit(1) + dbname = sys.argv[1] + module = sys.argv[2] + orgtype = sys.argv[3] + role = sys.argv[4] + await set_role_perms(dbname, module, orgtype, role, sys.argv[5:]) + + def run(coro): + p = '.' + config = getConfig(p, {'woridir':p}) + DBPools(config.databases) + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(coro()) + + run(main) + diff --git a/rbac/version.py b/rbac/version.py new file mode 100644 index 0000000..b794fd4 --- /dev/null +++ b/rbac/version.py @@ -0,0 +1 @@ +__version__ = '0.1.0' diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..34df19f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +aiohttp +PyJWT diff --git a/script/init.py b/script/init.py new file mode 100644 index 0000000..17c2340 --- /dev/null +++ b/script/init.py @@ -0,0 +1,191 @@ +import os +import sys +import argparse + +from traceback import format_exc +import asyncio +from appPublic.uniqueID import getID +from appPublic.timeUtils import days_between, strdate_add +from appPublic.jsonConfig import getConfig +from random import randint, random +from appPublic.folderUtils import listFile +from appPublic.registerfunction import RegisterFunction + +from sqlor.dbpools import DBPools +from rbac.check_perm import mypassword +databases = { + "sage":{ + "driver":"aiomysql", + "async_mode":True, + "coding":"utf8", + "maxconn":100, + "dbname":"sage", + "kwargs":{ + "user":"test", + "db":"sage", + "password":"QUZVcXg5V1p1STMybG5Ia6mX9D0v7+g=", + "host":"localhost" + } + } +} + +async def insert_perm(path): + db = DBPools() + async with db.sqlorContext('sage') as sor: + ns = { + 'id':getID(), + 'path':path + } + await sor.C('permission', ns) + +av_folders = [ + '/index.ui', + '/user.ui', + '/menu.ui', + '/get_code.dspy', + '/top.ui', + '/center', + '/bottom.ui', + '/app_panel.ui', + '/bricks', + '/imgs', + '/login.ui', + '/gen_code.dspy', + '/code_login.dspy', + '/up_login.dspy', + '/wechat_login.ui', + '/public', + '/debug/index.dspy', + '/rbac/userpassword_login.ui', + '/rbac/userpassword_login.dspy', + '/tmp' +] + + +orgtypes = ['owner', 'reseller'] +type_roles = { + 'owner':['superuser', 'customer'], + 'reseller':['admin','operator', 'sale', 'maintainer', 'accountant'], + 'customer':['admin', 'customer'] +} + +rbac_tables = [ + "organization", "orgtypes", "role", "users", "userrole", "rolepermission", "permission", + "userapp", "userdepartment" ] + +role_perms = { + 'superuser': [ + '/get_code.dspy', + '/rbac/add_adminuser.ui', + '/rbac/add_adminuser.dspy', + '/rbac/role/index.ui', + '/rbac/role/get_role.dspy', + '/rbac/role/update_role.dspy', + '/rbac/role/delete_role.dspy', + '/rbac/role/add_role.dspy', + '/rbac/permission/add_permission.dspy', + '/rbac/permission/delete_permission.dspy', + '/rbac/permission/update_permission.dspy', + '/rbac/permission/get_permission.dspy', + '/rbac/permission/index.ui', + '/rbac/rolepermission/add_rolepermission.dspy', + '/rbac/rolepermission/delete_rolepermission.dspy', + '/rbac/rolepermission/update_rolepermission.dspy', + '/rbac/rolepermission/get_rolepermission.dspy', + '/rbac/rolepermission/index.ui' + ], + 'admin':[ + '/rbac/users/add_users.dspy', + '/rbac/users/delete_users.dspy', + '/rbac/users/update_users.dspy', + '/rbac/users/get_users.dspy', + '/rbac/users/index.ui', + '/rbac/userrole/add_userrole.dspy', + '/rbac/userrole/delete_userrole.dspy', + '/rbac/userrole/update_userrole.dspy', + '/rbac/userrole/get_userrole.dspy', + '/rbac/userrole/index.ui' + ] +} + +async def init_perms(): + db = DBPools() + async with db.sqlorContext('sage') as sor: + for rn, pths in role_perms.items(): + roles = await sor.sqlExe('select * from role where name=${rn}$', {'rn':rn}) + if len(roles) == 0: + print(f'{rn=} not exists') + continue + for p in pths: + print(f'{rn=} set {p=}') + perms = await sor.sqlExe('select * from permission where path=${p}$', {'p':p}) + if len(perms): + await sor.C('rolepermission', {'id':getID(), + 'roleid':roles[0].id, + 'permid':perms[0].id}) + else: + print(f'{p} is not exists') + +async def init_rbac(passwd): + db = DBPools() + async with db.sqlorContext('sage') as sor: + recs = await sor.R('organization', {'id':'0'}) + if len(recs) > 0: + return + await sor.C('organization',{'id':'0'}) + await sor.C('orgtypes', {'id':getID(), 'orgid':'0', 'orgtypeid':'owner'}) + await sor.C('orgtypes', {'id':getID(), 'orgid':'0', 'orgtypeid':'reseller'}) + roleid = '' + for orgtype, roles in type_roles.items(): + for r in roles: + id = getID() + await sor.C('role', {'id':id,'orgtypeid':orgtype,'name':r}) + if orgtype == 'owner' and r == 'superuser': + roleid = id + ns = { + 'id':getID(), + 'username':'superuser', + 'orgid':'0', + 'password':passwd + } + await sor.C('users', ns) + ns1 = { + 'id':getID(), + 'userid':ns['id'], + 'roleid':roleid + } + await sor.C('userrole', ns1) + +async def add_permissions(workdir): + root = os.path.join(workdir, 'wwwroot') + for f in listFile(root, rescursive=True): + cnt = len(root) + pth = f[cnt:] + print(f'{f=},{root=},{pth=}, {cnt=}') + act = True + for f in av_folders: + if pth.startswith(f): + act = False + break + if act: + await insert_perm(pth) + +async def main(workdir, passwd): + await add_permissions(workdir) + await init_rbac(passwd) + await init_perms() + +if __name__ == '__main__': + parser = argparse.ArgumentParser(prog='RBAC init') + parser.add_argument('-w', '--workdir') + parser.add_argument('password') + args = parser.parse_args() + if args.password is None: + parser.usage() + sys.exit(1) + print(f'{args=}') + config = getConfig(args.workdir, {'workdir':args.workdir}) + supassword = mypassword(args.password) + DBPools(config.databases) + asyncio.get_event_loop().run_until_complete(main(args.workdir, supassword)) + diff --git a/script/setroleperms.sh b/script/setroleperms.sh new file mode 100644 index 0000000..5e435d3 --- /dev/null +++ b/script/setroleperms.sh @@ -0,0 +1,7 @@ +#!/usr/bin/bash + +python -m rbac.set_role_perms sage rbac owner superuser role permission rolepermission user user role organizationorgtypes +python -m rbac.set_role_perms sage rbac owner admin user userrole +python -m rbac.set_role_perms sage rbac customer admin user userrole +python -m rbac.set_role_perms sage rbac reseller admin user userrole + diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..db6fe8b --- /dev/null +++ b/setup.cfg @@ -0,0 +1,16 @@ +[metadata] +name=rbac +version = 1.0.0 +description = a RBAC user authenticate module +author = "yu moqing" +author_email = "yumoqing@gmail.com" +readme = "README.md" +license = "MIT" +[options] +packages = find: +requires_python = ">=3.8" +install_requires = + apppublic + sqlor + ahserver + diff --git a/wwwroot/add_adminuser.dspy b/wwwroot/add_adminuser.dspy new file mode 100644 index 0000000..61af0de --- /dev/null +++ b/wwwroot/add_adminuser.dspy @@ -0,0 +1,23 @@ +if params_kw.get('password') != params_kw.get('chkpassword'): + return Error(title='add user error', message='password not match') + +ns = params_kw.copy() +ns['id'] = uuid() +await rfexe('passowrd', ns) +user_orgid = await get_userorgid() +ns['orgid'] = user_orgid +dbname = await rfexe('get_module_dbname','rbac') +db = DBPools() +debug(f'{dbname=}') +async with db.sqlorContext(dbname) as sor: + await sor.C('users',ns.copy()) + uid = ns['id'] + ns = { + 'id':uuid(), + 'userid':uid, + 'roleid':'admin' + } + await sor.C('userrole', ns.copy()) + return UiMesage(title='Success', message='admin user added') +return UiError(title='Error', message='Error happened when add admin user') + diff --git a/wwwroot/add_adminuser.ui b/wwwroot/add_adminuser.ui new file mode 100644 index 0000000..7e58462 --- /dev/null +++ b/wwwroot/add_adminuser.ui @@ -0,0 +1,36 @@ +{ + "widgettype":"ModalForm", + "options":{ + "cwidth":20, + "cheight":30, + "title":"增加管理员", + "fields":[ + { + "name":"username", + "label":"用户名", + "uitype":"str" + }, + { + "name":"password", + "label":"密码", + "uitype":"password" + }, + { + "name":"chkpassword", + "label":"确认密码", + "uitype":"password" + } + ] + }, + "binds":[ + { + "wid":"self", + "event":"submit", + "actiontype":"urlwidget", + "target":"self", + "options":{ + "url":"{{entire_url('./add_adminuser.dspy')}}" + } + } + ] +} diff --git a/wwwroot/add_provider.dspy b/wwwroot/add_provider.dspy new file mode 100644 index 0000000..7a44acc --- /dev/null +++ b/wwwroot/add_provider.dspy @@ -0,0 +1,36 @@ +ns = params_kw.copy() +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id +db = DBPools() +dbname = get_module_dbname('rbac') +async with db.sqlorContext(dbname) as sor: + ownerid = await get_userorgid() + await create_org(sor, ns, orgtypes=['customer', 'provider']) + if openCustomerAccounts: + await openCustomerAccounts(sor, ownerid, orgid) + if openProviderAccounts: + await openProviderAccounts(sor, ownerid, id) + return { + "widgettype":"Message", + "options":{ + "user_data":ns, + "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" + } +} diff --git a/wwwroot/add_reseller.dspy b/wwwroot/add_reseller.dspy new file mode 100644 index 0000000..cd8de33 --- /dev/null +++ b/wwwroot/add_reseller.dspy @@ -0,0 +1,26 @@ +ns = params_kw.copy() +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id +db = DBPools() +dbname = get_module_dbname('rbac') +async with db.sqlorContext(dbname) as sor: + ownerid = await get_userorgid() + await create_org(sor, ns, orgtypes=['customer', 'reseller']) + if openCustomerAccounts: + await openCustomerAccounts(sor, ownerid, orgid) + if openResellerAccounts: + await openResellerAccounts(sor, ownerid, id) + return UiMessage(title="Success", message="add reseller success") + +return { + "widgettype":"Error", + "options":{ + "title":"Add reseller Error", + "cwidth":16, + "cheight":9, + "timeout":3, + "message":"failed" + } +} diff --git a/wwwroot/admin_menu.ui b/wwwroot/admin_menu.ui new file mode 100644 index 0000000..029a5f6 --- /dev/null +++ b/wwwroot/admin_menu.ui @@ -0,0 +1,7 @@ +[ + { + "name":"users", + "label":"用户管理" + "url":"{{entire_url('/rbac/users')}}" + } +] diff --git a/wwwroot/get_provider.dspy b/wwwroot/get_provider.dspy new file mode 100644 index 0000000..3d61294 --- /dev/null +++ b/wwwroot/get_provider.dspy @@ -0,0 +1,151 @@ +ns = params_kw.copy() +debug(f'get_organization.dspy:{ns=}') +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + ns['sort'] = 'orgname' + +sql = '''select a.*, b.province_id_text, c.city_id_text, d.distinct_id_text +from (select x.* from (select * from organization where 1=1 [[filterstr]]) x, orgtypes y where x.id = y.orgid and y.orgtypeid='provider') a left join (select k as province_id, + v as province_id_text from appcodes_kv where parentid='chnaddr') b on a.province_id = b.province_id left join (select k as city_id, + v as city_id_text from appcodes_kv where parentid='city') c on a.city_id = c.city_id left join (select k as distinct_id, + v as distinct_id_text from appcodes_kv where parentid='distinct') d on a.distinct_id = d.distinct_id''' + +filterjson = params_kw.get('data_filter') +if not filterjson: + fields = [ f['name'] for f in [ + { + "name": "id", + "title": "机构编码", + "type": "str", + "length": 32 + }, + { + "name": "orgname", + "title": "机构名称", + "type": "str", + "length": 100 + }, + { + "name": "alias_name", + "title": "机构别名", + "type": "str", + "length": 100 + }, + { + "name": "contactor", + "title": "联系人", + "type": "str", + "length": 32 + }, + { + "name": "contactor_phone", + "title": "联系人电话", + "type": "str", + "length": 100 + }, + { + "name": "province_id", + "title": "所在省id", + "type": "str", + "length": 32 + }, + { + "name": "city_id", + "title": "所在城市id", + "type": "str", + "length": 32 + }, + { + "name": "distinct_id", + "title": "所在地区id", + "type": "str", + "length": 32 + }, + { + "name": "emailaddress", + "title": "邮箱", + "type": "str", + "length": 256 + }, + { + "name": "address", + "title": "地址", + "type": "str", + "length": 400 + }, + { + "name": "main_business", + "title": "主营业务描述", + "type": "str", + "length": 1000 + }, + { + "name": "orgcode", + "title": "组织结构代码", + "type": "str", + "length": 100, + "comments": "个人客户存身份证" + }, + { + "name": "license_img", + "title": "营业执照", + "type": "str", + "length": 400, + "comments": "个人客户存身份证照片" + }, + { + "name": "id_img", + "title": "身份证", + "type": "str", + "length": 400, + "comments": "个人客户存身份证背面照片" + }, + { + "name": "parentid", + "title": "父机构id", + "type": "str", + "length": 32 + }, + { + "name": "org_type", + "title": "机构类型", + "type": "str", + "length": 32, + "comments": "0:业主机构;1:分销商;2:公司客户;3:个人客户;4:供应商" + }, + { + "name": "accountid", + "title": "账号", + "type": "str", + "length": 32 + } +] ] + 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=}') +db = DBPools() +dbname = get_module_dbname('rbac') +async with db.sqlorContext(dbname) as sor: + r = await sor.sqlPaging(sql, ns) + return r +return { + "total":0, + "rows":[] +} diff --git a/wwwroot/get_reseller.dspy b/wwwroot/get_reseller.dspy new file mode 100644 index 0000000..898ec19 --- /dev/null +++ b/wwwroot/get_reseller.dspy @@ -0,0 +1,151 @@ +ns = params_kw.copy() +debug(f'get_organization.dspy:{ns=}') +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + ns['sort'] = 'orgname' + +sql = '''select a.*, b.province_id_text, c.city_id_text, d.distinct_id_text +from (select x.* from (select * from organization where 1=1 [[filterstr]]) x, orgtypes y where x.id = y.orgid and y.orgtypeid='reseller') a left join (select k as province_id, + v as province_id_text from appcodes_kv where parentid='chnaddr') b on a.province_id = b.province_id left join (select k as city_id, + v as city_id_text from appcodes_kv where parentid='city') c on a.city_id = c.city_id left join (select k as distinct_id, + v as distinct_id_text from appcodes_kv where parentid='distinct') d on a.distinct_id = d.distinct_id''' + +filterjson = params_kw.get('data_filter') +if not filterjson: + fields = [ f['name'] for f in [ + { + "name": "id", + "title": "机构编码", + "type": "str", + "length": 32 + }, + { + "name": "orgname", + "title": "机构名称", + "type": "str", + "length": 100 + }, + { + "name": "alias_name", + "title": "机构别名", + "type": "str", + "length": 100 + }, + { + "name": "contactor", + "title": "联系人", + "type": "str", + "length": 32 + }, + { + "name": "contactor_phone", + "title": "联系人电话", + "type": "str", + "length": 100 + }, + { + "name": "province_id", + "title": "所在省id", + "type": "str", + "length": 32 + }, + { + "name": "city_id", + "title": "所在城市id", + "type": "str", + "length": 32 + }, + { + "name": "distinct_id", + "title": "所在地区id", + "type": "str", + "length": 32 + }, + { + "name": "emailaddress", + "title": "邮箱", + "type": "str", + "length": 256 + }, + { + "name": "address", + "title": "地址", + "type": "str", + "length": 400 + }, + { + "name": "main_business", + "title": "主营业务描述", + "type": "str", + "length": 1000 + }, + { + "name": "orgcode", + "title": "组织结构代码", + "type": "str", + "length": 100, + "comments": "个人客户存身份证" + }, + { + "name": "license_img", + "title": "营业执照", + "type": "str", + "length": 400, + "comments": "个人客户存身份证照片" + }, + { + "name": "id_img", + "title": "身份证", + "type": "str", + "length": 400, + "comments": "个人客户存身份证背面照片" + }, + { + "name": "parentid", + "title": "父机构id", + "type": "str", + "length": 32 + }, + { + "name": "org_type", + "title": "机构类型", + "type": "str", + "length": 32, + "comments": "0:业主机构;1:分销商;2:公司客户;3:个人客户;4:供应商" + }, + { + "name": "accountid", + "title": "账号", + "type": "str", + "length": 32 + } +] ] + 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=}') +db = DBPools() +dbname = get_module_dbname('rbac') +async with db.sqlorContext(dbname) as sor: + r = await sor.sqlPaging(sql, ns) + return r +return { + "total":0, + "rows":[] +} diff --git a/wwwroot/user/login.ui b/wwwroot/user/login.ui new file mode 100644 index 0000000..1ce2ddc --- /dev/null +++ b/wwwroot/user/login.ui @@ -0,0 +1,121 @@ +{ + "id":"login_window", + "widgettype":"PopupWindow", + "options":{ + "auto_open":true, + "anthor":"cc", + "cwidth":22, + "cheight":19 + }, + "subwidgets":[ +{ + "widgettype":"TabPanel", + "options":{ + "tab_wide":"auto", + "height":"100%", + "width":"100%", + "tab_pos":"top", + "items":[ + { + "name":"userpasswd", + "label":"用户密码", + "content":{ + "widgettype":"Form", + "options":{ + "cols":1, + "fields":[ + { + "name":"username", + "label":"用户名", + "uitype":"str" + }, + { + "name":"password", + "label":"密码", + "uitype":"password" + } + ] + }, + "binds":[ + { + "wid":"self", + "event":"submit", + "actiontype":"urlwidget", + "target":"self", + "options":{ + "method":"POST", + "url":"{{entire_url('up_login.dspy')}}" + } + } + ] + } + }, + { + "name":"checkcode", + "label":"手机验证码", + "content":{ + "widgettype":"Form", + "options":{ + "toolbar":{ + "tools":[ + { + "name":"gen_code", + "label":"发送验证码" + } + ] + }, + "description":"限中国国内手机", + "fields":[ + { + "name":"cell_no", + "label":"手机号", + "uitype":"str" + },{ + "name":"codeid", + "uitype":"hide", + "value":"{{uuid()}}" + },{ + "name":"check_code", + "uitype":"str" + } + ] + }, + "binds":[ + { + "wid":"self", + "event":"gen_code", + "actiontype":"urlwidget", + "datawidget":"self", + "datamethod":"getValue", + "target":"self", + "options":{ + "url":"{{entire_url('gen_code.dspy')}}" + } + }, + { + "wid":"self", + "event":"submit", + "actiontype":"urlwidget", + "target":"self", + "options":{ + "url":"{{entire_url('code_login.dspy')}}" + } + } + ] + } + }, + { + "name":"wechat", + "label":"微信", + "content":{ + "widgettype":"urlwidget", + "options":{ + "url":"{{entire_url('wechat_login.ui')}}" + } + } + } + ] + } +} + ] +} diff --git a/wwwroot/user/logout.dspy b/wwwroot/user/logout.dspy new file mode 100644 index 0000000..baf169d --- /dev/null +++ b/wwwroot/user/logout.dspy @@ -0,0 +1,23 @@ +await forget_user() +return { + "widgettype":"Message", + "options":{ + "title":"Message", + "message":"logout success", + "auto_open":true, + "anthor":"cc", + "timeout":3 + }, + "binds":[ + { + "wid":"self", + "event":"dismissed", + "actiontype":"urlwidget", + "target":"window", + "options":{ + "url":entire_url('/index.ui') + } + } + ] +} + diff --git a/wwwroot/user/myrole.ui b/wwwroot/user/myrole.ui new file mode 100644 index 0000000..e661030 --- /dev/null +++ b/wwwroot/user/myrole.ui @@ -0,0 +1,27 @@ +{ + "widgettype":"VBox", + "options":{ + "height":"100%" + }, + "subwidgets":[ + { + "widgettype":"Title4", + "options":{ + "otext":"我的角色", + "i18n":true + } + }, +{% set roles = get_user_roles(get_user()) %} +{% for role in roles %} + { + "widgettype":"Text", + "options":{ + "text":"{{role}}" + } + }, +{% endfor %} + { + "oops":true + } + ] +} diff --git a/wwwroot/user/register.dspy b/wwwroot/user/register.dspy new file mode 100644 index 0000000..25fd15d --- /dev/null +++ b/wwwroot/user/register.dspy @@ -0,0 +1,10 @@ +debug(f'{params_kw=}') +db = DBPools() +dbname = await rfexe('get_module_dbname', 'sage') +async with db.sqlorContext(dbname) as sor: + orgid = await register_user(sor, params_kw) + if get_owner_orgid and openCustomerAccounts: + ownerid = await get_owner_orgid(sor, orgid) + await openCustomerAccounts(sor, ownerid, orgid) + return UiMessage(title="Success", message="register success") +return UiError(title='Error', message="register failed") diff --git a/wwwroot/user/register.ui b/wwwroot/user/register.ui new file mode 100644 index 0000000..3a25c1d --- /dev/null +++ b/wwwroot/user/register.ui @@ -0,0 +1,80 @@ +{ + "widgettype":"PopupWindow", + "options":{ + "cwidth":22, + "height":"75%", + "archor":"cc", + "auto_open":true + }, + "subwidgets":[ + { + "widgettype":"Form", + "options":{ + "title":"user register", + "description":"base info we need is username, password and a cell phone number", + + "fields":[ + { + "name": "username", + "title": "\u7528\u6237\u540d", + "type": "str", + "length": 255, + "uitype": "str", + "datatype": "str", + "required":true, + "label": "\u7528\u6237\u540d" + }, + { + "name": "password", + "type": "str", + "length": 255, + "uitype": "password", + "datatype": "str", + "required":true, + "label": "\u5bc6\u7801" + }, + { + "name": "cfm_password", + "type": "str", + "length": 255, + "uitype": "password", + "datatype": "str", + "required":true, + "label": "\u5bc6\u7801" + }, + { + "name": "email", + "title": "\u90ae\u4ef6\u5730\u5740", + "type": "str", + "length": 255, + "uitype": "str", + "datatype": "str", + "required":true, + "label": "\u90ae\u4ef6\u5730\u5740" + }, + { + "name": "mobile", + "title": "\u624b\u673a", + "type": "str", + "length": 255, + "uitype": "str", + "datatype": "str", + "required":true, + "label": "\u624b\u673a" + } + ] + }, + "binds":[ + { + "wid":"self", + "event":"submit", + "actiontype":"urlwidget", + "target":"self", + "options":{ + "url":"{{entire_url('register.dspy')}}" + } + } + ] + } + ] +} diff --git a/wwwroot/user/reset_password/index.ui b/wwwroot/user/reset_password/index.ui new file mode 100644 index 0000000..8cec34a --- /dev/null +++ b/wwwroot/user/reset_password/index.ui @@ -0,0 +1,49 @@ +{ + "widgettype":"PopupWindow", + "options":{ + "cwidth":22, + "height":"75%", + "archor":"cc", + "auto_open":true + }, + "subwidgets":[ + { + "widgettype":"Form", + "options":{ + "title":"Reset Password", + "description":"reset yourself password", + "fields":[ + { + "name": "password", + "type": "str", + "length": 255, + "uitype": "password", + "datatype": "str", + "required":true, + "label": "\u5bc6\u7801" + }, + { + "name": "cfm_password", + "type": "str", + "length": 255, + "uitype": "password", + "datatype": "str", + "required":true, + "label": "\u5bc6\u7801" + } + ] + }, + "binds":[ + { + "wid":"self", + "event":"submit", + "actiontype":"urlwidget", + "target":"self", + "options":{ + "url":"{{entire_url('reset_password.dspy')}}" + } + } + ] + } + ] +} diff --git a/wwwroot/user/reset_password/reset_password.dspy b/wwwroot/user/reset_password/reset_password.dspy new file mode 100644 index 0000000..367b902 --- /dev/null +++ b/wwwroot/user/reset_password/reset_password.dspy @@ -0,0 +1,17 @@ +if params_kw.password != params_kw.cfm_password: + return UiError(title='Error', message='Password not match') + +userid = await get_user() +if userid is None: + return UiError(title='Error', message='You need login first') + +ns = { + 'id':userid, + 'password':params_kw.password +} +db = DBPools() +dbname = await rfexe('get_module_dbname', 'rbac') +async with db.sqlorContext(dbname) as sor: + await sor.U('users', ns) + return UiMessage(title='Success', message='Password reset success') +return UiError(title='Error', message='Reset password failed') diff --git a/wwwroot/user/up_login.dspy b/wwwroot/user/up_login.dspy new file mode 100644 index 0000000..0a7a8eb --- /dev/null +++ b/wwwroot/user/up_login.dspy @@ -0,0 +1,58 @@ + +debug(f'{params_kw=}, {password=}') +ns = { + "username":params_kw.username, + "password":password_encode(params_kw.password) +} + +info(f'{ns=}') +db = DBPools() +dbname = await rfexe('get_module_dbname', 'rbac') +async with db.sqlorContext(dbname) as sor: + r = await sor.sqlExe('select * from users where username=${username}$ and password=${password}$', ns.copy()) + if len(r) == 0: + return { + "widgettype":"Error", + "options":{ + "timeout":3, + "title":"Login Error", + "message":"user name or password error" + } + } + await remember_user(r[0].id, username=r[0].username, userorgid=r[0].orgid) + return { + "widgettype":"Message", + "options":{ + "timeout":3, + "auto_open":true, + "title":"Login", + "message":"Welcome back" + }, + "binds":[ + { + "wid":"self", + "event":"dismissed", + "actiontype":"urlwidget", + "target":"window", + "options":{ + "url":entire_url('/index.ui') + } + }, + { + "wid":"self", + "event":"opened", + "actiontype":"script", + "target":"window.login_window", + "script":"this.destroy()" + } + ] + } +return { + "widgettype":"Error", + "options":{ + "timeout":3, + "title":"Login Error", + "message":"system error" + } +} + diff --git a/wwwroot/user/user.ui b/wwwroot/user/user.ui new file mode 100644 index 0000000..842b715 --- /dev/null +++ b/wwwroot/user/user.ui @@ -0,0 +1,80 @@ +{% if get_user() %} +{ + "widgettype":"HBox", + "options":{ + "css":"clickable", + "tip":"用户功能", + "cwidth":6 + }, + "binds":[ + { + "wid":"self", + "event":"click", + "actiontype":"urlwidget", + "popup_options":{ + "eventpos":true, + "dismiss_events":["command"] + }, + "target":"Popup", + "options":{ + "url":"{{entire_url('user_menu.ui')}}" + } + } + ], + "subwidgets":[ + { + "widgettype":"Svg", + "options":{ + "url":"{{entire_url('/bricks/imgs/user.svg')}}", + "rate":1.5 + } + }, + { + "widgettype":"Text", + "options":{ + "cwidth": 4, + "text":"{{get_username()}}" + } + } + ] +} +{% else %} +{ + "widgettype":"IconBar", + "options":{ + "css":"filler", + "tools":[ + { + "name":"login", + "tip":"点击登录", + "icon":"{{entire_url('/bricks/imgs/login.svg')}}" + }, + { + "name":"register", + "tip":"点击注册", + "icon":"{{entire_url('/bricks/imgs/register.svg')}}" + } + ] + }, + "binds":[ + { + "wid":"self", + "event":"login", + "actiontype":"urlwidget", + "target":"self", + "options":{ + "url":"{{entire_url('login.ui')}}" + } + }, + { + "wid":"self", + "event":"register", + "actiontype":"urlwidget", + "target":"self", + "options":{ + "url":"{{entire_url('register.ui')}}" + } + } + ] +} +{% endif %} diff --git a/wwwroot/user/user_menu.ui b/wwwroot/user/user_menu.ui new file mode 100644 index 0000000..8c0ad70 --- /dev/null +++ b/wwwroot/user/user_menu.ui @@ -0,0 +1,55 @@ +{ + "widgettype":"Menu", + "options":{ + "cwidth":10, + "target":"PopupWindow", + "popup_options":{ + "height":"70%", + "width":"70%" + }, + "items":[ +{% if 'customer.customer' in get_user_roles(get_user()) %} + { + "name":"recharge", + "label":"充值", + "url":"{{entire_url('/platformbiz/recharge.ui')}}" + }, + { + "name":"myacc", + "label":"我的账户", + "url":"{{entire_url('myaccount')}}" + }, + { + "name":"mybill", + "label":"我的账单", + "url":"{{entire_url('mybill')}}" + }, + { + "name":"myorder", + "label":"我的订单", + "url":"{{entire_url('myorder')}}" + }, + { + "name":"myresource", + "label":"我的资源", + "url":"{{entire_url('myresource')}}" + }, +{% endif %} + { + "name":"resetpwd", + "label":"重置密码", + "url":"{{entire_url('reset_password')}}" + }, + { + "name":"myrole", + "label":"我的角色", + "url":"{{entire_url('myrole.ui')}}" + }, + { + "name":"logout", + "label":"签退", + "url":"{{entire_url('logout.dspy')}}" + } + ] + } +} diff --git a/wwwroot/user/user_panel.ui b/wwwroot/user/user_panel.ui new file mode 100644 index 0000000..e80acaa --- /dev/null +++ b/wwwroot/user/user_panel.ui @@ -0,0 +1,15 @@ +{ + "id":"user_panel", + "widgettype":"VBox", + "options":{ + "width":"auto" + }, + "subwidgets":[ + { + "widgettype":"urlwidget", + "options":{ + "url":"{{entire_url('user.ui')}}" + } + } + ] +} diff --git a/wwwroot/user/userapikey/add_userapikey.dspy b/wwwroot/user/userapikey/add_userapikey.dspy new file mode 100644 index 0000000..143f5a2 --- /dev/null +++ b/wwwroot/user/userapikey/add_userapikey.dspy @@ -0,0 +1,25 @@ + +ns = params_kw.copy() +id = params_kw.id +if not id or len(id) > 32: + id = uuid() +ns['id'] = id +db = DBPools() +async with db.sqlorContext('sage') as sor: + r = await sor.C('userapikey', ns.copy()) + return { + "widgettype":"Message", + "options":{ + "user_data":ns, + "title":"Add Success", + "message":"ok" + } + } + +return { + "widgettype":"Error", + "options":{ + "title":"Add Error", + "message":"failed" + } +} \ No newline at end of file diff --git a/wwwroot/user/userapikey/delete_userapikey.dspy b/wwwroot/user/userapikey/delete_userapikey.dspy new file mode 100644 index 0000000..ec1039b --- /dev/null +++ b/wwwroot/user/userapikey/delete_userapikey.dspy @@ -0,0 +1,24 @@ + +ns = { + 'id':params_kw['id'], +} +db = DBPools() +async with db.sqlorContext('sage') as sor: + r = await sor.D('userapikey', ns) + print('delete success'); + return { + "widgettype":"Message", + "options":{ + "title":"Delete Success", + "message":"ok" + } + } + +print('Delete failed'); +return { + "widgettype":"Error", + "options":{ + "title":"Delete Error", + "message":"failed" + } +} \ No newline at end of file diff --git a/wwwroot/user/userapikey/get_userapikey.dspy b/wwwroot/user/userapikey/get_userapikey.dspy new file mode 100644 index 0000000..085fa59 --- /dev/null +++ b/wwwroot/user/userapikey/get_userapikey.dspy @@ -0,0 +1,74 @@ + +ns = params_kw.copy() +print(f'get_userapikey.dspy:{ns=}') +if not ns.get('page'): + ns['page'] = 1 +if not ns.get('sort'): + + ns['sort'] = 'id' + +filterjson = params_kw.get('data_filter') +userid = await get_user() +ns['userid'] = userid +if not filterjson: + fields = [ f['name'] for f in [ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32 + }, + { + "name": "providerid", + "title": "\u4f9b\u5e94\u5546id", + "type": "str", + "length": 200 + }, + { + "name": "customerid", + "title": "\u7528\u6237id", + "type": "str", + "length": 32, + "default": "0" + }, + { + "name": "apikey", + "title": "api\u5bc6\u94a5", + "type": "str", + "length": 4000, + "default": "0" + }, + { + "name": "secretkey", + "title": "\u9644\u5c5e\u5bc6\u94a5", + "type": "str", + "length": 4000 + }, + { + "name": "rfname", + "title": "\u51fd\u6570\u540d", + "type": "str", + "length": 400 + } +] ] + filterjson = default_filterjson(fields, ns) +sql = '''select a.*, b.providerid_text, c.customerid_text +from (select y.* from users x, userapikey y where x.id=${userid}$ and x.orgid = y.customerid) a left join (select id as providerid, + orgname as providerid_text from organization where 1 = 1) b on a.providerid = b.providerid left join (select id as customerid, + orgname as customerid_text from organization where 1 = 1) c on a.customerid = c.customerid''' + +if filterjson: + dbf = DBFilter(filterjson) + conds = dbf.gen(ns) + if conds: + ns.update(dbf.consts) + sql += ' and ' + conds +info(f'{ns=},{sql=}') +db = DBPools() +async with db.sqlorContext('sage') as sor: + r = await sor.sqlPaging(sql, ns) + return r +return { + "total":0, + "rows":[] +} diff --git a/wwwroot/user/userapikey/index.ui b/wwwroot/user/userapikey/index.ui new file mode 100644 index 0000000..fa784c8 --- /dev/null +++ b/wwwroot/user/userapikey/index.ui @@ -0,0 +1,126 @@ + +{ + "widgettype":"Tabular", + "options":{ + + + "editable":{ + "new_data_url":"{{entire_url('add_userapikey.dspy')}}", + "delete_data_url":"{{entire_url('delete_userapikey.dspy')}}", + "update_data_url":"{{entire_url('update_userapikey.dspy')}}" + }, + + "data_url":"{{entire_url('./get_userapikey.dspy')}}", + "data_method":"GET", + "data_params":{{json.dumps(params_kw, indent=4)}}, + "row_options":{ + + + + + "browserfields":{ + "excloud": [], + "cwidth": {} +}, + + + "editexclouded":[ + "id" +], + + "fields":[ + { + "name": "id", + "title": "id", + "type": "str", + "length": 32, + "cwidth": 18, + "uitype": "str", + "datatype": "str", + "label": "id" + }, + { + "name": "providerid", + "title": "\u4f9b\u5e94\u5546id", + "type": "str", + "length": 200, + "label": "\u4f9b\u5e94\u5546id", + "cwidth":16, + "uitype": "code", + "cwidth":18, + "valueField": "providerid", + "textField": "providerid_text", + "params": { + "dbname": "sage", + "table": "organization", + "tblvalue": "id", + "tbltext": "orgname", + "valueField": "providerid", + "textField": "providerid_text" + }, + "dataurl": "\/get_code.dspy" + }, + { + "name": "customerid", + "title": "\u7528\u6237id", + "type": "str", + "length": 32, + "cwidth":18, + "default": "0", + "label": "\u7528\u6237id", + "cwidth":16, + "uitype": "code", + "valueField": "customerid", + "textField": "customerid_text", + "params": { + "dbname": "sage", + "table": "organization", + "tblvalue": "id", + "tbltext": "orgname", + "valueField": "customerid", + "textField": "customerid_text" + }, + "dataurl": "\/get_code.dspy" + }, + { + "name": "apikey", + "title": "api\u5bc6\u94a5", + "type": "str", + "length": 4000, + "default": "0", + "cwidth": 18, + "uitype": "text", + "rows": 4, + "datatype": "str", + "label": "api\u5bc6\u94a5" + }, + { + "name": "secretkey", + "title": "\u9644\u5c5e\u5bc6\u94a5", + "type": "str", + "length": 4000, + "cwidth": 18, + "uitype": "text", + "rows": 4, + "datatype": "str", + "label": "\u9644\u5c5e\u5bc6\u94a5" + }, + { + "name": "rfname", + "title": "\u51fd\u6570\u540d", + "type": "str", + "length": 400, + "cwidth": 18, + "uitype": "text", + "rows": 4, + "datatype": "str", + "label": "\u51fd\u6570\u540d" + } +] + }, + + "page_rows":160, + "cache_limit":5 + } + +} diff --git a/wwwroot/user/userapikey/update_userapikey.dspy b/wwwroot/user/userapikey/update_userapikey.dspy new file mode 100644 index 0000000..b31458e --- /dev/null +++ b/wwwroot/user/userapikey/update_userapikey.dspy @@ -0,0 +1,22 @@ + +ns = params_kw.copy() +db = DBPools() +async with db.sqlorContext('sage') as sor: + r = await sor.U('userapikey', ns) + print('update success'); + return { + "widgettype":"Message", + "options":{ + "title":"Update Success", + "message":"ok" + } + } + +print('update failed'); +return { + "widgettype":"Error", + "options":{ + "title":"Update Error", + "message":"failed" + } +} \ No newline at end of file diff --git a/wwwroot/user/wechat_login.ui b/wwwroot/user/wechat_login.ui new file mode 100644 index 0000000..9f65d1d --- /dev/null +++ b/wwwroot/user/wechat_login.ui @@ -0,0 +1,5 @@ +{ + "widgettype":"VBox", + "options":{ + } +} diff --git a/wwwroot/userpassword_login.dspy b/wwwroot/userpassword_login.dspy new file mode 100644 index 0000000..883e3f0 --- /dev/null +++ b/wwwroot/userpassword_login.dspy @@ -0,0 +1,11 @@ +username = params_kw.get('username') +passwd = params_kw.get('passwd') +if not passwd: + return UiError(title='Login failed', message='Password is required') +passwd = password(passwd) +rzt = await check_user_password(request, username, passwd) +if rzt: + return UiMessage(title='Logined', message=f'Welcome back ') +return UiError(title='login failed', message='user and password mismatch') + + diff --git a/wwwroot/userpassword_login.ui b/wwwroot/userpassword_login.ui new file mode 100644 index 0000000..e8933ad --- /dev/null +++ b/wwwroot/userpassword_login.ui @@ -0,0 +1,43 @@ +{ + "id":"login_window", + "widgettype":"PopupWindow", + "options":{ + "auto_open":true, + "anthor":"cc", + "cwidth":20, + "cheight":"14" + }, + "subwidgets":[ + { + "widgettype":"Form", + "id":"userpasswd", + "options":{ + "cols":1, + "fields":[ + { + "name":"username", + "label":"用户名", + "uitype":"str" + }, + { + "name":"passwd", + "label":"密码", + "uitype":"password" + } + ] + } + } + ], + "binds":[ + { + "wid":"userpasswd", + "event":"submit", + "actiontype":"urlwidget", + "target":"self", + "options":{ + "url":"{{entire_url('userpassword_login.dspy')}}" + } + } + ] +} +