2025-02-25 15:36:25 +01:00
|
|
|
import logging
|
2024-08-28 00:10:27 +02:00
|
|
|
from pathlib import Path
|
2024-08-14 13:46:31 +01:00
|
|
|
from typing import Optional
|
2025-04-05 04:40:01 -06:00
|
|
|
import time
|
2025-05-29 02:08:54 +04:00
|
|
|
import re
|
|
|
|
|
import aiohttp
|
2026-01-08 00:53:21 +04:00
|
|
|
from open_webui.env import AIOHTTP_CLIENT_TIMEOUT
|
2025-09-03 05:49:53 +09:00
|
|
|
from open_webui.models.groups import Groups
|
2025-05-29 02:08:54 +04:00
|
|
|
from pydantic import BaseModel, HttpUrl
|
2025-08-06 01:44:52 +04:00
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
2025-12-29 00:21:18 +04:00
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
from open_webui.internal.db import get_session
|
2025-08-06 01:44:52 +04:00
|
|
|
|
2024-06-10 20:39:55 -07:00
|
|
|
|
2025-09-25 01:49:16 -05:00
|
|
|
from open_webui.models.oauth_sessions import OAuthSessions
|
2024-12-10 00:54:13 -08:00
|
|
|
from open_webui.models.tools import (
|
2024-11-18 06:19:34 -08:00
|
|
|
ToolForm,
|
|
|
|
|
ToolModel,
|
|
|
|
|
ToolResponse,
|
|
|
|
|
ToolUserResponse,
|
2026-01-06 00:00:48 +01:00
|
|
|
ToolAccessResponse,
|
2024-11-18 06:19:34 -08:00
|
|
|
Tools,
|
|
|
|
|
)
|
2026-02-23 16:01:03 -06:00
|
|
|
from open_webui.models.access_grants import AccessGrants
|
2025-09-26 19:01:22 -05:00
|
|
|
from open_webui.utils.plugin import (
|
|
|
|
|
load_tool_module_by_id,
|
|
|
|
|
replace_imports,
|
|
|
|
|
get_tool_module_from_cache,
|
2026-01-22 03:55:07 +04:00
|
|
|
resolve_valves_schema_options,
|
2025-09-26 19:01:22 -05:00
|
|
|
)
|
2025-04-10 19:41:17 -07:00
|
|
|
from open_webui.utils.tools import get_tool_specs
|
2024-12-08 16:01:56 -08:00
|
|
|
from open_webui.utils.auth import get_admin_user, get_verified_user
|
2024-11-17 03:04:31 -08:00
|
|
|
from open_webui.utils.access_control import has_access, has_permission
|
2025-08-18 20:53:46 +04:00
|
|
|
from open_webui.utils.tools import get_tool_servers
|
2025-04-05 04:05:52 -06:00
|
|
|
|
2025-08-21 13:08:22 +04:00
|
|
|
from open_webui.config import CACHE_DIR, BYPASS_ADMIN_ACCESS_CONTROL
|
2025-08-06 01:44:52 +04:00
|
|
|
from open_webui.constants import ERROR_MESSAGES
|
|
|
|
|
|
2025-02-25 15:36:25 +01:00
|
|
|
log = logging.getLogger(__name__)
|
2024-06-10 23:40:27 -07:00
|
|
|
|
2024-06-10 20:39:55 -07:00
|
|
|
|
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
2025-08-18 20:53:46 +04:00
|
|
|
|
2025-09-26 19:01:22 -05:00
|
|
|
def get_tool_module(request, tool_id, load_from_db=True):
|
|
|
|
|
"""
|
|
|
|
|
Get the tool module by its ID.
|
|
|
|
|
"""
|
|
|
|
|
tool_module, _ = get_tool_module_from_cache(request, tool_id, load_from_db)
|
|
|
|
|
return tool_module
|
|
|
|
|
|
|
|
|
|
|
2024-06-10 20:39:55 -07:00
|
|
|
############################
|
2024-11-16 17:09:15 -08:00
|
|
|
# GetTools
|
2024-06-10 20:39:55 -07:00
|
|
|
############################
|
|
|
|
|
|
|
|
|
|
|
2024-11-18 06:19:34 -08:00
|
|
|
@router.get("/", response_model=list[ToolUserResponse])
|
2026-01-08 00:42:29 +04:00
|
|
|
async def get_tools(
|
|
|
|
|
request: Request,
|
|
|
|
|
user=Depends(get_verified_user),
|
|
|
|
|
db: Session = Depends(get_session),
|
|
|
|
|
):
|
2025-09-26 19:01:22 -05:00
|
|
|
tools = []
|
|
|
|
|
|
|
|
|
|
# Local Tools
|
2026-02-21 16:27:25 -06:00
|
|
|
for tool in Tools.get_tools(defer_content=True, db=db):
|
2026-02-23 01:40:53 -06:00
|
|
|
tool_module = (
|
|
|
|
|
request.app.state.TOOLS.get(tool.id)
|
|
|
|
|
if hasattr(request.app.state, "TOOLS")
|
|
|
|
|
else None
|
|
|
|
|
)
|
2025-09-26 19:01:22 -05:00
|
|
|
tools.append(
|
|
|
|
|
ToolUserResponse(
|
|
|
|
|
**{
|
|
|
|
|
**tool.model_dump(),
|
2026-02-23 01:40:53 -06:00
|
|
|
"has_user_valves": (
|
|
|
|
|
hasattr(tool_module, "UserValves") if tool_module else False
|
|
|
|
|
),
|
2025-09-26 19:01:22 -05:00
|
|
|
}
|
|
|
|
|
)
|
2025-09-24 21:12:25 -05:00
|
|
|
)
|
2025-08-18 20:53:46 +04:00
|
|
|
|
2025-09-23 02:03:26 -04:00
|
|
|
# OpenAPI Tool Servers
|
2026-02-11 02:06:43 -06:00
|
|
|
server_access_grants = {}
|
2025-08-18 20:53:46 +04:00
|
|
|
for server in await get_tool_servers(request):
|
2026-02-11 02:06:43 -06:00
|
|
|
connection = request.app.state.config.TOOL_SERVER_CONNECTIONS[
|
|
|
|
|
server.get("idx", 0)
|
|
|
|
|
]
|
|
|
|
|
server_config = connection.get("config", {})
|
|
|
|
|
|
|
|
|
|
server_id = f"server:{server.get('id')}"
|
|
|
|
|
server_access_grants[server_id] = server_config.get("access_grants", [])
|
|
|
|
|
|
2025-04-05 04:40:01 -06:00
|
|
|
tools.append(
|
|
|
|
|
ToolUserResponse(
|
|
|
|
|
**{
|
2026-02-11 02:06:43 -06:00
|
|
|
"id": server_id,
|
|
|
|
|
"user_id": server_id,
|
2025-05-27 00:10:33 +04:00
|
|
|
"name": server.get("openapi", {})
|
2025-04-05 04:40:01 -06:00
|
|
|
.get("info", {})
|
|
|
|
|
.get("title", "Tool Server"),
|
|
|
|
|
"meta": {
|
2025-05-27 00:10:33 +04:00
|
|
|
"description": server.get("openapi", {})
|
2025-04-05 04:40:01 -06:00
|
|
|
.get("info", {})
|
|
|
|
|
.get("description", ""),
|
|
|
|
|
},
|
|
|
|
|
"updated_at": int(time.time()),
|
|
|
|
|
"created_at": int(time.time()),
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2025-09-23 02:03:26 -04:00
|
|
|
# MCP Tool Servers
|
|
|
|
|
for server in request.app.state.config.TOOL_SERVER_CONNECTIONS:
|
2026-02-16 00:43:32 -06:00
|
|
|
if server.get("type", "openapi") == "mcp" and server.get("config", {}).get(
|
|
|
|
|
"enable"
|
|
|
|
|
):
|
2025-09-25 01:49:16 -05:00
|
|
|
server_id = server.get("info", {}).get("id")
|
|
|
|
|
auth_type = server.get("auth_type", "none")
|
|
|
|
|
|
|
|
|
|
session_token = None
|
|
|
|
|
if auth_type == "oauth_2.1":
|
|
|
|
|
splits = server_id.split(":")
|
|
|
|
|
server_id = splits[-1] if len(splits) > 1 else server_id
|
|
|
|
|
|
|
|
|
|
session_token = (
|
|
|
|
|
await request.app.state.oauth_client_manager.get_oauth_token(
|
|
|
|
|
user.id, f"mcp:{server_id}"
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2026-02-11 02:06:43 -06:00
|
|
|
server_config = server.get("config", {})
|
|
|
|
|
|
|
|
|
|
tool_id = f"server:mcp:{server.get('info', {}).get('id')}"
|
|
|
|
|
server_access_grants[tool_id] = server_config.get("access_grants", [])
|
|
|
|
|
|
2025-09-23 02:03:26 -04:00
|
|
|
tools.append(
|
|
|
|
|
ToolUserResponse(
|
|
|
|
|
**{
|
2026-02-11 02:06:43 -06:00
|
|
|
"id": tool_id,
|
|
|
|
|
"user_id": tool_id,
|
2025-09-23 02:03:26 -04:00
|
|
|
"name": server.get("info", {}).get("name", "MCP Tool Server"),
|
|
|
|
|
"meta": {
|
|
|
|
|
"description": server.get("info", {}).get(
|
|
|
|
|
"description", ""
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
"updated_at": int(time.time()),
|
|
|
|
|
"created_at": int(time.time()),
|
2025-09-25 01:49:16 -05:00
|
|
|
**(
|
|
|
|
|
{
|
|
|
|
|
"authenticated": session_token is not None,
|
|
|
|
|
}
|
|
|
|
|
if auth_type == "oauth_2.1"
|
|
|
|
|
else {}
|
|
|
|
|
),
|
2025-09-23 02:03:26 -04:00
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
2025-08-21 13:08:22 +04:00
|
|
|
if user.role == "admin" and BYPASS_ADMIN_ACCESS_CONTROL:
|
2025-08-06 01:44:52 +04:00
|
|
|
# Admin can see all tools
|
|
|
|
|
return tools
|
|
|
|
|
else:
|
2026-01-08 00:42:29 +04:00
|
|
|
user_group_ids = {
|
|
|
|
|
group.id for group in Groups.get_groups_by_member_id(user.id, db=db)
|
|
|
|
|
}
|
2025-04-05 04:40:01 -06:00
|
|
|
tools = [
|
|
|
|
|
tool
|
|
|
|
|
for tool in tools
|
|
|
|
|
if tool.user_id == user.id
|
2026-02-09 13:28:14 -06:00
|
|
|
or (
|
|
|
|
|
has_access(
|
|
|
|
|
user.id,
|
|
|
|
|
"read",
|
2026-02-11 02:06:43 -06:00
|
|
|
server_access_grants.get(str(tool.id), []),
|
2026-02-09 13:28:14 -06:00
|
|
|
user_group_ids,
|
|
|
|
|
db=db,
|
|
|
|
|
)
|
|
|
|
|
if str(tool.id).startswith("server:")
|
|
|
|
|
else AccessGrants.has_access(
|
|
|
|
|
user_id=user.id,
|
|
|
|
|
resource_type="tool",
|
|
|
|
|
resource_id=tool.id,
|
|
|
|
|
permission="read",
|
|
|
|
|
user_group_ids=user_group_ids,
|
|
|
|
|
db=db,
|
|
|
|
|
)
|
|
|
|
|
)
|
2025-04-05 04:40:01 -06:00
|
|
|
]
|
2025-08-06 01:44:52 +04:00
|
|
|
return tools
|
2024-11-16 17:09:15 -08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
############################
|
|
|
|
|
# GetToolList
|
|
|
|
|
############################
|
|
|
|
|
|
|
|
|
|
|
2026-01-06 00:00:48 +01:00
|
|
|
@router.get("/list", response_model=list[ToolAccessResponse])
|
2026-01-08 00:42:29 +04:00
|
|
|
async def get_tool_list(
|
|
|
|
|
user=Depends(get_verified_user), db: Session = Depends(get_session)
|
|
|
|
|
):
|
2025-08-21 13:08:22 +04:00
|
|
|
if user.role == "admin" and BYPASS_ADMIN_ACCESS_CONTROL:
|
2026-02-21 16:27:25 -06:00
|
|
|
tools = Tools.get_tools(defer_content=True, db=db)
|
2024-11-16 17:09:15 -08:00
|
|
|
else:
|
2026-02-21 16:27:25 -06:00
|
|
|
tools = Tools.get_tools_by_user_id(user.id, "read", defer_content=True, db=db)
|
|
|
|
|
|
|
|
|
|
user_group_ids = {
|
|
|
|
|
group.id for group in Groups.get_groups_by_member_id(user.id, db=db)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result = []
|
|
|
|
|
for tool in tools:
|
|
|
|
|
has_write = (
|
|
|
|
|
(user.role == "admin" and BYPASS_ADMIN_ACCESS_CONTROL)
|
|
|
|
|
or user.id == tool.user_id
|
|
|
|
|
or any(
|
|
|
|
|
g.permission == "write"
|
|
|
|
|
and (
|
2026-02-23 01:40:53 -06:00
|
|
|
(
|
|
|
|
|
g.principal_type == "user"
|
|
|
|
|
and (g.principal_id == user.id or g.principal_id == "*")
|
|
|
|
|
)
|
|
|
|
|
or (
|
|
|
|
|
g.principal_type == "group" and g.principal_id in user_group_ids
|
|
|
|
|
)
|
2026-02-09 13:28:14 -06:00
|
|
|
)
|
2026-02-21 16:27:25 -06:00
|
|
|
for g in tool.access_grants
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
result.append(
|
|
|
|
|
ToolAccessResponse(
|
|
|
|
|
**tool.model_dump(),
|
|
|
|
|
write_access=has_write,
|
|
|
|
|
)
|
2026-01-06 00:00:48 +01:00
|
|
|
)
|
2026-02-21 16:27:25 -06:00
|
|
|
return result
|
2024-06-10 21:33:46 -07:00
|
|
|
|
|
|
|
|
|
2025-05-29 02:08:54 +04:00
|
|
|
############################
|
|
|
|
|
# LoadFunctionFromLink
|
|
|
|
|
############################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LoadUrlForm(BaseModel):
|
|
|
|
|
url: HttpUrl
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def github_url_to_raw_url(url: str) -> str:
|
|
|
|
|
# Handle 'tree' (folder) URLs (add main.py at the end)
|
|
|
|
|
m1 = re.match(r"https://github\.com/([^/]+)/([^/]+)/tree/([^/]+)/(.*)", url)
|
|
|
|
|
if m1:
|
|
|
|
|
org, repo, branch, path = m1.groups()
|
|
|
|
|
return f"https://raw.githubusercontent.com/{org}/{repo}/refs/heads/{branch}/{path.rstrip('/')}/main.py"
|
|
|
|
|
|
|
|
|
|
# Handle 'blob' (file) URLs
|
|
|
|
|
m2 = re.match(r"https://github\.com/([^/]+)/([^/]+)/blob/([^/]+)/(.*)", url)
|
|
|
|
|
if m2:
|
|
|
|
|
org, repo, branch, path = m2.groups()
|
|
|
|
|
return (
|
|
|
|
|
f"https://raw.githubusercontent.com/{org}/{repo}/refs/heads/{branch}/{path}"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# No match; return as-is
|
|
|
|
|
return url
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/load/url", response_model=Optional[dict])
|
|
|
|
|
async def load_tool_from_url(
|
|
|
|
|
request: Request, form_data: LoadUrlForm, user=Depends(get_admin_user)
|
|
|
|
|
):
|
|
|
|
|
# NOTE: This is NOT a SSRF vulnerability:
|
|
|
|
|
# This endpoint is admin-only (see get_admin_user), meant for *trusted* internal use,
|
|
|
|
|
# and does NOT accept untrusted user input. Access is enforced by authentication.
|
|
|
|
|
|
|
|
|
|
url = str(form_data.url)
|
|
|
|
|
if not url:
|
|
|
|
|
raise HTTPException(status_code=400, detail="Please enter a valid URL")
|
|
|
|
|
|
|
|
|
|
url = github_url_to_raw_url(url)
|
|
|
|
|
url_parts = url.rstrip("/").split("/")
|
|
|
|
|
|
|
|
|
|
file_name = url_parts[-1]
|
|
|
|
|
tool_name = (
|
|
|
|
|
file_name[:-3]
|
|
|
|
|
if (
|
|
|
|
|
file_name.endswith(".py")
|
|
|
|
|
and (not file_name.startswith(("main.py", "index.py", "__init__.py")))
|
|
|
|
|
)
|
|
|
|
|
else url_parts[-2] if len(url_parts) > 1 else "function"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
try:
|
2026-01-08 00:42:29 +04:00
|
|
|
async with aiohttp.ClientSession(
|
|
|
|
|
trust_env=True, timeout=aiohttp.ClientTimeout(total=AIOHTTP_CLIENT_TIMEOUT)
|
|
|
|
|
) as session:
|
2025-05-29 02:08:54 +04:00
|
|
|
async with session.get(
|
|
|
|
|
url, headers={"Content-Type": "application/json"}
|
|
|
|
|
) as resp:
|
|
|
|
|
if resp.status != 200:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=resp.status, detail="Failed to fetch the tool"
|
|
|
|
|
)
|
|
|
|
|
data = await resp.text()
|
|
|
|
|
if not data:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=400, detail="No data received from the URL"
|
|
|
|
|
)
|
|
|
|
|
return {
|
|
|
|
|
"name": tool_name,
|
|
|
|
|
"content": data,
|
|
|
|
|
}
|
|
|
|
|
except Exception as e:
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"Error importing tool: {e}")
|
|
|
|
|
|
|
|
|
|
|
2024-06-10 21:33:46 -07:00
|
|
|
############################
|
2024-11-16 17:09:15 -08:00
|
|
|
# ExportTools
|
2024-06-10 21:33:46 -07:00
|
|
|
############################
|
|
|
|
|
|
|
|
|
|
|
2024-08-14 13:46:31 +01:00
|
|
|
@router.get("/export", response_model=list[ToolModel])
|
2026-01-08 00:42:29 +04:00
|
|
|
async def export_tools(
|
|
|
|
|
request: Request,
|
|
|
|
|
user=Depends(get_verified_user),
|
|
|
|
|
db: Session = Depends(get_session),
|
|
|
|
|
):
|
2025-11-18 00:25:23 +01:00
|
|
|
if user.role != "admin" and not has_permission(
|
2026-01-08 00:42:29 +04:00
|
|
|
user.id,
|
|
|
|
|
"workspace.tools_export",
|
|
|
|
|
request.app.state.config.USER_PERMISSIONS,
|
|
|
|
|
db=db,
|
2025-11-18 00:25:23 +01:00
|
|
|
):
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
|
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if user.role == "admin" and BYPASS_ADMIN_ACCESS_CONTROL:
|
2025-12-29 00:21:18 +04:00
|
|
|
return Tools.get_tools(db=db)
|
2025-11-18 00:25:23 +01:00
|
|
|
else:
|
2025-12-29 00:21:18 +04:00
|
|
|
return Tools.get_tools_by_user_id(user.id, "read", db=db)
|
2024-06-10 20:39:55 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
############################
|
2024-11-16 17:09:15 -08:00
|
|
|
# CreateNewTools
|
2024-06-10 20:39:55 -07:00
|
|
|
############################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/create", response_model=Optional[ToolResponse])
|
2024-11-16 17:09:15 -08:00
|
|
|
async def create_new_tools(
|
2024-06-18 15:03:31 +02:00
|
|
|
request: Request,
|
|
|
|
|
form_data: ToolForm,
|
2024-11-16 17:09:15 -08:00
|
|
|
user=Depends(get_verified_user),
|
2025-12-29 00:21:18 +04:00
|
|
|
db: Session = Depends(get_session),
|
2024-06-10 22:38:48 -07:00
|
|
|
):
|
2025-11-18 00:25:23 +01:00
|
|
|
if user.role != "admin" and not (
|
|
|
|
|
has_permission(
|
2025-12-29 00:21:18 +04:00
|
|
|
user.id, "workspace.tools", request.app.state.config.USER_PERMISSIONS, db=db
|
2025-11-18 00:25:23 +01:00
|
|
|
)
|
|
|
|
|
or has_permission(
|
2026-01-08 00:42:29 +04:00
|
|
|
user.id,
|
|
|
|
|
"workspace.tools_import",
|
|
|
|
|
request.app.state.config.USER_PERMISSIONS,
|
|
|
|
|
db=db,
|
2025-11-18 00:25:23 +01:00
|
|
|
)
|
2024-11-17 03:04:31 -08:00
|
|
|
):
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
|
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
|
|
|
|
)
|
|
|
|
|
|
2024-06-10 21:59:06 -07:00
|
|
|
if not form_data.id.isidentifier():
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
|
|
|
detail="Only alphanumeric characters and underscores are allowed in the id",
|
|
|
|
|
)
|
|
|
|
|
|
2024-06-10 22:10:53 -07:00
|
|
|
form_data.id = form_data.id.lower()
|
|
|
|
|
|
2025-12-29 00:21:18 +04:00
|
|
|
tools = Tools.get_tool_by_id(form_data.id, db=db)
|
2024-11-16 17:54:38 -08:00
|
|
|
if tools is None:
|
2024-06-10 20:39:55 -07:00
|
|
|
try:
|
2024-09-04 19:57:41 +02:00
|
|
|
form_data.content = replace_imports(form_data.content)
|
2025-04-10 19:41:17 -07:00
|
|
|
tool_module, frontmatter = load_tool_module_by_id(
|
2024-09-04 19:55:20 +02:00
|
|
|
form_data.id, content=form_data.content
|
|
|
|
|
)
|
2024-06-23 20:31:40 -07:00
|
|
|
form_data.meta.manifest = frontmatter
|
2024-06-10 22:38:48 -07:00
|
|
|
|
|
|
|
|
TOOLS = request.app.state.TOOLS
|
2025-04-10 19:41:17 -07:00
|
|
|
TOOLS[form_data.id] = tool_module
|
2024-06-10 20:39:55 -07:00
|
|
|
|
2025-04-10 19:41:17 -07:00
|
|
|
specs = get_tool_specs(TOOLS[form_data.id])
|
2025-12-29 00:21:18 +04:00
|
|
|
tools = Tools.insert_new_tool(user.id, form_data, specs, db=db)
|
2024-06-10 20:39:55 -07:00
|
|
|
|
2025-03-04 19:53:52 +02:00
|
|
|
tool_cache_dir = CACHE_DIR / "tools" / form_data.id
|
2024-06-18 18:07:51 -07:00
|
|
|
tool_cache_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
2024-11-16 17:54:38 -08:00
|
|
|
if tools:
|
|
|
|
|
return tools
|
2024-06-10 20:39:55 -07:00
|
|
|
else:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
2024-11-16 17:54:38 -08:00
|
|
|
detail=ERROR_MESSAGES.DEFAULT("Error creating tools"),
|
2024-06-10 20:39:55 -07:00
|
|
|
)
|
|
|
|
|
except Exception as e:
|
2025-02-25 15:36:25 +01:00
|
|
|
log.exception(f"Failed to load the tool by id {form_data.id}: {e}")
|
2024-06-10 20:39:55 -07:00
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
2024-08-03 14:24:26 +01:00
|
|
|
detail=ERROR_MESSAGES.DEFAULT(str(e)),
|
2024-06-10 20:39:55 -07:00
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
2024-06-10 21:33:46 -07:00
|
|
|
detail=ERROR_MESSAGES.ID_TAKEN,
|
2024-06-10 20:39:55 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
############################
|
2024-11-16 17:09:15 -08:00
|
|
|
# GetToolsById
|
2024-06-10 20:39:55 -07:00
|
|
|
############################
|
|
|
|
|
|
|
|
|
|
|
2026-01-06 00:00:48 +01:00
|
|
|
@router.get("/id/{id}", response_model=Optional[ToolAccessResponse])
|
2026-01-08 00:42:29 +04:00
|
|
|
async def get_tools_by_id(
|
|
|
|
|
id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)
|
|
|
|
|
):
|
2025-12-29 00:21:18 +04:00
|
|
|
tools = Tools.get_tool_by_id(id, db=db)
|
2024-06-10 20:39:55 -07:00
|
|
|
|
2024-11-16 17:54:38 -08:00
|
|
|
if tools:
|
2024-11-16 17:57:19 -08:00
|
|
|
if (
|
|
|
|
|
user.role == "admin"
|
|
|
|
|
or tools.user_id == user.id
|
2026-02-09 13:28:14 -06:00
|
|
|
or AccessGrants.has_access(
|
|
|
|
|
user_id=user.id,
|
|
|
|
|
resource_type="tool",
|
|
|
|
|
resource_id=tools.id,
|
|
|
|
|
permission="read",
|
|
|
|
|
db=db,
|
|
|
|
|
)
|
2024-11-16 17:57:19 -08:00
|
|
|
):
|
2026-01-06 00:00:48 +01:00
|
|
|
return ToolAccessResponse(
|
|
|
|
|
**tools.model_dump(),
|
|
|
|
|
write_access=(
|
|
|
|
|
(user.role == "admin" and BYPASS_ADMIN_ACCESS_CONTROL)
|
|
|
|
|
or user.id == tools.user_id
|
2026-02-09 13:28:14 -06:00
|
|
|
or AccessGrants.has_access(
|
|
|
|
|
user_id=user.id,
|
|
|
|
|
resource_type="tool",
|
|
|
|
|
resource_id=tools.id,
|
|
|
|
|
permission="write",
|
|
|
|
|
db=db,
|
|
|
|
|
)
|
2026-01-06 00:00:48 +01:00
|
|
|
),
|
|
|
|
|
)
|
2025-12-31 08:28:59 +01:00
|
|
|
else:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
|
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
|
|
|
|
)
|
2024-06-10 20:39:55 -07:00
|
|
|
else:
|
|
|
|
|
raise HTTPException(
|
2025-12-31 08:28:59 +01:00
|
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
2024-06-10 20:39:55 -07:00
|
|
|
detail=ERROR_MESSAGES.NOT_FOUND,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2024-06-23 20:31:40 -07:00
|
|
|
############################
|
2024-11-16 17:09:15 -08:00
|
|
|
# UpdateToolsById
|
2024-06-23 20:31:40 -07:00
|
|
|
############################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/id/{id}/update", response_model=Optional[ToolModel])
|
2024-11-16 17:09:15 -08:00
|
|
|
async def update_tools_by_id(
|
2024-06-18 15:03:31 +02:00
|
|
|
request: Request,
|
|
|
|
|
id: str,
|
|
|
|
|
form_data: ToolForm,
|
2024-11-16 17:09:15 -08:00
|
|
|
user=Depends(get_verified_user),
|
2025-12-29 00:21:18 +04:00
|
|
|
db: Session = Depends(get_session),
|
2024-06-23 20:31:40 -07:00
|
|
|
):
|
2025-12-29 00:21:18 +04:00
|
|
|
tools = Tools.get_tool_by_id(id, db=db)
|
2024-11-16 17:57:19 -08:00
|
|
|
if not tools:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
|
detail=ERROR_MESSAGES.NOT_FOUND,
|
|
|
|
|
)
|
|
|
|
|
|
2025-01-10 18:44:26 +00:00
|
|
|
# Is the user the original creator, in a group with write access, or an admin
|
2025-01-19 11:59:07 -08:00
|
|
|
if (
|
|
|
|
|
tools.user_id != user.id
|
2026-02-09 13:28:14 -06:00
|
|
|
and not AccessGrants.has_access(
|
|
|
|
|
user_id=user.id,
|
|
|
|
|
resource_type="tool",
|
|
|
|
|
resource_id=tools.id,
|
|
|
|
|
permission="write",
|
|
|
|
|
db=db,
|
|
|
|
|
)
|
2025-01-19 11:59:07 -08:00
|
|
|
and user.role != "admin"
|
|
|
|
|
):
|
2024-11-16 17:57:19 -08:00
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
|
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
|
|
|
|
)
|
|
|
|
|
|
2024-06-23 20:31:40 -07:00
|
|
|
try:
|
2024-09-04 19:57:41 +02:00
|
|
|
form_data.content = replace_imports(form_data.content)
|
2025-04-10 19:41:17 -07:00
|
|
|
tool_module, frontmatter = load_tool_module_by_id(id, content=form_data.content)
|
2024-06-23 20:31:40 -07:00
|
|
|
form_data.meta.manifest = frontmatter
|
|
|
|
|
|
|
|
|
|
TOOLS = request.app.state.TOOLS
|
2025-04-10 19:41:17 -07:00
|
|
|
TOOLS[id] = tool_module
|
2024-06-23 20:31:40 -07:00
|
|
|
|
2025-04-10 19:41:17 -07:00
|
|
|
specs = get_tool_specs(TOOLS[id])
|
2024-06-23 20:31:40 -07:00
|
|
|
|
|
|
|
|
updated = {
|
|
|
|
|
**form_data.model_dump(exclude={"id"}),
|
|
|
|
|
"specs": specs,
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-25 15:36:25 +01:00
|
|
|
log.debug(updated)
|
2025-12-29 00:21:18 +04:00
|
|
|
tools = Tools.update_tool_by_id(id, updated, db=db)
|
2024-06-23 20:31:40 -07:00
|
|
|
|
2024-11-16 17:54:38 -08:00
|
|
|
if tools:
|
|
|
|
|
return tools
|
2024-06-23 20:31:40 -07:00
|
|
|
else:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
2024-11-16 17:54:38 -08:00
|
|
|
detail=ERROR_MESSAGES.DEFAULT("Error updating tools"),
|
2024-06-23 20:31:40 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
2024-08-03 14:24:26 +01:00
|
|
|
detail=ERROR_MESSAGES.DEFAULT(str(e)),
|
2024-06-23 20:31:40 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2026-02-10 15:41:11 -06:00
|
|
|
############################
|
|
|
|
|
# UpdateToolAccessById
|
|
|
|
|
############################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ToolAccessGrantsForm(BaseModel):
|
|
|
|
|
access_grants: list[dict]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/id/{id}/access/update", response_model=Optional[ToolModel])
|
|
|
|
|
async def update_tool_access_by_id(
|
fix: enforce public sharing permission checks across all resource types (#21358)
The sharePublic prop in editor components (Knowledge, Tools, Skills,
Prompts, Models) incorrectly included an "|| edit" / "|| write_access"
condition, allowing users with write access to see and use the "Public"
sharing option regardless of their actual public sharing permission.
Additionally, all backend access/update endpoints only verified write
authorization but did not check the corresponding sharing.public_*
permission, allowing direct API calls to bypass frontend restrictions
entirely.
Frontend: removed the edit/write_access bypass from sharePublic in all
five editor components so visibility is gated solely by the user's
sharing.public_* permission or admin role.
Backend: added has_public_read_access_grant checks to the access/update
endpoints in knowledge.py, tools.py, prompts.py, skills.py, models.py,
and notes.py. Public grants are silently stripped when the user lacks
the corresponding permission.
Fixes #21356
2026-02-13 18:22:32 +01:00
|
|
|
request: Request,
|
2026-02-10 15:41:11 -06:00
|
|
|
id: str,
|
|
|
|
|
form_data: ToolAccessGrantsForm,
|
|
|
|
|
user=Depends(get_verified_user),
|
|
|
|
|
db: Session = Depends(get_session),
|
|
|
|
|
):
|
|
|
|
|
tools = Tools.get_tool_by_id(id, db=db)
|
|
|
|
|
if not tools:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
|
|
|
detail=ERROR_MESSAGES.NOT_FOUND,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
tools.user_id != user.id
|
|
|
|
|
and not AccessGrants.has_access(
|
|
|
|
|
user_id=user.id,
|
|
|
|
|
resource_type="tool",
|
|
|
|
|
resource_id=tools.id,
|
|
|
|
|
permission="write",
|
|
|
|
|
db=db,
|
|
|
|
|
)
|
|
|
|
|
and user.role != "admin"
|
|
|
|
|
):
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
|
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
|
|
|
|
)
|
|
|
|
|
|
2026-02-23 16:01:03 -06:00
|
|
|
form_data.access_grants = filter_allowed_access_grants(
|
|
|
|
|
request.app.state.config.USER_PERMISSIONS,
|
|
|
|
|
user.id,
|
|
|
|
|
user.role,
|
|
|
|
|
form_data.access_grants,
|
|
|
|
|
"sharing.public_tools"
|
|
|
|
|
)
|
2026-02-23 15:49:05 -06:00
|
|
|
|
2026-02-11 16:24:11 -06:00
|
|
|
AccessGrants.set_access_grants("tool", id, form_data.access_grants, db=db)
|
2026-02-10 15:41:11 -06:00
|
|
|
|
|
|
|
|
return Tools.get_tool_by_id(id, db=db)
|
|
|
|
|
|
|
|
|
|
|
2024-06-23 20:31:40 -07:00
|
|
|
############################
|
2024-11-16 17:09:15 -08:00
|
|
|
# DeleteToolsById
|
2024-06-23 20:31:40 -07:00
|
|
|
############################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/id/{id}/delete", response_model=bool)
|
2024-11-16 17:09:15 -08:00
|
|
|
async def delete_tools_by_id(
|
2026-01-08 00:42:29 +04:00
|
|
|
request: Request,
|
|
|
|
|
id: str,
|
|
|
|
|
user=Depends(get_verified_user),
|
|
|
|
|
db: Session = Depends(get_session),
|
2024-11-16 17:09:15 -08:00
|
|
|
):
|
2025-12-29 00:21:18 +04:00
|
|
|
tools = Tools.get_tool_by_id(id, db=db)
|
2024-11-16 17:57:19 -08:00
|
|
|
if not tools:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
|
detail=ERROR_MESSAGES.NOT_FOUND,
|
|
|
|
|
)
|
2024-06-23 20:31:40 -07:00
|
|
|
|
2025-01-27 18:11:52 +00:00
|
|
|
if (
|
|
|
|
|
tools.user_id != user.id
|
2026-02-09 13:28:14 -06:00
|
|
|
and not AccessGrants.has_access(
|
|
|
|
|
user_id=user.id,
|
|
|
|
|
resource_type="tool",
|
|
|
|
|
resource_id=tools.id,
|
|
|
|
|
permission="write",
|
|
|
|
|
db=db,
|
|
|
|
|
)
|
2025-01-27 18:11:52 +00:00
|
|
|
and user.role != "admin"
|
|
|
|
|
):
|
2024-11-16 17:57:19 -08:00
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
|
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
|
|
|
|
)
|
|
|
|
|
|
2025-12-29 00:21:18 +04:00
|
|
|
result = Tools.delete_tool_by_id(id, db=db)
|
2024-06-23 20:31:40 -07:00
|
|
|
if result:
|
|
|
|
|
TOOLS = request.app.state.TOOLS
|
|
|
|
|
if id in TOOLS:
|
|
|
|
|
del TOOLS[id]
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
2024-06-23 18:34:42 -07:00
|
|
|
############################
|
|
|
|
|
# GetToolValves
|
|
|
|
|
############################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/id/{id}/valves", response_model=Optional[dict])
|
2026-01-08 00:42:29 +04:00
|
|
|
async def get_tools_valves_by_id(
|
|
|
|
|
id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)
|
|
|
|
|
):
|
2025-12-29 00:21:18 +04:00
|
|
|
tools = Tools.get_tool_by_id(id, db=db)
|
2024-11-16 17:54:38 -08:00
|
|
|
if tools:
|
2024-06-23 18:34:42 -07:00
|
|
|
try:
|
2025-12-29 00:21:18 +04:00
|
|
|
valves = Tools.get_tool_valves_by_id(id, db=db)
|
2024-06-23 19:18:13 -07:00
|
|
|
return valves
|
2024-06-23 18:34:42 -07:00
|
|
|
except Exception as e:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
2024-08-03 14:24:26 +01:00
|
|
|
detail=ERROR_MESSAGES.DEFAULT(str(e)),
|
2024-06-23 18:34:42 -07:00
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
|
detail=ERROR_MESSAGES.NOT_FOUND,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2024-06-23 19:02:27 -07:00
|
|
|
############################
|
|
|
|
|
# GetToolValvesSpec
|
|
|
|
|
############################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/id/{id}/valves/spec", response_model=Optional[dict])
|
2024-11-16 17:09:15 -08:00
|
|
|
async def get_tools_valves_spec_by_id(
|
2026-01-08 00:42:29 +04:00
|
|
|
request: Request,
|
|
|
|
|
id: str,
|
|
|
|
|
user=Depends(get_verified_user),
|
|
|
|
|
db: Session = Depends(get_session),
|
2024-06-23 19:02:27 -07:00
|
|
|
):
|
2025-12-29 00:21:18 +04:00
|
|
|
tools = Tools.get_tool_by_id(id, db=db)
|
2024-11-16 17:54:38 -08:00
|
|
|
if tools:
|
2024-06-23 19:02:27 -07:00
|
|
|
if id in request.app.state.TOOLS:
|
2024-11-16 17:54:38 -08:00
|
|
|
tools_module = request.app.state.TOOLS[id]
|
2024-06-23 19:02:27 -07:00
|
|
|
else:
|
2025-04-10 19:41:17 -07:00
|
|
|
tools_module, _ = load_tool_module_by_id(id)
|
2024-11-16 17:54:38 -08:00
|
|
|
request.app.state.TOOLS[id] = tools_module
|
2024-06-23 19:02:27 -07:00
|
|
|
|
2024-11-16 17:54:38 -08:00
|
|
|
if hasattr(tools_module, "Valves"):
|
|
|
|
|
Valves = tools_module.Valves
|
2026-01-22 03:55:07 +04:00
|
|
|
schema = Valves.schema()
|
|
|
|
|
# Resolve dynamic options for select dropdowns
|
|
|
|
|
schema = resolve_valves_schema_options(Valves, schema, user)
|
|
|
|
|
return schema
|
2024-06-23 19:02:27 -07:00
|
|
|
return None
|
|
|
|
|
else:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
|
detail=ERROR_MESSAGES.NOT_FOUND,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2024-06-23 18:34:42 -07:00
|
|
|
############################
|
|
|
|
|
# UpdateToolValves
|
|
|
|
|
############################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/id/{id}/valves/update", response_model=Optional[dict])
|
2024-11-16 17:09:15 -08:00
|
|
|
async def update_tools_valves_by_id(
|
2026-01-08 00:42:29 +04:00
|
|
|
request: Request,
|
|
|
|
|
id: str,
|
|
|
|
|
form_data: dict,
|
|
|
|
|
user=Depends(get_verified_user),
|
|
|
|
|
db: Session = Depends(get_session),
|
2024-06-23 18:34:42 -07:00
|
|
|
):
|
2025-12-29 00:21:18 +04:00
|
|
|
tools = Tools.get_tool_by_id(id, db=db)
|
2024-11-21 17:19:56 +00:00
|
|
|
if not tools:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
|
detail=ERROR_MESSAGES.NOT_FOUND,
|
|
|
|
|
)
|
2025-01-23 10:37:44 -08:00
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
tools.user_id != user.id
|
2026-02-09 13:28:14 -06:00
|
|
|
and not AccessGrants.has_access(
|
|
|
|
|
user_id=user.id,
|
|
|
|
|
resource_type="tool",
|
|
|
|
|
resource_id=tools.id,
|
|
|
|
|
permission="write",
|
|
|
|
|
db=db,
|
|
|
|
|
)
|
2025-01-23 10:37:44 -08:00
|
|
|
and user.role != "admin"
|
|
|
|
|
):
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
|
|
|
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
|
|
|
|
)
|
|
|
|
|
|
2024-11-21 17:19:56 +00:00
|
|
|
if id in request.app.state.TOOLS:
|
|
|
|
|
tools_module = request.app.state.TOOLS[id]
|
2024-06-23 18:34:42 -07:00
|
|
|
else:
|
2025-04-10 19:41:17 -07:00
|
|
|
tools_module, _ = load_tool_module_by_id(id)
|
2024-11-21 17:19:56 +00:00
|
|
|
request.app.state.TOOLS[id] = tools_module
|
|
|
|
|
|
|
|
|
|
if not hasattr(tools_module, "Valves"):
|
2024-06-23 18:34:42 -07:00
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
|
detail=ERROR_MESSAGES.NOT_FOUND,
|
|
|
|
|
)
|
2024-11-21 17:19:56 +00:00
|
|
|
Valves = tools_module.Valves
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
form_data = {k: v for k, v in form_data.items() if v is not None}
|
|
|
|
|
valves = Valves(**form_data)
|
2025-09-24 09:21:53 -05:00
|
|
|
valves_dict = valves.model_dump(exclude_unset=True)
|
2025-12-29 00:21:18 +04:00
|
|
|
Tools.update_tool_valves_by_id(id, valves_dict, db=db)
|
2025-09-24 09:21:53 -05:00
|
|
|
return valves_dict
|
2024-11-21 17:19:56 +00:00
|
|
|
except Exception as e:
|
2025-02-25 15:36:25 +01:00
|
|
|
log.exception(f"Failed to update tool valves by id {id}: {e}")
|
2024-11-21 17:19:56 +00:00
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
|
|
|
detail=ERROR_MESSAGES.DEFAULT(str(e)),
|
|
|
|
|
)
|
2024-06-23 18:34:42 -07:00
|
|
|
|
|
|
|
|
|
2024-06-22 11:26:33 -07:00
|
|
|
############################
|
|
|
|
|
# ToolUserValves
|
|
|
|
|
############################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/id/{id}/valves/user", response_model=Optional[dict])
|
2026-01-08 00:42:29 +04:00
|
|
|
async def get_tools_user_valves_by_id(
|
|
|
|
|
id: str, user=Depends(get_verified_user), db: Session = Depends(get_session)
|
|
|
|
|
):
|
2025-12-29 00:21:18 +04:00
|
|
|
tools = Tools.get_tool_by_id(id, db=db)
|
2024-11-16 17:54:38 -08:00
|
|
|
if tools:
|
2024-06-22 11:26:33 -07:00
|
|
|
try:
|
2025-12-29 00:21:18 +04:00
|
|
|
user_valves = Tools.get_user_valves_by_id_and_user_id(id, user.id, db=db)
|
2024-06-22 11:26:33 -07:00
|
|
|
return user_valves
|
|
|
|
|
except Exception as e:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
2024-08-03 14:24:26 +01:00
|
|
|
detail=ERROR_MESSAGES.DEFAULT(str(e)),
|
2024-06-22 11:26:33 -07:00
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
|
detail=ERROR_MESSAGES.NOT_FOUND,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/id/{id}/valves/user/spec", response_model=Optional[dict])
|
2024-11-16 17:09:15 -08:00
|
|
|
async def get_tools_user_valves_spec_by_id(
|
2026-01-08 00:42:29 +04:00
|
|
|
request: Request,
|
|
|
|
|
id: str,
|
|
|
|
|
user=Depends(get_verified_user),
|
|
|
|
|
db: Session = Depends(get_session),
|
2024-06-22 11:26:33 -07:00
|
|
|
):
|
2025-12-29 00:21:18 +04:00
|
|
|
tools = Tools.get_tool_by_id(id, db=db)
|
2024-11-16 17:54:38 -08:00
|
|
|
if tools:
|
2024-06-22 11:26:33 -07:00
|
|
|
if id in request.app.state.TOOLS:
|
2024-11-16 17:54:38 -08:00
|
|
|
tools_module = request.app.state.TOOLS[id]
|
2024-06-22 11:26:33 -07:00
|
|
|
else:
|
2025-04-10 19:41:17 -07:00
|
|
|
tools_module, _ = load_tool_module_by_id(id)
|
2024-11-16 17:54:38 -08:00
|
|
|
request.app.state.TOOLS[id] = tools_module
|
2024-06-22 11:26:33 -07:00
|
|
|
|
2024-11-16 17:54:38 -08:00
|
|
|
if hasattr(tools_module, "UserValves"):
|
|
|
|
|
UserValves = tools_module.UserValves
|
2026-01-22 03:55:07 +04:00
|
|
|
schema = UserValves.schema()
|
|
|
|
|
# Resolve dynamic options for select dropdowns
|
|
|
|
|
schema = resolve_valves_schema_options(UserValves, schema, user)
|
|
|
|
|
return schema
|
2024-06-22 11:26:33 -07:00
|
|
|
return None
|
|
|
|
|
else:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
|
detail=ERROR_MESSAGES.NOT_FOUND,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/id/{id}/valves/user/update", response_model=Optional[dict])
|
2024-11-16 17:09:15 -08:00
|
|
|
async def update_tools_user_valves_by_id(
|
2026-01-08 00:42:29 +04:00
|
|
|
request: Request,
|
|
|
|
|
id: str,
|
|
|
|
|
form_data: dict,
|
|
|
|
|
user=Depends(get_verified_user),
|
|
|
|
|
db: Session = Depends(get_session),
|
2024-06-22 11:26:33 -07:00
|
|
|
):
|
2025-12-29 00:21:18 +04:00
|
|
|
tools = Tools.get_tool_by_id(id, db=db)
|
2024-06-22 11:26:33 -07:00
|
|
|
|
2024-11-16 17:54:38 -08:00
|
|
|
if tools:
|
2024-06-22 11:26:33 -07:00
|
|
|
if id in request.app.state.TOOLS:
|
2024-11-16 17:54:38 -08:00
|
|
|
tools_module = request.app.state.TOOLS[id]
|
2024-06-22 11:26:33 -07:00
|
|
|
else:
|
2025-04-10 19:41:17 -07:00
|
|
|
tools_module, _ = load_tool_module_by_id(id)
|
2024-11-16 17:54:38 -08:00
|
|
|
request.app.state.TOOLS[id] = tools_module
|
2024-06-22 11:26:33 -07:00
|
|
|
|
2024-11-16 17:54:38 -08:00
|
|
|
if hasattr(tools_module, "UserValves"):
|
|
|
|
|
UserValves = tools_module.UserValves
|
2024-06-22 11:26:33 -07:00
|
|
|
|
|
|
|
|
try:
|
2024-06-23 19:05:56 -07:00
|
|
|
form_data = {k: v for k, v in form_data.items() if v is not None}
|
2024-06-22 11:26:33 -07:00
|
|
|
user_valves = UserValves(**form_data)
|
2025-09-24 09:21:53 -05:00
|
|
|
user_valves_dict = user_valves.model_dump(exclude_unset=True)
|
2024-06-22 11:26:33 -07:00
|
|
|
Tools.update_user_valves_by_id_and_user_id(
|
2025-12-29 00:21:18 +04:00
|
|
|
id, user.id, user_valves_dict, db=db
|
2024-06-22 11:26:33 -07:00
|
|
|
)
|
2025-09-24 09:21:53 -05:00
|
|
|
return user_valves_dict
|
2024-06-22 11:26:33 -07:00
|
|
|
except Exception as e:
|
2025-02-25 15:36:25 +01:00
|
|
|
log.exception(f"Failed to update user valves by id {id}: {e}")
|
2024-06-22 11:26:33 -07:00
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
2024-08-03 14:24:26 +01:00
|
|
|
detail=ERROR_MESSAGES.DEFAULT(str(e)),
|
2024-06-22 11:26:33 -07:00
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
|
detail=ERROR_MESSAGES.NOT_FOUND,
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
raise HTTPException(
|
|
|
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
|
detail=ERROR_MESSAGES.NOT_FOUND,
|
|
|
|
|
)
|