Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
d442bf0
[OND211-2329] : Create user api and tests, update user api and tests.
hetavi-bluexkye Nov 10, 2025
61b84b0
[OND211-2329]: Updated create_user and update_user APIs and correspon…
hetavi-bluexkye Nov 11, 2025
7890c31
[OND211-2329]: Added list_users api.
hetavi-bluexkye Nov 11, 2025
1d23b46
[OND211-2329]: Added delete user api and tests.
hetavi-bluexkye Nov 11, 2025
d50c085
[OND211-2329]: Removed unnecessary encryption and decryption for pass…
hetavi-bluexkye Nov 11, 2025
de8dcf4
[OND211-2329]: Updated create user and update user API's to handle auth.
hetavi-bluexkye Nov 12, 2025
1cc081e
[OND211-2329]: Updated create user and update user tests's to test th…
hetavi-bluexkye Nov 12, 2025
043b06a
[OND211-2329]: Updated list users and delete user API's & tests to ha…
hetavi-bluexkye Nov 12, 2025
00b4767
[OND211-2329]: Added create department API and tests.
hetavi-bluexkye Nov 12, 2025
b21f39f
Merge remote-tracking branch 'origin/main' into feature/OND211-2329-C…
hetavi-bluexkye Nov 13, 2025
d7b9925
[OND211-2329]: Updated team creation API to allow only added models a…
hetavi-bluexkye Nov 13, 2025
b388a3d
[OND211-2329]: Added API and tests to add/remove users in a team.
hetavi-bluexkye Nov 13, 2025
ed7b44f
[OND211-2329]: Updatd API's to add users to a team, remove users from…
hetavi-bluexkye Nov 13, 2025
060ec78
[OND211-2329]: Updatd API's to add users to a team, remove users from…
hetavi-bluexkye Nov 13, 2025
3870f50
[OND211-2329]: Added API to update team settings.
hetavi-bluexkye Nov 13, 2025
91013f7
Initial plan
Copilot Nov 13, 2025
a80b2ff
Add comprehensive test coverage: security, performance, edge cases, a…
Copilot Nov 13, 2025
113620e
[OND211-2329]: Updated the user tests to follow typings and shifted r…
hetavi-bluexkye Nov 14, 2025
7253068
[OND211-2329]: Added department model and API to create department, a…
hetavi-bluexkye Nov 14, 2025
f3966d2
[OND211-2329]: Updated the create team API to handle -ve credit score.
hetavi-bluexkye Nov 17, 2025
83bf319
[OND211-2329]: Added API to promote/demote team admins.
hetavi-bluexkye Nov 17, 2025
ab88159
[OND211-2329]: Added API to update department.
hetavi-bluexkye Nov 17, 2025
a0204b0
[OND211-2329]: Added API to delete department.
hetavi-bluexkye Nov 17, 2025
7a8bba8
[OND211-2329]: Added API to list users in a department.
hetavi-bluexkye Nov 17, 2025
0e2c72a
[OND211-2329]: Added tests to test the create department API.
hetavi-bluexkye Nov 17, 2025
5413628
[OND211-2329]: Added tests to test the add/remove members to a depart…
hetavi-bluexkye Nov 17, 2025
feb6a3a
[OND211-2329]: updated the create and update user API's to handle edg…
hetavi-bluexkye Nov 18, 2025
f3dd4bd
[OND211-2329]: updated the create and update user API's to handle sec…
hetavi-bluexkye Nov 18, 2025
bd83d24
[OND211-2329]: Updated user management tests, fixed typings.
hetavi-bluexkye Nov 18, 2025
f2eeb2d
[OND211-2329]: Updated user management tests, fixed some pycharm edit…
hetavi-bluexkye Nov 18, 2025
26a17e6
[OND211-2329]: Updated conftest files, fixed typings.
hetavi-bluexkye Nov 18, 2025
c38ec24
[OND211-2329]: Added few tests for department API and updated typings.
hetavi-bluexkye Nov 18, 2025
6ef0668
[OND211-2329]: Added helper methods for department tests.
hetavi-bluexkye Nov 18, 2025
3d02d93
[OND211-2329]: Updated import statements alignment.
hetavi-bluexkye Nov 19, 2025
23915dd
[OND211-2329]: Added group model and API endpoint's
hetavi-bluexkye Nov 19, 2025
1ce52f0
[OND211-2329]: Updated create user API to handle encrypted passwords,…
hetavi-bluexkye Nov 19, 2025
5af04fd
[OND211-2329]: Updated tets, fixed typings.
hetavi-bluexkye Nov 19, 2025
3e7d57b
[OND211-2329]: Updated team API's and added create group tests.
hetavi-bluexkye Nov 19, 2025
c50f7b3
[OND211-2329]: Updated create group tests and added tests for add/rem…
hetavi-bluexkye Nov 19, 2025
3ffe94b
[OND211-2329]: Added tests for list members to group.
hetavi-bluexkye Nov 19, 2025
4c8ea1d
[OND211-2329]: Added tests to delete group.
hetavi-bluexkye Nov 19, 2025
b54a6ac
[OND211-2329]: Added tests for update team API.
hetavi-bluexkye Nov 19, 2025
0d48560
[OND211-2329]: Added new tests and updated existing tests for team API.
hetavi-bluexkye Nov 19, 2025
6c68429
[OND211-2329]: Added new tests for promote/demote admin API.
hetavi-bluexkye Nov 19, 2025
d5dcb44
[OND211-2329]: Updated remove member from team API and tests.
hetavi-bluexkye Nov 20, 2025
cd0dc13
[OND211-2329]: Removed Department and group modules.
hetavi-bluexkye Nov 21, 2025
4c3db69
Merge remote-tracking branch 'origin/main' into feature/OND211-2329-C…
hetavi-bluexkye Nov 21, 2025
60a6265
[OND211-2329]: Updated permission on dataset and canvas to share to a…
hetavi-bluexkye Nov 21, 2025
89d27c7
[OND211-2329]: Added permissions for team users to CRUD on datasets a…
hetavi-bluexkye Nov 21, 2025
ed886ff
[OND211-2329]: Allow only team owners to promote/demote admins.
hetavi-bluexkye Nov 21, 2025
b20737c
Merge remote-tracking branch 'origin/main' into feature/OND211-2329-C…
hetavi-bluexkye Nov 24, 2025
13b8f0c
[OND211-2329]: Allowed users to demote themselves from admin.
hetavi-bluexkye Nov 24, 2025
e462d5f
[OND211-2329]: Updated permissions API and added tests.
hetavi-bluexkye Nov 24, 2025
f710354
[OND211-2329]: Removed the API key.
hetavi-bluexkye Nov 24, 2025
f307a18
[OND211-2329]: Updated user tests to remove test data from db.
hetavi-bluexkye Nov 25, 2025
d615f62
[OND211-2329]: Updated team and permissions tests to remove test cach…
hetavi-bluexkye Nov 25, 2025
ce8862e
[OND211-2329]: Updated import statements order.
hetavi-bluexkye Nov 25, 2025
abeb37a
[OND211-2329]: Removed unused imports.
hetavi-bluexkye Nov 26, 2025
55e0ace
[OND211-2329]: Updated add users API and tests to add users with a ro…
hetavi-bluexkye Nov 27, 2025
d92b2fc
[OND211-2329]: Removed unused Makefile.
hetavi-bluexkye Nov 27, 2025
d274e39
Merge branch 'main' into feature/OND211-2329-Check-existing-REST-endp…
teddius Nov 27, 2025
bbd80b9
[OND211-2329]: Fixed ruff and lint issues and updated few test cases …
hetavi-bluexkye Nov 28, 2025
bf21ac7
[OND211-2329]: Fixed import issues.
hetavi-bluexkye Nov 28, 2025
5cd7d13
Merge branch 'main' into feature/OND211-2329-Check-existing-REST-endp…
hetavi-bluexkye Nov 28, 2025
8a9ada6
Merge branch 'main' into feature/OND211-2329-Check-existing-REST-endp…
hetavi-bluexkye Dec 1, 2025
b0ecda4
[OND211-2329]: Fixed ruff checks.
hetavi-bluexkye Dec 1, 2025
0647abc
Merge branch 'main' into feature/OND211-2329-Check-existing-REST-endp…
hetavi-bluexkye Dec 1, 2025
477c72c
[OND211-2329]: Fixed import issues and ruff checks.
hetavi-bluexkye Dec 1, 2025
9f4e0f4
[OND211-2329]: Added Crypto package.
hetavi-bluexkye Dec 1, 2025
d247aab
[OND211-2329]: Updated user list pagination response.
hetavi-bluexkye Dec 1, 2025
063474a
[OND211-2329]: Updated Cryto imports.
hetavi-bluexkye Dec 1, 2025
28029bd
Merge branch 'main' into feature/OND211-2329-Check-existing-REST-endp…
hetavi-bluexkye Dec 2, 2025
0879b2d
[OND211-2329]: Updated imports from Cryto to Cryptodome in all files.
hetavi-bluexkye Dec 2, 2025
eb478f6
[OND211-2329]: Removed unnecessory packages.
hetavi-bluexkye Dec 2, 2025
edbda14
[OND211-2329]: Moved pycryptodomex to test group in pyproject.toml an…
hetavi-bluexkye Dec 2, 2025
525aa84
[OND211-2329]: Updated pycryptodomex imports.
hetavi-bluexkye Dec 2, 2025
c0c22fb
[OND211-2329]: Updated strenum imports.
hetavi-bluexkye Dec 3, 2025
6ed50ac
[OND211-2329]: Updated few imports and fixed a http_api_auth function.
hetavi-bluexkye Dec 3, 2025
415b800
Merge branch 'main' into feature/OND211-2329-Check-existing-REST-endp…
hetavi-bluexkye Dec 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions api/apps/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,18 @@
P = ParamSpec("P")

def _load_user():
jwt = Serializer(secret_key=settings.SECRET_KEY)
authorization = request.headers.get("Authorization")
try:
jwt = Serializer(secret_key=settings.SECRET_KEY)
authorization = request.headers.get("Authorization")
except RuntimeError as e:
# Working outside of request context - no user authenticated
if "request context" in str(e).lower():
return None
# Re-raise other RuntimeErrors
raise
g.user = None
if not authorization:
return
return None

try:
access_token = str(jwt.loads(authorization))
Expand Down
165 changes: 120 additions & 45 deletions api/apps/canvas_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,53 @@
import asyncio
import json
import logging
import time
from functools import partial
from quart import request, Response, make_response
from typing import Any, Dict, Optional

from peewee import MySQLDatabase, PostgresqlDatabase
from quart import (
make_response,
request,
Response,
)
from agent.canvas import Canvas
from agent.component import LLM
from api.apps import current_user, login_required
from api.common.permission_utils import has_permission
from api.db import CanvasCategory
from api.db.services.canvas_service import CanvasTemplateService, UserCanvasService, API4ConversationService
from api.db.db_models import APIToken, Task
from api.db.services.canvas_service import (
API4ConversationService,
CanvasTemplateService,
UserCanvasService,
)
from api.db.services.document_service import DocumentService
from api.db.services.file_service import FileService
from api.db.services.pipeline_operation_log_service import PipelineOperationLogService
from api.db.services.task_service import queue_dataflow, CANVAS_DEBUG_DOC_ID, TaskService
from api.db.services.user_service import TenantService
from api.db.services.pipeline_operation_log_service import (
PipelineOperationLogService,
)
from api.db.services.task_service import (
CANVAS_DEBUG_DOC_ID,
TaskService,
queue_dataflow,
)
from api.db.services.user_service import TenantService, UserTenantService
from api.db.services.user_canvas_version import UserCanvasVersionService
from common.constants import RetCode
from api.utils.api_utils import (
get_data_error_result,
get_json_result,
get_request_json,
server_error_response,
validate_request,
)
from common import settings
from common.constants import RetCode, StatusEnum
from common.misc_utils import get_uuid
from api.utils.api_utils import get_json_result, server_error_response, validate_request, get_data_error_result, \
get_request_json
from agent.canvas import Canvas
from peewee import MySQLDatabase, PostgresqlDatabase
from api.db.db_models import APIToken, Task
import time

