diff --git a/wwwroot/api/fix_stuck.dspy b/wwwroot/api/fix_stuck.dspy index ec46a6f..b50b3f4 100644 --- a/wwwroot/api/fix_stuck.dspy +++ b/wwwroot/api/fix_stuck.dspy @@ -1,24 +1,33 @@ -# fix_stuck.dspy - 清除卡住任务的 claimed_by +# fix_stuck.dspy - 清除指定项目卡住任务的 claimed_by(仅项目 owner 可操作) + +user_id = await get_user() +if not user_id: + return json.dumps({"error": "未登录"}, ensure_ascii=False) + +project_id = params_kw.get('project_id', '').strip() +if not project_id: + return json.dumps({"error": "缺少 project_id"}, ensure_ascii=False) dbname = get_module_dbname('pipeline-sdlc') async with DBPools().sqlorContext(dbname) as sor: - # 清除 submitted 状态但有 claimed_by 的僵尸任务 - result = await sor.sqlExe( - "UPDATE pipeline_tasks SET claimed_by=NULL WHERE state='submitted' AND claimed_by IS NOT NULL", - {}) - # 同时清除 review 状态但有 claimed_by 的(超时未处理的) - result2 = await sor.sqlExe( - "UPDATE pipeline_tasks SET claimed_by=NULL WHERE state='review' AND claimed_by IS NOT NULL", - {}) - + ok, emsg = await check_project_owner(project_id, user_id, sor) + if not ok: + return json.dumps({"error": emsg}, ensure_ascii=False) + # 清除该项目 submitted/review 状态但有 claimed_by 的僵尸任务 + await sor.sqlExe( + "UPDATE pipeline_tasks SET claimed_by=NULL " + "WHERE tenant_id=${pid}$ AND state IN ('submitted','review') AND claimed_by IS NOT NULL", + {"pid": project_id}) + await sor.sqlExe("COMMIT", {}) # 查看还有多少卡住的 stuck = await sor.sqlExe( - "SELECT id, title, role, state, claimed_by FROM pipeline_tasks WHERE claimed_by IS NOT NULL", - {}) - + "SELECT id, title, role, state, claimed_by FROM pipeline_tasks " + "WHERE tenant_id=${pid}$ AND claimed_by IS NOT NULL", + {"pid": project_id}) + await sor.sqlExe("COMMIT", {}) + result = { - "cleared_submitted": "done", - "cleared_review": "done", + "cleared": "done", "remaining_stuck": [{"id": getattr(t,'id','')[:12], "state": getattr(t,'state',''), "claimed_by": getattr(t,'claimed_by','')[:8]} for t in (stuck or [])] } diff --git a/wwwroot/api/sd_iteration_create.dspy b/wwwroot/api/sd_iteration_create.dspy index da941d1..54d1c7d 100644 --- a/wwwroot/api/sd_iteration_create.dspy +++ b/wwwroot/api/sd_iteration_create.dspy @@ -14,7 +14,10 @@ data = { try: async with get_sor_context(request._run_ns, 'pipeline') as sor: + ok, emsg = await check_project_owner(data.get('project_id', ''), user_id, sor) + if not ok: + return {"widgettype":"Error","options":{"title":"操作失败","message":emsg,"cwidth":16,"cheight":9,"timeout":3}} await sor.C('sd_iterations', data) - return {"widgettype":"Message","options":{"title":"创建成功","message":"ok","cwidth":16,"cheight":9,"timeout":3,"user_data": data} + return {"widgettype":"Message","options":{"title":"创建成功","message":"ok","cwidth":16,"cheight":9,"timeout":3},"user_data": data} except Exception as e: return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}} diff --git a/wwwroot/api/sd_iteration_delete.dspy b/wwwroot/api/sd_iteration_delete.dspy index 0fdd976..0dd9042 100644 --- a/wwwroot/api/sd_iteration_delete.dspy +++ b/wwwroot/api/sd_iteration_delete.dspy @@ -8,6 +8,12 @@ if not rec_id: try: async with get_sor_context(request._run_ns, 'pipeline') as sor: + recs = await sor.sqlExe("SELECT project_id FROM sd_iterations WHERE id=${id}$", {"id": rec_id}) + await sor.sqlExe("COMMIT", {}) + pid = getattr(recs[0], 'project_id', '') if recs else '' + ok, emsg = await check_project_owner(pid, user_id, sor) + if not ok: + return {"widgettype":"Error","options":{"title":"操作失败","message":emsg,"cwidth":16,"cheight":9,"timeout":3}} await sor.D('sd_iterations', {'id': rec_id}) return {"widgettype":"Message","options":{"title":"操作成功","message":"ok","cwidth":16,"cheight":9,"timeout":3}} except Exception as e: diff --git a/wwwroot/api/sd_iteration_update.dspy b/wwwroot/api/sd_iteration_update.dspy index 62463f7..6e153b1 100644 --- a/wwwroot/api/sd_iteration_update.dspy +++ b/wwwroot/api/sd_iteration_update.dspy @@ -20,6 +20,12 @@ if 'priority' in params_kw: try: async with get_sor_context(request._run_ns, 'pipeline') as sor: + recs = await sor.sqlExe("SELECT project_id FROM sd_iterations WHERE id=${id}$", {"id": rec_id}) + await sor.sqlExe("COMMIT", {}) + pid = getattr(recs[0], 'project_id', '') if recs else '' + ok, emsg = await check_project_owner(pid, user_id, sor) + if not ok: + return {"widgettype":"Error","options":{"title":"操作失败","message":emsg,"cwidth":16,"cheight":9,"timeout":3}} await sor.U('sd_iterations', data) return {"widgettype":"Message","options":{"title":"操作成功","message":"ok","cwidth":16,"cheight":9,"timeout":3}} except Exception as e: diff --git a/wwwroot/api/sd_project_create.dspy b/wwwroot/api/sd_project_create.dspy index 0b09277..8f49148 100644 --- a/wwwroot/api/sd_project_create.dspy +++ b/wwwroot/api/sd_project_create.dspy @@ -13,12 +13,13 @@ data = { 'pipeline_id': params_kw.get('pipeline_id', '') or 'sdlc_general', 'status': params_kw.get('status', ''), 'org_id': org_id, + 'created_by': user_id, 'created_at': curDateString(), } try: async with get_sor_context(request._run_ns, 'pipeline') as sor: await sor.C('sd_projects', data) - return {"widgettype":"Message","options":{"title":"创建成功","message":"ok","cwidth":16,"cheight":9,"timeout":3,"user_data": data} + return {"widgettype":"Message","options":{"title":"创建成功","message":"ok","cwidth":16,"cheight":9,"timeout":3},"user_data": data} except Exception as e: return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}} diff --git a/wwwroot/api/sd_project_delete.dspy b/wwwroot/api/sd_project_delete.dspy index a993977..d70bb14 100644 --- a/wwwroot/api/sd_project_delete.dspy +++ b/wwwroot/api/sd_project_delete.dspy @@ -8,6 +8,9 @@ if not rec_id: try: async with get_sor_context(request._run_ns, 'pipeline') as sor: + ok, emsg = await check_project_owner(rec_id, user_id, sor) + if not ok: + return {"widgettype":"Error","options":{"title":"操作失败","message":emsg,"cwidth":16,"cheight":9,"timeout":3}} await sor.D('sd_projects', {'id': rec_id}) return {"widgettype":"Message","options":{"title":"操作成功","message":"ok","cwidth":16,"cheight":9,"timeout":3}} except Exception as e: diff --git a/wwwroot/api/sd_project_update.dspy b/wwwroot/api/sd_project_update.dspy index 48d79ea..92bc267 100644 --- a/wwwroot/api/sd_project_update.dspy +++ b/wwwroot/api/sd_project_update.dspy @@ -24,6 +24,9 @@ if 'status' in params_kw: try: async with get_sor_context(request._run_ns, 'pipeline') as sor: + ok, emsg = await check_project_owner(rec_id, user_id, sor) + if not ok: + return {"widgettype":"Error","options":{"title":"操作失败","message":emsg,"cwidth":16,"cheight":9,"timeout":3}} await sor.U('sd_projects', data) return {"widgettype":"Message","options":{"title":"操作成功","message":"ok","cwidth":16,"cheight":9,"timeout":3}} except Exception as e: diff --git a/wwwroot/api/submit_bug.dspy b/wwwroot/api/submit_bug.dspy index 46b15fd..9ade5da 100644 --- a/wwwroot/api/submit_bug.dspy +++ b/wwwroot/api/submit_bug.dspy @@ -24,6 +24,22 @@ data = { try: async with get_sor_context(request._run_ns, 'pipeline') as sor: + # 同机构校验:人类报告的 bug 须与项目同机构(agent 报告跳过) + if reporter_type == 'human' and data.get('iteration_id'): + it = await sor.sqlExe( + "SELECT project_id FROM sd_iterations WHERE id=${iid}$", + {"iid": data['iteration_id']}) + await sor.sqlExe("COMMIT", {}) + pid = getattr(it[0], 'project_id', '') if it else '' + if pid: + p = await sor.sqlExe("SELECT org_id FROM sd_projects WHERE id=${pid}$", {"pid": pid}) + await sor.sqlExe("COMMIT", {}) + proj_org = getattr(p[0], 'org_id', '') if p else '' + u = await sor.sqlExe("SELECT orgid FROM users WHERE id=${u}$", {"u": user_id}) + await sor.sqlExe("COMMIT", {}) + user_org = getattr(u[0], 'orgid', '') if u else '' + if proj_org and user_org != proj_org: + return json.dumps({'status': 'error', 'message': '仅同机构用户可报告 Bug'}, ensure_ascii=False) await sor.C('sd_bugs', data) return json.dumps({'status': 'ok', 'id': data['id']}, ensure_ascii=False) except Exception as e: