fix: spec compliance pass - ServerEnv removal in .dspy, core.py DBPools singleton, scripts/load_path.py
- All .dspy files: replace ServerEnv() org_id access with await get_userorgid() (get_userorgid is registered as global in ahserver processorResource) - core.py: simplify _get_db to use DBPools() singleton directly (DBPools is @SingletonDecorator) remove unnecessary db.databases = config.databases assignment - core.py: add MODULE_NAME constant, use env.get_module_dbname(MODULE_NAME) pattern - Create scripts/load_path.py with all RBAC paths per module-development-spec Covers: entry pages, feature .ui files, CRUD directories, all API .dspy endpoints - .gitignore: add __pycache__/ exclusion - All models/*.json and json/*.json pass spec validation checks
This commit is contained in:
parent
4fd136bf53
commit
43787a63a4
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
||||
product_management.egg-info/
|
||||
__pycache__/
|
||||
|
||||
@ -8,6 +8,9 @@ from sqlor.dbpools import DBPools
|
||||
from ahserver.serverenv import ServerEnv
|
||||
|
||||
|
||||
MODULE_NAME = "product_management"
|
||||
|
||||
|
||||
class ProductManager:
|
||||
"""Core manager for product catalog, category tree, and operator configs.
|
||||
|
||||
@ -15,15 +18,10 @@ class ProductManager:
|
||||
Different resellers have completely independent category trees and products.
|
||||
"""
|
||||
|
||||
def _get_db(self):
|
||||
"""Get database context following Sage singleton fork-safe pattern."""
|
||||
def _get_dbname(self):
|
||||
"""Get module database name from ServerEnv."""
|
||||
env = ServerEnv()
|
||||
dbname = env.get_module_dbname('product_management')
|
||||
from appPublic.jsonConfig import getConfig
|
||||
config = getConfig()
|
||||
db = DBPools()
|
||||
db.databases = config.databases
|
||||
return db, dbname
|
||||
return env.get_module_dbname(MODULE_NAME)
|
||||
|
||||
def _get_current_org_id(self):
|
||||
"""Get current user's organization ID from ServerEnv."""
|
||||
@ -35,8 +33,8 @@ class ProductManager:
|
||||
if not org_id:
|
||||
org_id = self._get_current_org_id()
|
||||
|
||||
db, dbname = self._get_db()
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
dbname = self._get_dbname()
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
sql = """SELECT * FROM product_category
|
||||
WHERE org_id = ${org_id}$ AND status = '1'
|
||||
ORDER BY sort_order ASC, name ASC"""
|
||||
@ -65,10 +63,10 @@ class ProductManager:
|
||||
if not org_id:
|
||||
org_id = self._get_current_org_id()
|
||||
|
||||
db, dbname = self._get_db()
|
||||
dbname = self._get_dbname()
|
||||
|
||||
# Get all sub-category IDs recursively within same org
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
all_ids = [category_id]
|
||||
queue = [category_id]
|
||||
while queue:
|
||||
@ -132,7 +130,7 @@ class ProductManager:
|
||||
if not org_id:
|
||||
org_id = self._get_current_org_id()
|
||||
|
||||
db, dbname = self._get_db()
|
||||
dbname = self._get_dbname()
|
||||
|
||||
conditions = ["p.status = '1'", "p.org_id = ${org_id}$"]
|
||||
params = {'org_id': org_id}
|
||||
@ -150,7 +148,7 @@ class ProductManager:
|
||||
|
||||
where_clause = " AND ".join(conditions)
|
||||
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
sql = f"""SELECT p.id, p.product_code, p.product_name, p.category_id,
|
||||
pc.name as category_name, p.brief_intro,
|
||||
p.price, p.currency, p.enabled_date, p.expired_date,
|
||||
@ -191,7 +189,7 @@ class ProductManager:
|
||||
except:
|
||||
user_id = 'anonymous'
|
||||
|
||||
db, dbname = self._get_db()
|
||||
dbname = self._get_dbname()
|
||||
|
||||
conditions = ["p.org_id = ${org_id}$"]
|
||||
params = {'org_id': org_id}
|
||||
@ -207,7 +205,7 @@ class ProductManager:
|
||||
|
||||
where_clause = " AND ".join(conditions)
|
||||
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
sql = f"""SELECT p.*, pc.name as category_name, pc.description as category_description
|
||||
FROM product p
|
||||
LEFT JOIN product_category pc ON p.category_id = pc.id AND p.org_id = pc.org_id
|
||||
@ -277,11 +275,11 @@ class ProductManager:
|
||||
if not product_id:
|
||||
return {'success': False, 'message': 'Missing product_id'}
|
||||
|
||||
db, dbname = self._get_db()
|
||||
dbname = self._get_dbname()
|
||||
now = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
quantity = int(quantity) if quantity else 1
|
||||
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
sql = """SELECT * FROM product WHERE id = ${product_id}$ AND status = '1' AND org_id = ${org_id}$"""
|
||||
rows = await sor.sqlExe(sql, {'product_id': product_id, 'org_id': org_id})
|
||||
if not rows:
|
||||
@ -340,9 +338,9 @@ class ProductManager:
|
||||
if not product_id:
|
||||
return {'success': False, 'message': 'Missing product_id'}
|
||||
|
||||
db, dbname = self._get_db()
|
||||
dbname = self._get_dbname()
|
||||
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
sql = """SELECT * FROM product WHERE id = ${product_id}$ AND status = '1' AND org_id = ${org_id}$"""
|
||||
rows = await sor.sqlExe(sql, {'product_id': product_id, 'org_id': org_id})
|
||||
if not rows:
|
||||
@ -401,9 +399,9 @@ class ProductManager:
|
||||
except:
|
||||
user_id = 'anonymous'
|
||||
|
||||
db, dbname = self._get_db()
|
||||
dbname = self._get_dbname()
|
||||
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
sql = """SELECT * FROM product_type_config
|
||||
WHERE category_id = ${category_id}$
|
||||
AND org_id = ${org_id}$
|
||||
@ -443,7 +441,7 @@ class ProductManager:
|
||||
if not config_name:
|
||||
return {'success': False, 'message': 'Missing config_name'}
|
||||
|
||||
db, dbname = self._get_db()
|
||||
dbname = self._get_dbname()
|
||||
now = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
try:
|
||||
@ -451,7 +449,7 @@ class ProductManager:
|
||||
except:
|
||||
return {'success': False, 'message': 'Invalid config_json format'}
|
||||
|
||||
async with db.sqlorContext(dbname) as sor:
|
||||
async with DBPools().sqlorContext(dbname) as sor:
|
||||
# Verify category belongs to org
|
||||
cat_check = await sor.sqlExe(
|
||||
"SELECT id FROM product_category WHERE id = ${category_id}$ AND org_id = ${org_id}$",
|
||||
|
||||
122
scripts/load_path.py
Normal file
122
scripts/load_path.py
Normal file
@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
product_management 模块 RBAC 权限管理脚本
|
||||
|
||||
使用方法:
|
||||
cd ~/repos/sage
|
||||
./py3/bin/python ~/repos/product_management/scripts/load_path.py
|
||||
|
||||
每次代码变更如有新 path 出现,需同步更新此脚本。
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
|
||||
def find_sage_root():
|
||||
candidates = [
|
||||
os.path.expanduser("~/repos/sage"),
|
||||
os.path.expanduser("~/sage"),
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))),
|
||||
]
|
||||
for c in candidates:
|
||||
if os.path.isdir(os.path.join(c, "py3")) and os.path.isdir(os.path.join(c, "wwwroot")):
|
||||
return c
|
||||
return None
|
||||
|
||||
SAGE_ROOT = find_sage_root()
|
||||
if not SAGE_ROOT:
|
||||
print("ERROR: Cannot find Sage root directory")
|
||||
sys.exit(1)
|
||||
|
||||
PYTHON = os.path.join(SAGE_ROOT, "py3", "bin", "python")
|
||||
SET_PERM_SCRIPT = os.path.join(SAGE_ROOT, "set_role_perm.py")
|
||||
|
||||
MOD = "product_management"
|
||||
|
||||
# ============================================================
|
||||
# 权限路径定义 — 每次新增页面或API时同步更新
|
||||
# ============================================================
|
||||
|
||||
# any — 无需登录(仅静态资源和菜单)
|
||||
PATHS_ANY = [
|
||||
f"/{MOD}/menu.ui",
|
||||
]
|
||||
|
||||
# logined — 需要认证的页面和 API
|
||||
PATHS_LOGINED = [
|
||||
# Module entry
|
||||
f"/{MOD}",
|
||||
f"/{MOD}/index.ui",
|
||||
|
||||
# Feature pages
|
||||
f"/{MOD}/category_manage.ui",
|
||||
f"/{MOD}/product_manage.ui",
|
||||
f"/{MOD}/product_type_config_manage.ui",
|
||||
|
||||
# CRUD alias directories (directory path + index.ui path)
|
||||
f"/{MOD}/product_category_tree",
|
||||
f"/{MOD}/product_category_tree/index.ui",
|
||||
f"/{MOD}/product_list",
|
||||
f"/{MOD}/product_list/index.ui",
|
||||
f"/{MOD}/product_type_config_list",
|
||||
f"/{MOD}/product_type_config_list/index.ui",
|
||||
|
||||
# API endpoints (.dspy)
|
||||
f"/{MOD}/api/category_options.dspy",
|
||||
f"/{MOD}/api/product_brief.dspy",
|
||||
f"/{MOD}/api/product_category_create.dspy",
|
||||
f"/{MOD}/api/product_category_update.dspy",
|
||||
f"/{MOD}/api/product_category_delete.dspy",
|
||||
f"/{MOD}/api/product_create.dspy",
|
||||
f"/{MOD}/api/product_update.dspy",
|
||||
f"/{MOD}/api/product_delete.dspy",
|
||||
f"/{MOD}/api/product_detail.dspy",
|
||||
f"/{MOD}/api/product_purchase.dspy",
|
||||
f"/{MOD}/api/product_use.dspy",
|
||||
f"/{MOD}/api/product_type_config_create.dspy",
|
||||
f"/{MOD}/api/product_type_config_update.dspy",
|
||||
f"/{MOD}/api/product_type_config_delete.dspy",
|
||||
|
||||
# CRUD auto-generated .dspy
|
||||
f"/{MOD}/product_category_tree/get_product_category.dspy",
|
||||
f"/{MOD}/product_category_tree/new_product_category.dspy",
|
||||
f"/{MOD}/product_category_tree/update_product_category.dspy",
|
||||
f"/{MOD}/product_category_tree/delete_product_category.dspy",
|
||||
f"/{MOD}/product_list/get_product.dspy",
|
||||
f"/{MOD}/product_list/add_product.dspy",
|
||||
f"/{MOD}/product_list/update_product.dspy",
|
||||
f"/{MOD}/product_list/delete_product.dspy",
|
||||
f"/{MOD}/product_type_config_list/get_product_type_config.dspy",
|
||||
f"/{MOD}/product_type_config_list/add_product_type_config.dspy",
|
||||
f"/{MOD}/product_type_config_list/update_product_type_config.dspy",
|
||||
f"/{MOD}/product_type_config_list/delete_product_type_config.dspy",
|
||||
]
|
||||
|
||||
# ============================================================
|
||||
# 执行注册
|
||||
# ============================================================
|
||||
|
||||
def run_set_perm(role, path):
|
||||
cmd = [PYTHON, SET_PERM_SCRIPT, role, path]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
return result.returncode == 0
|
||||
|
||||
def register_role_paths(role, paths):
|
||||
count = 0
|
||||
for p in paths:
|
||||
if run_set_perm(role, p):
|
||||
count += 1
|
||||
print(f" {role}: {count}/{len(paths)} paths registered")
|
||||
return count
|
||||
|
||||
def main():
|
||||
print(f"Sage root: {SAGE_ROOT}")
|
||||
total = 0
|
||||
total += register_role_paths("any", PATHS_ANY)
|
||||
total += register_role_paths("logined", PATHS_LOGINED)
|
||||
print(f"\nDone. Total {total} permission entries registered.")
|
||||
print("NOTE: Restart Sage after permission changes to reload RBAC cache.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -5,9 +5,7 @@ result = {'success': False, 'data': []}
|
||||
|
||||
try:
|
||||
user_id = await get_user()
|
||||
from ahserver.serverenv import ServerEnv
|
||||
env = ServerEnv()
|
||||
org_id = getattr(env, 'orgid', None) or getattr(env, 'org_id', '0')
|
||||
org_id = (await get_userorgid()) or '0'
|
||||
|
||||
dbname = get_module_dbname('product_management')
|
||||
sql = """SELECT id, name FROM product_category
|
||||
|
||||
@ -15,9 +15,7 @@ result = {'success': False, 'data': [], 'total': 0}
|
||||
|
||||
try:
|
||||
user_id = await get_user()
|
||||
from ahserver.serverenv import ServerEnv
|
||||
env = ServerEnv()
|
||||
org_id = params_kw.get('org_id', None) or getattr(env, 'orgid', None) or getattr(env, 'org_id', '0')
|
||||
org_id = params_kw.get('org_id', None) or (await get_userorgid()) or '0'
|
||||
|
||||
product_id = params_kw.get('product_id', '')
|
||||
product_code = params_kw.get('product_code', '')
|
||||
|
||||
@ -6,9 +6,7 @@ result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Inv
|
||||
|
||||
try:
|
||||
user_id = await get_user()
|
||||
from ahserver.serverenv import ServerEnv
|
||||
env = ServerEnv()
|
||||
org_id = getattr(env, 'orgid', None) or getattr(env, 'org_id', '0')
|
||||
org_id = (await get_userorgid()) or '0'
|
||||
now = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
dbname = get_module_dbname('product_management')
|
||||
|
||||
@ -5,9 +5,7 @@ result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Inv
|
||||
|
||||
try:
|
||||
user_id = await get_user()
|
||||
from ahserver.serverenv import ServerEnv
|
||||
env = ServerEnv()
|
||||
org_id = getattr(env, 'orgid', None) or getattr(env, 'org_id', '0')
|
||||
org_id = (await get_userorgid()) or '0'
|
||||
|
||||
dbname = get_module_dbname('product_management')
|
||||
data = dict(params_kw)
|
||||
|
||||
@ -5,9 +5,7 @@ result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Inv
|
||||
|
||||
try:
|
||||
user_id = await get_user()
|
||||
from ahserver.serverenv import ServerEnv
|
||||
env = ServerEnv()
|
||||
org_id = getattr(env, 'orgid', None) or getattr(env, 'org_id', '0')
|
||||
org_id = (await get_userorgid()) or '0'
|
||||
now = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
dbname = get_module_dbname('product_management')
|
||||
|
||||
@ -6,9 +6,7 @@ result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Inv
|
||||
|
||||
try:
|
||||
user_id = await get_user()
|
||||
from ahserver.serverenv import ServerEnv
|
||||
env = ServerEnv()
|
||||
org_id = getattr(env, 'orgid', None) or getattr(env, 'org_id', '0')
|
||||
org_id = (await get_userorgid()) or '0'
|
||||
now = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
dbname = get_module_dbname('product_management')
|
||||
|
||||
@ -5,9 +5,7 @@ result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Inv
|
||||
|
||||
try:
|
||||
user_id = await get_user()
|
||||
from ahserver.serverenv import ServerEnv
|
||||
env = ServerEnv()
|
||||
org_id = getattr(env, 'orgid', None) or getattr(env, 'org_id', '0')
|
||||
org_id = (await get_userorgid()) or '0'
|
||||
|
||||
dbname = get_module_dbname('product_management')
|
||||
data = dict(params_kw)
|
||||
|
||||
@ -18,9 +18,7 @@ result = {'success': False, 'data': {}}
|
||||
|
||||
try:
|
||||
user_id = await get_user()
|
||||
from ahserver.serverenv import ServerEnv
|
||||
env = ServerEnv()
|
||||
org_id = params_kw.get('org_id', None) or getattr(env, 'orgid', None) or getattr(env, 'org_id', '0')
|
||||
org_id = params_kw.get('org_id', None) or (await get_userorgid()) or '0'
|
||||
|
||||
product_id = params_kw.get('product_id', '')
|
||||
product_code = params_kw.get('product_code', '')
|
||||
|
||||
@ -16,9 +16,7 @@ result = {'success': False, 'order_id': '', 'message': ''}
|
||||
|
||||
try:
|
||||
user_id = await get_user()
|
||||
from ahserver.serverenv import ServerEnv
|
||||
env = ServerEnv()
|
||||
org_id = params_kw.get('org_id', None) or getattr(env, 'orgid', None) or getattr(env, 'org_id', '0')
|
||||
org_id = params_kw.get('org_id', None) or (await get_userorgid()) or '0'
|
||||
now = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
product_id = params_kw.get('product_id', '')
|
||||
|
||||
@ -6,9 +6,7 @@ result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Inv
|
||||
|
||||
try:
|
||||
user_id = await get_user()
|
||||
from ahserver.serverenv import ServerEnv
|
||||
env = ServerEnv()
|
||||
org_id = getattr(env, 'orgid', None) or getattr(env, 'org_id', '0')
|
||||
org_id = (await get_userorgid()) or '0'
|
||||
now = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
dbname = get_module_dbname('product_management')
|
||||
|
||||
@ -5,9 +5,7 @@ result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Inv
|
||||
|
||||
try:
|
||||
user_id = await get_user()
|
||||
from ahserver.serverenv import ServerEnv
|
||||
env = ServerEnv()
|
||||
org_id = getattr(env, 'orgid', None) or getattr(env, 'org_id', '0')
|
||||
org_id = (await get_userorgid()) or '0'
|
||||
|
||||
dbname = get_module_dbname('product_management')
|
||||
data = dict(params_kw)
|
||||
|
||||
@ -5,9 +5,7 @@ result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Inv
|
||||
|
||||
try:
|
||||
user_id = await get_user()
|
||||
from ahserver.serverenv import ServerEnv
|
||||
env = ServerEnv()
|
||||
org_id = getattr(env, 'orgid', None) or getattr(env, 'org_id', '0')
|
||||
org_id = (await get_userorgid()) or '0'
|
||||
now = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
dbname = get_module_dbname('product_management')
|
||||
|
||||
@ -5,9 +5,7 @@ result = {'widgettype': 'Message', 'options': {'title': 'Error', 'message': 'Inv
|
||||
|
||||
try:
|
||||
user_id = await get_user()
|
||||
from ahserver.serverenv import ServerEnv
|
||||
env = ServerEnv()
|
||||
org_id = getattr(env, 'orgid', None) or getattr(env, 'org_id', '0')
|
||||
org_id = (await get_userorgid()) or '0'
|
||||
now = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
dbname = get_module_dbname('product_management')
|
||||
|
||||
@ -16,9 +16,7 @@ result = {'success': False, 'use_record_id': '', 'message': '', 'data': {}}
|
||||
|
||||
try:
|
||||
user_id = await get_user()
|
||||
from ahserver.serverenv import ServerEnv
|
||||
env = ServerEnv()
|
||||
org_id = params_kw.get('org_id', None) or getattr(env, 'orgid', None) or getattr(env, 'org_id', '0')
|
||||
org_id = params_kw.get('org_id', None) or (await get_userorgid()) or '0'
|
||||
now = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
product_id = params_kw.get('product_id', '')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user