from rag.flow.pipeline import Pipeline
from rag.nlp import search
from rag.utils.redis_conn import REDIS_CONN
from common import settings
from api.apps import login_required, current_user


@manager.route('/templates', methods=['GET']) # noqa: F821
@login_required
Expand All @@ -55,35 +76,72 @@ def templates():
async def rm():
req = await get_request_json()
for i in req["canvas_ids"]:
if not UserCanvasService.accessible(i, current_user.id):
# Check delete permission
if not UserCanvasService.accessible(i, current_user.id, required_permission="delete"):
return get_json_result(
data=False, message='Only owner of canvas authorized for this operation.',
code=RetCode.OPERATING_ERROR)
data=False,
message='You do not have delete permission for this canvas.',
code=RetCode.PERMISSION_ERROR
)
UserCanvasService.delete_by_id(i)
return get_json_result(data=True)


@manager.route('/set', methods=['POST']) # noqa: F821
@validate_request("dsl", "title")
@login_required
async def save():
req = await get_request_json()
async def save() -> Any:
req: Dict[str, Any] = await get_request_json()
if not isinstance(req["dsl"], str):
req["dsl"] = json.dumps(req["dsl"], ensure_ascii=False)
req["dsl"] = json.loads(req["dsl"])

# Validate shared_tenant_id if provided
shared_tenant_id: Optional[str] = req.get("shared_tenant_id")
if shared_tenant_id:
if req.get("permission") != "team":
return get_json_result(
data=False,
message="shared_tenant_id can only be set when permission is 'team'",
code=RetCode.ARGUMENT_ERROR
)
# Verify user is a member of the shared tenant

user_tenant = UserTenantService.filter_by_tenant_and_user_id(shared_tenant_id, current_user.id)
if not user_tenant or user_tenant.status != StatusEnum.VALID.value:
return get_json_result(
data=False,
message="You are not a member of the selected team",
code=RetCode.PERMISSION_ERROR
)

cate = req.get("canvas_category", CanvasCategory.Agent)
if "id" not in req:
# Check create permission if sharing with team
if req.get("permission") == "team":
shared_tenant_id: Optional[str] = req.get("shared_tenant_id")
target_tenant_id: str = shared_tenant_id if shared_tenant_id else current_user.id
if not has_permission(target_tenant_id, current_user.id, "canvas", "create"):
return get_json_result(
data=False,
message='You do not have create permission for canvases in this team.',
code=RetCode.PERMISSION_ERROR
)

req["user_id"] = current_user.id
if UserCanvasService.query(user_id=current_user.id, title=req["title"].strip(), canvas_category=cate):
return get_data_error_result(message=f"{req['title'].strip()} already exists.")
req["id"] = get_uuid()
if not UserCanvasService.save(**req):
return get_data_error_result(message="Fail to save canvas.")
else:
if not UserCanvasService.accessible(req["id"], current_user.id):
# Check update permission
if not UserCanvasService.accessible(req["id"], current_user.id, required_permission="update"):
return get_json_result(
data=False, message='Only owner of canvas authorized for this operation.',
code=RetCode.OPERATING_ERROR)
data=False,
message='You do not have update permission for this canvas.',
code=RetCode.PERMISSION_ERROR
)
UserCanvasService.update_by_id(req["id"], req)
# save version
UserCanvasVersionService.insert(user_canvas_id=req["id"], dsl=req["dsl"], title="{0}_{1}".format(req["title"], time.strftime("%Y_%m_%d_%H_%M_%S")))
Expand All @@ -95,7 +153,11 @@ async def save():
@login_required
def get(canvas_id):
if not UserCanvasService.accessible(canvas_id, current_user.id):
return get_data_error_result(message="canvas not found.")
return get_json_result(
data=False,
message='You do not have read permission for this canvas.',
code=RetCode.PERMISSION_ERROR
)
e, c = UserCanvasService.get_by_canvas_id(canvas_id)
return get_json_result(data=c)

Expand Down Expand Up @@ -131,10 +193,13 @@ async def run():
files = req.get("files", [])
inputs = req.get("inputs", {})
user_id = req.get("user_id", current_user.id)
if not await asyncio.to_thread(UserCanvasService.accessible, req["id"], current_user.id):
# Check read permission (to run the canvas)
if not UserCanvasService.accessible(req["id"], current_user.id, required_permission="read"):
return get_json_result(
data=False, message='Only owner of canvas authorized for this operation.',
code=RetCode.OPERATING_ERROR)
data=False,
message='You do not have read permission for this canvas.',
code=RetCode.PERMISSION_ERROR
)

e, cvs = await asyncio.to_thread(UserCanvasService.get_by_id, req["id"])
if not e:
Expand Down Expand Up @@ -197,8 +262,8 @@ async def rerun():
doc["chunk_num"] = 0
doc["token_num"] = 0
DocumentService.clear_chunk_num_when_rerun(doc["id"])
DocumentService.update_by_id(id, doc)
TaskService.filter_delete([Task.doc_id == id])
DocumentService.update_by_id(doc["id"], doc)
TaskService.filter_delete([Task.doc_id == doc["id"]])

dsl = req["dsl"]
dsl["path"] = [req["component_id"]]
Expand All @@ -222,10 +287,14 @@ def cancel(task_id):
@login_required
async def reset():
req = await get_request_json()
if not UserCanvasService.accessible(req["id"], current_user.id):
# Check update permission (to reset the canvas)
if not UserCanvasService.accessible(req["id"], current_user.id, required_permission="update"):
return get_json_result(
data=False, message='Only owner of canvas authorized for this operation.',
code=RetCode.OPERATING_ERROR)
data=False,
message='You do not have update permission for this canvas.',
code=RetCode.PERMISSION_ERROR
)

try:
e, user_canvas = UserCanvasService.get_by_id(req["id"])
if not e:
Expand All @@ -252,7 +321,7 @@ async def upload(canvas_id):
try:
return get_json_result(data=FileService.upload_info(user_id, file, request.args.get("url")))
except Exception as e:
return server_error_response(e)
return server_error_response(e)


@manager.route('/input_form', methods=['GET']) # noqa: F821
Expand Down Expand Up @@ -280,10 +349,13 @@ def input_form():
@login_required
async def debug():
req = await get_request_json()
if not UserCanvasService.accessible(req["id"], current_user.id):
# Check read permission (to debug the canvas)
if not UserCanvasService.accessible(req["id"], current_user.id, required_permission="read"):
return get_json_result(
data=False, message='Only owner of canvas authorized for this operation.',
code=RetCode.OPERATING_ERROR)
data=False,
message='You do not have read permission for this canvas.',
code=RetCode.PERMISSION_ERROR
)
try:
e, user_canvas = UserCanvasService.get_by_id(req["id"])
canvas = Canvas(json.dumps(user_canvas.dsl), current_user.id)
Expand All @@ -294,7 +366,7 @@ async def debug():

if isinstance(component, LLM):
component.set_debug_inputs(req["params"])
component.invoke(**{k: o["value"] for k,o in req["params"].items()})
component.invoke(**{k: o["value"] for k, o in req["params"].items()})
outputs = component.output()
for k in outputs.keys():
if isinstance(outputs[k], partial):
Expand Down Expand Up @@ -406,7 +478,7 @@ def _parse_catalog_schema(db_name: str):
@login_required
def getlistversion(canvas_id):
try:
versions =sorted([c.to_dict() for c in UserCanvasVersionService.list_by_canvas_id(canvas_id)], key=lambda x: x["update_time"]*-1)
versions = sorted([c.to_dict() for c in UserCanvasVersionService.list_by_canvas_id(canvas_id)], key=lambda x: x["update_time"] * -1)
return get_json_result(data=versions)
except Exception as e:
return get_data_error_result(message=f"Error getting history files: {e}")
Expand All @@ -415,7 +487,7 @@ def getlistversion(canvas_id):
#api get version dsl of canvas
@manager.route('/getversion/<version_id>', methods=['GET']) # noqa: F821
@login_required
def getversion( version_id):
def getversion(version_id):
try:
e, version = UserCanvasVersionService.get_by_id(version_id)
if version:
Expand All @@ -436,7 +508,7 @@ def list_canvas():
desc = False
else:
desc = True
owner_ids = [id for id in request.args.get("owner_ids", "").strip().split(",") if id]
owner_ids = [owner_id for owner_id in request.args.get("owner_ids", "").strip().split(",") if owner_id]
if not owner_ids:
tenants = TenantService.get_joined_tenants_by_user_id(current_user.id)
tenants = [m["tenant_id"] for m in tenants]
Expand All @@ -459,12 +531,15 @@ async def setting():
req = await get_request_json()
req["user_id"] = current_user.id

if not UserCanvasService.accessible(req["id"], current_user.id):
# Check update permission (to change settings)
if not UserCanvasService.accessible(req["id"], current_user.id, required_permission="update"):
return get_json_result(
data=False, message='Only owner of canvas authorized for this operation.',
code=RetCode.OPERATING_ERROR)
data=False,
message='You do not have update permission for this canvas.',
code=RetCode.PERMISSION_ERROR
)

e,flow = UserCanvasService.get_by_id(req["id"])
e, flow = UserCanvasService.get_by_id(req["id"])
if not e:
return get_data_error_result(message="canvas not found.")
flow = flow.to_dict()
Expand All @@ -474,7 +549,7 @@ async def setting():
if value := req.get(key):
flow[key] = value

num= UserCanvasService.update_by_id(req["id"], flow)
num = UserCanvasService.update_by_id(req["id"], flow)
return get_json_result(data=num)


Expand Down
Loading