mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-06-26 21:39:43 +02:00
chore: fix linting
This commit is contained in:
parent
7d4c994900
commit
d6bffa6f07
61 changed files with 167 additions and 218 deletions
|
|
@ -77,7 +77,5 @@ def upgrade() -> None:
|
|||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute(
|
||||
"ALTER TABLE searchspaces DROP COLUMN IF EXISTS api_access_enabled"
|
||||
)
|
||||
op.execute("ALTER TABLE searchspaces DROP COLUMN IF EXISTS api_access_enabled")
|
||||
op.execute("DROP TABLE IF EXISTS personal_access_tokens")
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ Revises: 168
|
|||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
from alembic import op
|
||||
|
||||
revision: str = "169"
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ def create_create_automation_tool(
|
|||
``AsyncSession`` is opened per call to avoid stale sessions on
|
||||
compiled-agent cache hits (same pattern as the Notion / memory tools).
|
||||
"""
|
||||
|
||||
@tool
|
||||
async def create_automation(intent: str, runtime: ToolRuntime) -> dict[str, Any]:
|
||||
"""Draft + save an automation from a natural-language intent.
|
||||
|
|
|
|||
|
|
@ -242,6 +242,7 @@ def create_generate_image_tool(
|
|||
|
||||
# Update all image URLs in response_dict to be absolute (for the serving endpoint)
|
||||
from urllib.parse import urlparse
|
||||
|
||||
for image in images:
|
||||
if image.get("url"):
|
||||
raw_url: str = image["url"]
|
||||
|
|
|
|||
|
|
@ -949,9 +949,7 @@ if config.AUTH_TYPE == "GOOGLE":
|
|||
)
|
||||
async def google_authorize(request: Request, response: Response):
|
||||
"""Return Google's authorization URL, matching fastapi-users' shape."""
|
||||
return {
|
||||
"authorization_url": await _google_authorization_url(request, response)
|
||||
}
|
||||
return {"authorization_url": await _google_authorization_url(request, response)}
|
||||
|
||||
@app.get(
|
||||
"/auth/google/callback",
|
||||
|
|
@ -1015,7 +1013,9 @@ if config.AUTH_TYPE == "GOOGLE":
|
|||
data=token_payload,
|
||||
)
|
||||
if token_response.status_code >= 400:
|
||||
_error_logger.warning("Web Google OAuth exchange failed: %s", token_response.text)
|
||||
_error_logger.warning(
|
||||
"Web Google OAuth exchange failed: %s", token_response.text
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="OAuth exchange failed",
|
||||
|
|
|
|||
|
|
@ -43,7 +43,10 @@ class CsrfOriginMiddleware(BaseHTTPMiddleware):
|
|||
|
||||
# PAT/Bearer credentials are not ambient browser credentials and are not
|
||||
# CSRF-able. Enforce only when the web session cookie is the credential.
|
||||
if request.headers.get("Authorization") or config.SESSION_COOKIE_NAME not in request.cookies:
|
||||
if (
|
||||
request.headers.get("Authorization")
|
||||
or config.SESSION_COOKIE_NAME not in request.cookies
|
||||
):
|
||||
return await call_next(request)
|
||||
|
||||
origin = request.headers.get("Origin") or _origin_from_url(
|
||||
|
|
|
|||
|
|
@ -88,7 +88,9 @@ def clear_session(response: Response, request: Request | None = None) -> None:
|
|||
)
|
||||
|
||||
|
||||
def read_refresh(request: Request, body: Any | None = None) -> tuple[str | None, TransportMode]:
|
||||
def read_refresh(
|
||||
request: Request, body: Any | None = None
|
||||
) -> tuple[str | None, TransportMode]:
|
||||
cookie = request.cookies.get(config.REFRESH_COOKIE_NAME)
|
||||
if cookie:
|
||||
return cookie, TransportMode.COOKIE
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from sqlalchemy import func, select
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
from app.automations.persistence.enums.trigger_type import TriggerType
|
||||
from app.automations.persistence.models.automation import Automation
|
||||
from app.automations.persistence.models.trigger import AutomationTrigger
|
||||
|
|
@ -27,7 +28,6 @@ from app.automations.services.model_policy import (
|
|||
)
|
||||
from app.automations.triggers import get_trigger
|
||||
from app.automations.triggers.builtin.schedule import compute_next_fire_at
|
||||
from app.auth.context import AuthContext
|
||||
from app.db import Permission, SearchSpace, get_async_session
|
||||
from app.users import get_auth_context
|
||||
from app.utils.rbac import check_permission
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ from fastapi import Depends, HTTPException
|
|||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
from app.automations.persistence.models.automation import Automation
|
||||
from app.automations.persistence.models.run import AutomationRun
|
||||
from app.auth.context import AuthContext
|
||||
from app.db import Permission, get_async_session
|
||||
from app.users import get_auth_context
|
||||
from app.utils.rbac import check_permission
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ from fastapi import Depends, HTTPException
|
|||
from pydantic import ValidationError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
from app.automations.persistence.enums.trigger_type import TriggerType
|
||||
from app.automations.persistence.models.automation import Automation
|
||||
from app.automations.persistence.models.trigger import AutomationTrigger
|
||||
from app.automations.schemas.api import TriggerCreate, TriggerUpdate
|
||||
from app.automations.triggers import get_trigger
|
||||
from app.automations.triggers.builtin.schedule import compute_next_fire_at
|
||||
from app.auth.context import AuthContext
|
||||
from app.db import Permission, get_async_session
|
||||
from app.users import get_auth_context
|
||||
from app.utils.rbac import check_permission
|
||||
|
|
|
|||
|
|
@ -948,9 +948,7 @@ class Config:
|
|||
if origin.strip()
|
||||
]
|
||||
_PAT_MAX_EXPIRY_DAYS = os.getenv("PAT_MAX_EXPIRY_DAYS", "").strip()
|
||||
PAT_MAX_EXPIRY_DAYS = (
|
||||
int(_PAT_MAX_EXPIRY_DAYS) if _PAT_MAX_EXPIRY_DAYS else None
|
||||
)
|
||||
PAT_MAX_EXPIRY_DAYS = int(_PAT_MAX_EXPIRY_DAYS) if _PAT_MAX_EXPIRY_DAYS else None
|
||||
|
||||
# ETL Service
|
||||
ETL_SERVICE = os.getenv("ETL_SERVICE")
|
||||
|
|
|
|||
|
|
@ -28,13 +28,12 @@ from pydantic import BaseModel
|
|||
from sqlalchemy import func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
from app.agents.chat.multi_agent_chat.shared.feature_flags import get_flags
|
||||
from app.auth.context import AuthContext
|
||||
from app.db import (
|
||||
AgentActionLog,
|
||||
NewChatThread,
|
||||
Permission,
|
||||
User,
|
||||
get_async_session,
|
||||
)
|
||||
from app.users import get_auth_context
|
||||
|
|
@ -114,7 +113,6 @@ async def list_thread_actions(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
) -> AgentActionListResponse:
|
||||
user = auth.user
|
||||
"""List agent actions for a thread, newest first.
|
||||
|
||||
Authorization:
|
||||
|
|
|
|||
|
|
@ -30,14 +30,13 @@ from sqlalchemy import select
|
|||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
from app.agents.chat.multi_agent_chat.shared.feature_flags import get_flags
|
||||
from app.auth.context import AuthContext
|
||||
from app.db import (
|
||||
AgentPermissionRule,
|
||||
NewChatThread,
|
||||
Permission,
|
||||
SearchSpace,
|
||||
User,
|
||||
get_async_session,
|
||||
)
|
||||
from app.users import get_auth_context
|
||||
|
|
@ -136,7 +135,6 @@ def _to_read(row: AgentPermissionRule) -> AgentPermissionRuleRead:
|
|||
async def _ensure_search_space_membership_admin(
|
||||
session: AsyncSession, auth: AuthContext, search_space_id: int
|
||||
) -> None:
|
||||
user = auth.user
|
||||
"""Curating agent rules == "settings" administration on the space."""
|
||||
space = await session.get(SearchSpace, search_space_id)
|
||||
if space is None:
|
||||
|
|
|
|||
|
|
@ -75,7 +75,10 @@ async def resolve_google_user(
|
|||
account id regardless of the current email claim.
|
||||
"""
|
||||
if not claims.get("sub") or not claims.get("email"):
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid Google identity token")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid Google identity token",
|
||||
)
|
||||
|
||||
sub = claims["sub"]
|
||||
email_verified = bool(claims.get("email_verified"))
|
||||
|
|
@ -202,7 +205,9 @@ async def logout_all_devices(
|
|||
"""
|
||||
user: User | None = None
|
||||
try:
|
||||
auth = await get_auth_context(request, session=session, user_manager=user_manager)
|
||||
auth = await get_auth_context(
|
||||
request, session=session, user_manager=user_manager
|
||||
)
|
||||
if auth.is_session:
|
||||
user = auth.user
|
||||
except HTTPException:
|
||||
|
|
@ -210,7 +215,9 @@ async def logout_all_devices(
|
|||
|
||||
if user is None:
|
||||
refresh_token, _mode = read_refresh(request, body)
|
||||
token_record = await validate_refresh_token(refresh_token) if refresh_token else None
|
||||
token_record = (
|
||||
await validate_refresh_token(refresh_token) if refresh_token else None
|
||||
)
|
||||
if token_record:
|
||||
user = await _load_user(token_record.user_id)
|
||||
|
||||
|
|
@ -243,7 +250,9 @@ async def get_session(
|
|||
access_token = token
|
||||
|
||||
if access_token is None:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Unauthorized")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="Unauthorized"
|
||||
)
|
||||
return SessionResponse(access_expires_at=access_expires_at(access_token))
|
||||
|
||||
|
||||
|
|
@ -298,7 +307,9 @@ async def create_desktop_session(
|
|||
and redirect_port is not None
|
||||
and parsed_redirect.path == "/callback"
|
||||
):
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid redirect URI")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid redirect URI"
|
||||
)
|
||||
if not config.GOOGLE_DESKTOP_CLIENT_ID:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
|
|
@ -316,15 +327,21 @@ async def create_desktop_session(
|
|||
token_payload["client_secret"] = config.GOOGLE_DESKTOP_CLIENT_SECRET
|
||||
|
||||
async with httpx.AsyncClient(timeout=10) as client:
|
||||
token_response = await client.post("https://oauth2.googleapis.com/token", data=token_payload)
|
||||
token_response = await client.post(
|
||||
"https://oauth2.googleapis.com/token", data=token_payload
|
||||
)
|
||||
if token_response.status_code >= 400:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="OAuth exchange failed")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="OAuth exchange failed"
|
||||
)
|
||||
token_data = token_response.json()
|
||||
|
||||
id_token = token_data.get("id_token")
|
||||
access_token = token_data.get("access_token")
|
||||
if not id_token or not access_token:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="OAuth exchange failed")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="OAuth exchange failed"
|
||||
)
|
||||
|
||||
try:
|
||||
claims = google_id_token.verify_oauth2_token(
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
from sqlalchemy.future import select
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
from app.agents.chat.runtime.path_resolver import virtual_path_to_doc
|
||||
from app.auth.context import AuthContext
|
||||
from app.db import (
|
||||
Chunk,
|
||||
Document,
|
||||
|
|
@ -18,7 +18,6 @@ from app.db import (
|
|||
Permission,
|
||||
SearchSpace,
|
||||
SearchSpaceMembership,
|
||||
User,
|
||||
get_async_session,
|
||||
)
|
||||
from app.schemas import (
|
||||
|
|
@ -684,7 +683,6 @@ async def search_document_titles(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Lightweight document title search optimized for mention picker (@mentions).
|
||||
|
||||
|
|
@ -789,7 +787,6 @@ async def get_document_by_virtual_path(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Resolve a knowledge-base document by its agent-facing virtual path.
|
||||
|
||||
The agent renders every document under ``/documents/...`` with a
|
||||
|
|
@ -847,7 +844,6 @@ async def get_documents_status(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Batch status endpoint for documents in a search space.
|
||||
|
||||
|
|
@ -1071,7 +1067,6 @@ async def get_watched_folders(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Return root folders that are marked as watched (metadata->>'watched' = 'true')."""
|
||||
await check_permission(
|
||||
session,
|
||||
|
|
@ -1113,7 +1108,6 @@ async def get_document_chunks_paginated(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Paginated chunk loading for a document.
|
||||
Supports both page-based and offset-based access.
|
||||
|
|
@ -1175,7 +1169,6 @@ async def read_document(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Get a specific document by ID.
|
||||
Requires DOCUMENTS_READ permission for the search space.
|
||||
|
|
@ -1230,7 +1223,6 @@ async def update_document(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Update a document.
|
||||
Requires DOCUMENTS_UPDATE permission for the search space.
|
||||
|
|
@ -1290,7 +1282,6 @@ async def delete_document(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Delete a document.
|
||||
Requires DOCUMENTS_DELETE permission for the search space.
|
||||
|
|
@ -1536,7 +1527,6 @@ async def folder_mtime_check(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Pre-upload optimization: check which files need uploading based on mtime.
|
||||
|
||||
Returns the subset of relative paths where the file is new or has a
|
||||
|
|
@ -1754,7 +1744,6 @@ async def folder_unlink(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Handle file deletion events from the desktop watcher.
|
||||
|
||||
For each relative path, find the matching document and delete it.
|
||||
|
|
@ -1809,7 +1798,6 @@ async def folder_sync_finalize(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Finalize a full folder scan by deleting orphaned documents.
|
||||
|
||||
The client sends the complete list of relative paths currently in the
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ from sqlalchemy import func, select
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
from app.db import Chunk, Document, DocumentType, Permission, User, get_async_session
|
||||
from app.db import Chunk, Document, DocumentType, Permission, get_async_session
|
||||
from app.routes.reports_routes import (
|
||||
_FILE_EXTENSIONS,
|
||||
_MEDIA_TYPES,
|
||||
|
|
@ -50,7 +50,6 @@ async def get_editor_content(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Get document content for editing.
|
||||
|
||||
|
|
@ -182,7 +181,6 @@ async def download_document_markdown(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Download the full document content as a .md file.
|
||||
Reconstructs markdown from source_markdown or chunks.
|
||||
|
|
@ -337,7 +335,6 @@ async def export_document(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Export a document in the requested format (reuses the report export pipeline)."""
|
||||
await check_permission(
|
||||
session,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from fastapi.responses import StreamingResponse
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
from app.db import Permission, User, get_async_session
|
||||
from app.db import Permission, get_async_session
|
||||
from app.services.export_service import build_export_zip
|
||||
from app.users import get_auth_context
|
||||
from app.utils.rbac import check_permission
|
||||
|
|
@ -27,7 +27,6 @@ async def export_knowledge_base(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Export documents as a ZIP of markdown files preserving folder structure."""
|
||||
await check_permission(
|
||||
session,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
from sqlalchemy.future import select
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
from app.db import Document, Folder, Permission, User, get_async_session
|
||||
from app.db import Document, Folder, Permission, get_async_session
|
||||
from app.schemas import (
|
||||
BulkDocumentMove,
|
||||
DocumentMove,
|
||||
|
|
@ -95,7 +95,6 @@ async def list_folders(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""List all folders in a search space (flat). Requires DOCUMENTS_READ permission."""
|
||||
try:
|
||||
await check_permission(
|
||||
|
|
@ -127,7 +126,6 @@ async def get_folder(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Get a single folder. Requires DOCUMENTS_READ permission."""
|
||||
try:
|
||||
folder = await session.get(Folder, folder_id)
|
||||
|
|
@ -158,7 +156,6 @@ async def get_folder_breadcrumb(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Get ancestor chain for breadcrumb display. Requires DOCUMENTS_READ permission."""
|
||||
try:
|
||||
folder = await session.get(Folder, folder_id)
|
||||
|
|
@ -203,7 +200,6 @@ async def stop_watching_folder(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Clear the watched flag from a folder's metadata."""
|
||||
folder = await session.get(Folder, folder_id)
|
||||
if not folder:
|
||||
|
|
@ -232,7 +228,6 @@ async def update_folder(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Rename a folder. Requires DOCUMENTS_UPDATE permission."""
|
||||
try:
|
||||
folder = await session.get(Folder, folder_id)
|
||||
|
|
@ -273,7 +268,6 @@ async def move_folder(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Move a folder to a new parent. Requires DOCUMENTS_UPDATE permission."""
|
||||
try:
|
||||
folder = await session.get(Folder, folder_id)
|
||||
|
|
@ -334,7 +328,6 @@ async def reorder_folder(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Reorder a folder among its siblings via fractional indexing. Requires DOCUMENTS_UPDATE."""
|
||||
try:
|
||||
folder = await session.get(Folder, folder_id)
|
||||
|
|
@ -376,7 +369,6 @@ async def delete_folder(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Mark documents for deletion and dispatch Celery to delete docs first, then folders."""
|
||||
try:
|
||||
folder = await session.get(Folder, folder_id)
|
||||
|
|
@ -451,7 +443,6 @@ async def move_document(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Move a document to a folder (or root). Requires DOCUMENTS_UPDATE permission."""
|
||||
try:
|
||||
result = await session.execute(
|
||||
|
|
@ -498,7 +489,6 @@ async def bulk_move_documents(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Move multiple documents to a folder (or root). Requires DOCUMENTS_UPDATE permission."""
|
||||
try:
|
||||
if not request.document_ids:
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ from app.db import (
|
|||
ExternalChatHealthStatus,
|
||||
ExternalChatPeerKind,
|
||||
ExternalChatPlatform,
|
||||
User,
|
||||
get_async_session,
|
||||
)
|
||||
from app.gateway.accounts import (
|
||||
|
|
@ -979,7 +978,6 @@ async def list_platforms(
|
|||
async def get_gateway_config(
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
) -> dict[str, bool | str]:
|
||||
user = auth.user
|
||||
if not config.GATEWAY_ENABLED:
|
||||
return {
|
||||
"enabled": False,
|
||||
|
|
|
|||
|
|
@ -101,7 +101,6 @@ async def request_pairing_code(
|
|||
async def bridge_health(
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
) -> dict[str, Any]:
|
||||
user = auth.user
|
||||
_ensure_baileys_enabled()
|
||||
adapter = WhatsAppBaileysAdapter()
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ from app.db import (
|
|||
Permission,
|
||||
SearchSpace,
|
||||
SearchSpaceMembership,
|
||||
User,
|
||||
get_async_session,
|
||||
)
|
||||
from app.schemas import (
|
||||
|
|
@ -224,6 +223,7 @@ async def _execute_image_generation(
|
|||
|
||||
# Fix relative URLs in response data (for the serving endpoint)
|
||||
from urllib.parse import urlparse
|
||||
|
||||
images = response_dict.get("data", [])
|
||||
provider_base_url = resolved_kwargs.get("api_base")
|
||||
for image in images:
|
||||
|
|
@ -422,7 +422,6 @@ async def get_image_generation(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Get a specific image generation by ID."""
|
||||
try:
|
||||
result = await session.execute(
|
||||
|
|
@ -455,7 +454,6 @@ async def delete_image_generation(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Delete an image generation record."""
|
||||
try:
|
||||
result = await session.execute(
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ from app.db import (
|
|||
Permission,
|
||||
SearchSpace,
|
||||
SearchSpaceMembership,
|
||||
User,
|
||||
get_async_session,
|
||||
)
|
||||
from app.schemas import LogCreate, LogRead, LogUpdate
|
||||
|
|
@ -29,7 +28,6 @@ async def create_log(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Create a new log entry.
|
||||
Note: This is typically called internally. Requires LOGS_READ permission (since logs are usually system-generated).
|
||||
|
|
@ -141,7 +139,6 @@ async def read_log(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Get a specific log by ID.
|
||||
Requires LOGS_READ permission for the search space.
|
||||
|
|
@ -178,7 +175,6 @@ async def update_log(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Update a log entry.
|
||||
Requires LOGS_READ permission (logs are typically updated by system).
|
||||
|
|
@ -222,7 +218,6 @@ async def delete_log(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Delete a log entry.
|
||||
Requires LOGS_DELETE permission for the search space.
|
||||
|
|
@ -262,7 +257,6 @@ async def get_logs_summary(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Get a summary of logs for a search space in the last X hours.
|
||||
Requires LOGS_READ permission for the search space.
|
||||
|
|
|
|||
|
|
@ -325,7 +325,9 @@ async def _assert_connection_access(
|
|||
|
||||
|
||||
@router.get("/global-llm-config-status")
|
||||
async def global_llm_config_status(auth: AuthContext = Depends(require_session_context)):
|
||||
async def global_llm_config_status(
|
||||
auth: AuthContext = Depends(require_session_context),
|
||||
):
|
||||
del auth
|
||||
return {"exists": config.GLOBAL_LLM_CONFIG_FILE_EXISTS}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from sqlalchemy import select
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
from app.db import Document, DocumentType, Permission, User, get_async_session
|
||||
from app.db import Document, DocumentType, Permission, get_async_session
|
||||
from app.schemas import DocumentRead, PaginatedResponse
|
||||
from app.users import get_auth_context
|
||||
from app.utils.rbac import check_permission
|
||||
|
|
@ -102,7 +102,6 @@ async def list_notes(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
List all notes in a search space.
|
||||
|
||||
|
|
@ -196,7 +195,6 @@ async def delete_note(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Delete a note.
|
||||
|
||||
|
|
|
|||
|
|
@ -125,7 +125,6 @@ PERMISSION_DESCRIPTIONS = {
|
|||
async def list_all_permissions(
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
List all available permissions that can be assigned to roles.
|
||||
"""
|
||||
|
|
@ -162,7 +161,6 @@ async def create_role(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Create a new custom role in a search space.
|
||||
Requires ROLES_CREATE permission.
|
||||
|
|
@ -244,7 +242,6 @@ async def list_roles(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
List all roles in a search space.
|
||||
Requires ROLES_READ permission.
|
||||
|
|
@ -283,7 +280,6 @@ async def get_role(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Get a specific role by ID.
|
||||
Requires ROLES_READ permission.
|
||||
|
|
@ -329,7 +325,6 @@ async def update_role(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Update a role.
|
||||
Requires ROLES_UPDATE permission.
|
||||
|
|
@ -427,7 +422,6 @@ async def delete_role(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Delete a custom role.
|
||||
Requires ROLES_DELETE permission.
|
||||
|
|
@ -485,7 +479,6 @@ async def list_members(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
List all members of a search space.
|
||||
Requires MEMBERS_VIEW permission.
|
||||
|
|
@ -551,7 +544,6 @@ async def update_member_role(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Update a member's role.
|
||||
Requires MEMBERS_MANAGE_ROLES permission.
|
||||
|
|
@ -689,7 +681,6 @@ async def remove_member(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Remove a member from a search space.
|
||||
Requires MEMBERS_REMOVE permission.
|
||||
|
|
@ -814,7 +805,6 @@ async def list_invites(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
List all invites for a search space.
|
||||
Requires MEMBERS_INVITE permission.
|
||||
|
|
@ -854,7 +844,6 @@ async def update_invite(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Update an invite.
|
||||
Requires MEMBERS_INVITE permission.
|
||||
|
|
@ -921,7 +910,6 @@ async def revoke_invite(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Revoke (delete) an invite.
|
||||
Requires MEMBERS_INVITE permission.
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ from app.db import (
|
|||
Report,
|
||||
SearchSpace,
|
||||
SearchSpaceMembership,
|
||||
User,
|
||||
get_async_session,
|
||||
)
|
||||
from app.schemas import ReportContentRead, ReportContentUpdate, ReportRead
|
||||
|
|
@ -161,7 +160,6 @@ async def _get_report_with_access(
|
|||
session: AsyncSession,
|
||||
auth: AuthContext,
|
||||
) -> Report:
|
||||
user = auth.user
|
||||
"""Fetch a report and verify the user belongs to its search space.
|
||||
|
||||
Raises HTTPException(404) if not found, HTTPException(403) if no access.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
|||
from sqlalchemy.future import select
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
from app.db import NewChatThread, Permission, User, get_async_session
|
||||
from app.db import NewChatThread, Permission, get_async_session
|
||||
from app.users import get_auth_context
|
||||
from app.utils.rbac import check_permission
|
||||
|
||||
|
|
@ -50,7 +50,6 @@ async def download_sandbox_file(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Download a file from the Daytona sandbox associated with a chat thread."""
|
||||
|
||||
from app.agents.chat.multi_agent_chat.shared.middleware.filesystem.sandbox import (
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ from app.db import (
|
|||
Permission,
|
||||
SearchSourceConnector,
|
||||
SearchSourceConnectorType,
|
||||
User,
|
||||
async_session_maker,
|
||||
get_async_session,
|
||||
)
|
||||
|
|
@ -286,7 +285,6 @@ async def read_search_source_connectors(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
List all search source connectors for a search space.
|
||||
Requires CONNECTORS_READ permission.
|
||||
|
|
@ -330,7 +328,6 @@ async def read_search_source_connector(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Get a specific search source connector by ID.
|
||||
Requires CONNECTORS_READ permission.
|
||||
|
|
@ -565,7 +562,6 @@ async def delete_search_source_connector(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Delete a search source connector and all its associated documents.
|
||||
|
||||
|
|
@ -2735,7 +2731,6 @@ async def list_mcp_connectors(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
List all MCP connectors for a search space.
|
||||
|
||||
|
|
@ -2787,7 +2782,6 @@ async def get_mcp_connector(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Get a specific MCP connector by ID.
|
||||
|
||||
|
|
@ -2841,7 +2835,6 @@ async def update_mcp_connector(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Update an MCP connector.
|
||||
|
||||
|
|
@ -2918,7 +2911,6 @@ async def delete_mcp_connector(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Delete an MCP connector.
|
||||
|
||||
|
|
@ -2977,7 +2969,6 @@ async def test_mcp_server_connection(
|
|||
server_config: dict = Body(...),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Test connection to an MCP server and fetch available tools.
|
||||
|
||||
|
|
@ -3058,7 +3049,6 @@ async def get_drive_picker_token(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""Return an OAuth access token + client ID for the Google Picker API."""
|
||||
result = await session.execute(
|
||||
select(SearchSourceConnector).filter(SearchSourceConnector.id == connector_id)
|
||||
|
|
|
|||
|
|
@ -279,7 +279,9 @@ async def update_search_space(
|
|||
) from e
|
||||
|
||||
|
||||
@router.put("/searchspaces/{search_space_id}/api-access", response_model=SearchSpaceRead)
|
||||
@router.put(
|
||||
"/searchspaces/{search_space_id}/api-access", response_model=SearchSpaceRead
|
||||
)
|
||||
async def update_search_space_api_access(
|
||||
search_space_id: int,
|
||||
body: SearchSpaceApiAccessUpdate,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from pydantic import BaseModel
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
from app.db import User, get_async_session
|
||||
from app.db import get_async_session
|
||||
from app.services.memory import (
|
||||
MemoryRead,
|
||||
MemoryScope,
|
||||
|
|
@ -32,7 +32,6 @@ async def get_team_memory(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
await check_search_space_access(session, auth, search_space_id)
|
||||
memory_md = await read_memory(
|
||||
scope=MemoryScope.TEAM,
|
||||
|
|
@ -49,7 +48,6 @@ async def update_team_memory(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
await check_search_space_access(session, auth, search_space_id)
|
||||
result = await save_memory(
|
||||
scope=MemoryScope.TEAM,
|
||||
|
|
@ -68,7 +66,6 @@ async def reset_team_memory(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
await check_search_space_access(session, auth, search_space_id)
|
||||
result = await reset_memory(
|
||||
scope=MemoryScope.TEAM,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,12 @@ from fastapi import APIRouter, Depends, Request
|
|||
|
||||
from app.auth.context import AuthContext
|
||||
from app.schemas import UserRead, UserUpdate
|
||||
from app.users import UserManager, get_auth_context, get_user_manager, require_session_context
|
||||
from app.users import (
|
||||
UserManager,
|
||||
get_auth_context,
|
||||
get_user_manager,
|
||||
require_session_context,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/users", tags=["users"])
|
||||
|
||||
|
|
@ -23,5 +28,7 @@ async def update_current_user_profile(
|
|||
auth: AuthContext = Depends(require_session_context),
|
||||
user_manager: UserManager = Depends(get_user_manager),
|
||||
):
|
||||
updated_user = await user_manager.update(update, auth.user, safe=True, request=request)
|
||||
updated_user = await user_manager.update(
|
||||
update, auth.user, safe=True, request=request
|
||||
)
|
||||
return updated_user
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ from app.db import (
|
|||
Permission,
|
||||
SearchSpace,
|
||||
SearchSpaceMembership,
|
||||
User,
|
||||
VideoPresentation,
|
||||
get_async_session,
|
||||
)
|
||||
|
|
@ -93,7 +92,6 @@ async def read_video_presentation(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Get a specific video presentation by ID.
|
||||
Requires authentication with VIDEO_PRESENTATIONS_READ permission.
|
||||
|
|
@ -137,7 +135,6 @@ async def delete_video_presentation(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Delete a video presentation.
|
||||
Requires VIDEO_PRESENTATIONS_DELETE permission for the search space.
|
||||
|
|
@ -181,7 +178,6 @@ async def stream_slide_audio(
|
|||
session: AsyncSession = Depends(get_async_session),
|
||||
auth: AuthContext = Depends(get_auth_context),
|
||||
):
|
||||
user = auth.user
|
||||
"""
|
||||
Stream the audio file for a specific slide in a video presentation.
|
||||
The slide_number is 1-based. Audio path is read from the slides JSONB.
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
"""Zero sync authentication context routes."""
|
||||
|
||||
from pydantic import BaseModel
|
||||
from fastapi import APIRouter, Depends
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.auth.context import AuthContext
|
||||
|
|
|
|||
|
|
@ -242,9 +242,9 @@ __all__ = [
|
|||
"SearchSourceConnectorCreate",
|
||||
"SearchSourceConnectorRead",
|
||||
"SearchSourceConnectorUpdate",
|
||||
"SearchSpaceApiAccessUpdate",
|
||||
# Search space schemas
|
||||
"SearchSpaceBase",
|
||||
"SearchSpaceApiAccessUpdate",
|
||||
"SearchSpaceCreate",
|
||||
"SearchSpaceRead",
|
||||
"SearchSpaceUpdate",
|
||||
|
|
|
|||
|
|
@ -401,4 +401,3 @@ async def require_session_context(
|
|||
detail="This action requires an interactive session",
|
||||
)
|
||||
return auth
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,9 @@ async def create_refresh_token(
|
|||
token_hash = hash_token(token)
|
||||
now = datetime.now(UTC)
|
||||
if absolute_expiry is None:
|
||||
absolute_expiry = now + timedelta(seconds=config.REFRESH_ABSOLUTE_LIFETIME_SECONDS)
|
||||
absolute_expiry = now + timedelta(
|
||||
seconds=config.REFRESH_ABSOLUTE_LIFETIME_SECONDS
|
||||
)
|
||||
expires_at = min(
|
||||
now + timedelta(seconds=config.REFRESH_TOKEN_LIFETIME_SECONDS),
|
||||
absolute_expiry,
|
||||
|
|
|
|||
|
|
@ -56,7 +56,9 @@ def test_cookie_transport_re_stamps_access_without_refresh_body_or_cookie():
|
|||
|
||||
set_cookie_headers = response.headers.getlist("set-cookie")
|
||||
assert any(config.SESSION_COOKIE_NAME in header for header in set_cookie_headers)
|
||||
assert not any(config.REFRESH_COOKIE_NAME in header for header in set_cookie_headers)
|
||||
assert not any(
|
||||
config.REFRESH_COOKIE_NAME in header for header in set_cookie_headers
|
||||
)
|
||||
|
||||
|
||||
def test_header_transport_returns_body_tokens_without_cookies():
|
||||
|
|
|
|||
|
|
@ -40,7 +40,9 @@ async def cleanup_supervisors():
|
|||
async def test_start_byo_long_poll_noops_when_mode_is_webhook(monkeypatch):
|
||||
monkeypatch.setattr(byo_long_poll.config, "GATEWAY_ENABLED", True)
|
||||
monkeypatch.setattr(byo_long_poll.config, "GATEWAY_TELEGRAM_INTAKE_MODE", "webhook")
|
||||
monkeypatch.setattr(byo_long_poll.config, "GATEWAY_WHATSAPP_INTAKE_MODE", "disabled")
|
||||
monkeypatch.setattr(
|
||||
byo_long_poll.config, "GATEWAY_WHATSAPP_INTAKE_MODE", "disabled"
|
||||
)
|
||||
|
||||
await byo_long_poll.start_byo_long_poll_supervisors()
|
||||
|
||||
|
|
@ -53,7 +55,9 @@ async def test_start_byo_long_poll_noops_when_no_byo_accounts(mocker, monkeypatc
|
|||
monkeypatch.setattr(
|
||||
byo_long_poll.config, "GATEWAY_TELEGRAM_INTAKE_MODE", "longpoll"
|
||||
)
|
||||
monkeypatch.setattr(byo_long_poll.config, "GATEWAY_WHATSAPP_INTAKE_MODE", "disabled")
|
||||
monkeypatch.setattr(
|
||||
byo_long_poll.config, "GATEWAY_WHATSAPP_INTAKE_MODE", "disabled"
|
||||
)
|
||||
session = mocker.AsyncMock()
|
||||
session.execute.return_value = ScalarResult([])
|
||||
monkeypatch.setattr(
|
||||
|
|
@ -75,7 +79,9 @@ async def test_start_byo_long_poll_spawns_one_supervisor_per_account(
|
|||
monkeypatch.setattr(
|
||||
byo_long_poll.config, "GATEWAY_TELEGRAM_INTAKE_MODE", "longpoll"
|
||||
)
|
||||
monkeypatch.setattr(byo_long_poll.config, "GATEWAY_WHATSAPP_INTAKE_MODE", "disabled")
|
||||
monkeypatch.setattr(
|
||||
byo_long_poll.config, "GATEWAY_WHATSAPP_INTAKE_MODE", "disabled"
|
||||
)
|
||||
accounts = [mocker.Mock(id=1), mocker.Mock(id=2)]
|
||||
session = mocker.AsyncMock()
|
||||
session.execute.return_value = ScalarResult(accounts)
|
||||
|
|
@ -125,7 +131,9 @@ async def test_shutdown_cancels_running_supervisors(mocker, monkeypatch):
|
|||
monkeypatch.setattr(
|
||||
byo_long_poll.config, "GATEWAY_TELEGRAM_INTAKE_MODE", "longpoll"
|
||||
)
|
||||
monkeypatch.setattr(byo_long_poll.config, "GATEWAY_WHATSAPP_INTAKE_MODE", "disabled")
|
||||
monkeypatch.setattr(
|
||||
byo_long_poll.config, "GATEWAY_WHATSAPP_INTAKE_MODE", "disabled"
|
||||
)
|
||||
session = mocker.AsyncMock()
|
||||
session.execute.return_value = ScalarResult([mocker.Mock(id=1)])
|
||||
monkeypatch.setattr(
|
||||
|
|
|
|||
|
|
@ -450,7 +450,9 @@ class TestRevertTurnDispatch:
|
|||
thread_id=1,
|
||||
chat_turn_id="ct-mixed-all",
|
||||
session=session,
|
||||
auth=AuthContext.session(_FakeUser()), # only id=7 has a different user_id
|
||||
auth=AuthContext.session(
|
||||
_FakeUser()
|
||||
), # only id=7 has a different user_id
|
||||
)
|
||||
|
||||
assert response.total == len(rows) == 6
|
||||
|
|
|
|||
|
|
@ -32,7 +32,9 @@ def _patch_common_bundle_dependencies(monkeypatch: pytest.MonkeyPatch):
|
|||
|
||||
_CapturedChatLiteLLM.calls = []
|
||||
|
||||
async def _fake_search_space(_session: Any, _search_space_id: int) -> SimpleNamespace:
|
||||
async def _fake_search_space(
|
||||
_session: Any, _search_space_id: int
|
||||
) -> SimpleNamespace:
|
||||
return SimpleNamespace(id=42, user_id="user-1")
|
||||
|
||||
monkeypatch.setattr(llm_bundle, "_load_search_space", _fake_search_space)
|
||||
|
|
|
|||
|
|
@ -32,11 +32,7 @@ CONNECTOR_LISTERS = [
|
|||
|
||||
|
||||
def _python_files() -> list[Path]:
|
||||
return [
|
||||
path
|
||||
for path in APP_ROOT.rglob("*.py")
|
||||
if "__pycache__" not in path.parts
|
||||
]
|
||||
return [path for path in APP_ROOT.rglob("*.py") if "__pycache__" not in path.parts]
|
||||
|
||||
|
||||
def test_current_active_user_is_removed_from_app_tree() -> None:
|
||||
|
|
|
|||
|
|
@ -290,4 +290,4 @@ class TestExtractTextContent:
|
|||
def test_boolean_returns_empty_string(self):
|
||||
from app.utils.content_utils import extract_text_content
|
||||
|
||||
assert extract_text_content(True) == ""
|
||||
assert extract_text_content(True) == ""
|
||||
|
|
|
|||
|
|
@ -23,8 +23,7 @@ function validateZeroCacheRequest(request: Request): NextResponse | null {
|
|||
async function authenticateRequest(
|
||||
request: Request
|
||||
): Promise<
|
||||
| { ctx: Exclude<Context, undefined>; error?: never }
|
||||
| { ctx?: never; error: NextResponse }
|
||||
{ ctx: Exclude<Context, undefined>; error?: never } | { ctx?: never; error: NextResponse }
|
||||
> {
|
||||
const authHeader = request.headers.get("Authorization");
|
||||
const cookieHeader = request.headers.get("Cookie");
|
||||
|
|
|
|||
|
|
@ -105,9 +105,7 @@ export function ApiKeyContent() {
|
|||
<p className="font-mono text-xs text-muted-foreground">{token.prefix}...</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Expires: {expiresAt ? expiresAt.toLocaleDateString() : "Never"} · Last used:{" "}
|
||||
{token.last_used_at
|
||||
? new Date(token.last_used_at).toLocaleString()
|
||||
: "Never"}
|
||||
{token.last_used_at ? new Date(token.last_used_at).toLocaleString() : "Never"}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
|
|
@ -175,8 +173,7 @@ export function ApiKeyContent() {
|
|||
<DialogHeader>
|
||||
<DialogTitle>Copy your token now</DialogTitle>
|
||||
<DialogDescription>
|
||||
This token is shown only once. Store it somewhere secure before closing this
|
||||
dialog.
|
||||
This token is shown only once. Store it somewhere secure before closing this dialog.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex items-center gap-2 rounded-md border border-border/60 bg-muted/30 p-2">
|
||||
|
|
|
|||
|
|
@ -9,13 +9,7 @@ import { useEffect, useState } from "react";
|
|||
import { searchSpacesAtom } from "@/atoms/search-spaces/search-space-query.atoms";
|
||||
import { CreateSearchSpaceDialog } from "@/components/layout";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Card, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { useGlobalLoadingEffect } from "@/hooks/use-global-loading";
|
||||
|
||||
function ErrorScreen({ message }: { message: string }) {
|
||||
|
|
|
|||
|
|
@ -18,13 +18,10 @@ import {
|
|||
import { ThemeProvider } from "@/components/theme/theme-provider";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import { LocaleProvider } from "@/contexts/LocaleContext";
|
||||
import { BUILD_TIME_AUTH_TYPE } from "@/lib/env-config";
|
||||
import { PlatformProvider } from "@/contexts/platform-context";
|
||||
import { BUILD_TIME_AUTH_TYPE } from "@/lib/env-config";
|
||||
import { ReactQueryClientProvider } from "@/lib/query-client/query-client.provider";
|
||||
import {
|
||||
getRuntimeAuthInitScript,
|
||||
resolveRuntimeAuthUiMode,
|
||||
} from "@/lib/runtime-auth-config";
|
||||
import { getRuntimeAuthInitScript, resolveRuntimeAuthUiMode } from "@/lib/runtime-auth-config";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const roboto = Roboto({
|
||||
|
|
|
|||
|
|
@ -100,9 +100,7 @@ export const ObsidianConnectForm: FC<ConnectFormProps> = ({ onBack }) => {
|
|||
<div className="flex size-7 items-center justify-center rounded-md border border-slate-400/30 text-xs font-medium">
|
||||
2
|
||||
</div>
|
||||
<h3 className="text-sm font-medium sm:text-base">
|
||||
Create a personal access token
|
||||
</h3>
|
||||
<h3 className="text-sm font-medium sm:text-base">Create a personal access token</h3>
|
||||
</header>
|
||||
<p className="mb-3 text-[11px] text-muted-foreground sm:text-xs">
|
||||
Create a token and paste it into the plugin's{" "}
|
||||
|
|
|
|||
|
|
@ -28,11 +28,7 @@ function initializeMermaid() {
|
|||
mermaidInitialized = true;
|
||||
}
|
||||
|
||||
function MermaidDiagramComponent({
|
||||
source,
|
||||
isDarkMode,
|
||||
fallback,
|
||||
}: MermaidDiagramProps) {
|
||||
function MermaidDiagramComponent({ source, isDarkMode, fallback }: MermaidDiagramProps) {
|
||||
const id = useId();
|
||||
const [svg, setSvg] = useState<string | null>(null);
|
||||
const [hasError, setHasError] = useState(false);
|
||||
|
|
@ -107,11 +103,7 @@ function MermaidDiagramComponent({
|
|||
aria-label={hasCopied ? "Copied Mermaid source" : "Copy Mermaid source"}
|
||||
>
|
||||
<span className="sr-only">Copy Source</span>
|
||||
{hasCopied ? (
|
||||
<CheckIcon className="!size-3" />
|
||||
) : (
|
||||
<CopyIcon className="!size-3" />
|
||||
)}
|
||||
{hasCopied ? <CheckIcon className="!size-3" /> : <CopyIcon className="!size-3" />}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
|
@ -131,4 +123,4 @@ function MermaidDiagramComponent({
|
|||
);
|
||||
}
|
||||
|
||||
export const MermaidDiagram = memo(MermaidDiagramComponent);
|
||||
export const MermaidDiagram = memo(MermaidDiagramComponent);
|
||||
|
|
|
|||
|
|
@ -274,9 +274,9 @@ export function ImageModelSelector({
|
|||
<ImagePlus className="size-4 shrink-0" />
|
||||
)}
|
||||
{showIconOnlyTrigger ? null : (
|
||||
<span className="min-w-0 flex-1 truncate text-sm">
|
||||
{selected ? modelName(selected) : "Auto"}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate text-sm">
|
||||
{selected ? modelName(selected) : "Auto"}
|
||||
</span>
|
||||
)}
|
||||
<ChevronDown className="h-3.5 w-3.5 shrink-0" />
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -43,12 +43,7 @@ export function PublicChatFooter({ shareToken }: PublicChatFooterProps) {
|
|||
const action = new URLSearchParams(window.location.search).get("action");
|
||||
|
||||
// Only auto-clone once, if authenticated and action=clone is present
|
||||
if (
|
||||
action === "clone" &&
|
||||
session.authenticated &&
|
||||
!hasAutoCloned.current &&
|
||||
!isCloning
|
||||
) {
|
||||
if (action === "clone" && session.authenticated && !hasAutoCloned.current && !isCloning) {
|
||||
hasAutoCloned.current = true;
|
||||
triggerClone();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -210,8 +210,8 @@ export function GeneralSettingsManager({ searchSpaceId }: GeneralSettingsManager
|
|||
<div className="space-y-1">
|
||||
<Label htmlFor="api-access-enabled">Programmatic API access</Label>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Allow personal access tokens to use this search space. Web and desktop sessions are
|
||||
not affected.
|
||||
Allow personal access tokens to use this search space. Web and desktop sessions are not
|
||||
affected.
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
import { useCallback, useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import type {
|
||||
CreatePatRequest,
|
||||
CreatedPat,
|
||||
CreatePatRequest,
|
||||
PersonalAccessToken,
|
||||
} from "@/contracts/types/pat.types";
|
||||
import { patsApiService } from "@/lib/apis/pats-api.service";
|
||||
|
|
|
|||
|
|
@ -50,11 +50,7 @@ export type RequestOptions = {
|
|||
};
|
||||
|
||||
class BaseApiService {
|
||||
noAuthEndpoints: string[] = [
|
||||
"/auth/jwt/login",
|
||||
"/auth/register",
|
||||
"/auth/jwt/refresh",
|
||||
];
|
||||
noAuthEndpoints: string[] = ["/auth/jwt/login", "/auth/register", "/auth/jwt/refresh"];
|
||||
|
||||
// Prefixes that don't require auth (checked with startsWith)
|
||||
noAuthPrefixes: string[] = ["/api/v1/public/"];
|
||||
|
|
|
|||
|
|
@ -13,11 +13,11 @@ import {
|
|||
getSearchSpacesRequest,
|
||||
getSearchSpacesResponse,
|
||||
leaveSearchSpaceResponse,
|
||||
type UpdateSearchSpaceRequest,
|
||||
type UpdateSearchSpaceApiAccessRequest,
|
||||
updateSearchSpaceRequest,
|
||||
type UpdateSearchSpaceRequest,
|
||||
updateSearchSpaceApiAccessRequest,
|
||||
updateSearchSpaceApiAccessResponse,
|
||||
updateSearchSpaceRequest,
|
||||
updateSearchSpaceResponse,
|
||||
} from "@/contracts/types/search-space.types";
|
||||
import { ValidationError } from "../error";
|
||||
|
|
|
|||
|
|
@ -4,11 +4,7 @@ let desktopAccessToken: string | null = null;
|
|||
let didSubscribeToDesktopAuth = false;
|
||||
|
||||
function subscribeToDesktopAuth(): void {
|
||||
if (
|
||||
didSubscribeToDesktopAuth ||
|
||||
typeof window === "undefined" ||
|
||||
!window.electronAPI
|
||||
) {
|
||||
if (didSubscribeToDesktopAuth || typeof window === "undefined" || !window.electronAPI) {
|
||||
return;
|
||||
}
|
||||
didSubscribeToDesktopAuth = true;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import { NextResponse, type NextRequest } from "next/server";
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { BUILD_TIME_AUTH_TYPE } from "@/lib/env-config";
|
||||
import {
|
||||
RUNTIME_AUTH_TYPE_COOKIE_NAME,
|
||||
resolveRuntimeAuthUiMode,
|
||||
} from "@/lib/runtime-auth-config";
|
||||
import { RUNTIME_AUTH_TYPE_COOKIE_NAME, resolveRuntimeAuthUiMode } from "@/lib/runtime-auth-config";
|
||||
|
||||
export function proxy(request: NextRequest) {
|
||||
const response = NextResponse.next();
|
||||
|
|
|
|||
|
|
@ -20,7 +20,13 @@ export function constrainToAllowedSpaces<T extends SpaceScopedQuery>(query: T, c
|
|||
if (allowedSpaceIds.length === 1) {
|
||||
return query.where("searchSpaceId", allowedSpaceIds[0]) as T;
|
||||
}
|
||||
return query.where(({ cmp, or }: { cmp: (column: string, value: number) => unknown; or: (...args: unknown[]) => unknown }) =>
|
||||
or(...allowedSpaceIds.map((id) => cmp("searchSpaceId", id)))
|
||||
return query.where(
|
||||
({
|
||||
cmp,
|
||||
or,
|
||||
}: {
|
||||
cmp: (column: string, value: number) => unknown;
|
||||
or: (...args: unknown[]) => unknown;
|
||||
}) => or(...allowedSpaceIds.map((id) => cmp("searchSpaceId", id)))
|
||||
) as T;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ import { constrainToAllowedSpaces } from "./authz";
|
|||
// authorizes via `automation_id -> search_space`. No search_space_id on the
|
||||
// table by design.
|
||||
export const automationRunQueries = {
|
||||
byAutomation: defineQuery(z.object({ automationId: z.number() }), ({ args: { automationId }, ctx }) =>
|
||||
zql.automation_runs
|
||||
.where("automationId", automationId)
|
||||
.whereExists("automation", (q) => constrainToAllowedSpaces(q, ctx))
|
||||
.orderBy("createdAt", "desc")
|
||||
byAutomation: defineQuery(
|
||||
z.object({ automationId: z.number() }),
|
||||
({ args: { automationId }, ctx }) =>
|
||||
zql.automation_runs
|
||||
.where("automationId", automationId)
|
||||
.whereExists("automation", (q) => constrainToAllowedSpaces(q, ctx))
|
||||
.orderBy("createdAt", "desc")
|
||||
),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,17 +4,23 @@ import { zql } from "../schema/index";
|
|||
import { canReadSpace, constrainToAllowedSpaces, denySpace } from "./authz";
|
||||
|
||||
export const documentQueries = {
|
||||
bySpace: defineQuery(z.object({ searchSpaceId: z.number() }), ({ args: { searchSpaceId }, ctx }) => {
|
||||
const query = zql.documents.where("searchSpaceId", searchSpaceId);
|
||||
if (!canReadSpace(ctx, searchSpaceId)) return denySpace(query).orderBy("createdAt", "desc");
|
||||
return constrainToAllowedSpaces(query, ctx).orderBy("createdAt", "desc");
|
||||
}),
|
||||
bySpace: defineQuery(
|
||||
z.object({ searchSpaceId: z.number() }),
|
||||
({ args: { searchSpaceId }, ctx }) => {
|
||||
const query = zql.documents.where("searchSpaceId", searchSpaceId);
|
||||
if (!canReadSpace(ctx, searchSpaceId)) return denySpace(query).orderBy("createdAt", "desc");
|
||||
return constrainToAllowedSpaces(query, ctx).orderBy("createdAt", "desc");
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
export const connectorQueries = {
|
||||
bySpace: defineQuery(z.object({ searchSpaceId: z.number() }), ({ args: { searchSpaceId }, ctx }) => {
|
||||
const query = zql.search_source_connectors.where("searchSpaceId", searchSpaceId);
|
||||
if (!canReadSpace(ctx, searchSpaceId)) return denySpace(query).orderBy("createdAt", "desc");
|
||||
return constrainToAllowedSpaces(query, ctx).orderBy("createdAt", "desc");
|
||||
}),
|
||||
bySpace: defineQuery(
|
||||
z.object({ searchSpaceId: z.number() }),
|
||||
({ args: { searchSpaceId }, ctx }) => {
|
||||
const query = zql.search_source_connectors.where("searchSpaceId", searchSpaceId);
|
||||
if (!canReadSpace(ctx, searchSpaceId)) return denySpace(query).orderBy("createdAt", "desc");
|
||||
return constrainToAllowedSpaces(query, ctx).orderBy("createdAt", "desc");
|
||||
}
|
||||
),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,9 +4,12 @@ import { zql } from "../schema/index";
|
|||
import { canReadSpace, constrainToAllowedSpaces, denySpace } from "./authz";
|
||||
|
||||
export const folderQueries = {
|
||||
bySpace: defineQuery(z.object({ searchSpaceId: z.number() }), ({ args: { searchSpaceId }, ctx }) => {
|
||||
const query = zql.folders.where("searchSpaceId", searchSpaceId);
|
||||
if (!canReadSpace(ctx, searchSpaceId)) return denySpace(query).orderBy("position", "asc");
|
||||
return constrainToAllowedSpaces(query, ctx).orderBy("position", "asc");
|
||||
}),
|
||||
bySpace: defineQuery(
|
||||
z.object({ searchSpaceId: z.number() }),
|
||||
({ args: { searchSpaceId }, ctx }) => {
|
||||
const query = zql.folders.where("searchSpaceId", searchSpaceId);
|
||||
if (!canReadSpace(ctx, searchSpaceId)) return denySpace(query).orderBy("position", "asc");
|
||||
return constrainToAllowedSpaces(query, ctx).orderBy("position", "asc");
|
||||
}
|
||||
),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,11 +4,14 @@ import { zql } from "../schema/index";
|
|||
import { canReadSpace, constrainToAllowedSpaces, denySpace } from "./authz";
|
||||
|
||||
export const podcastQueries = {
|
||||
bySpace: defineQuery(z.object({ searchSpaceId: z.number() }), ({ args: { searchSpaceId }, ctx }) => {
|
||||
const query = zql.podcasts.where("searchSpaceId", searchSpaceId);
|
||||
if (!canReadSpace(ctx, searchSpaceId)) return denySpace(query).orderBy("createdAt", "desc");
|
||||
return constrainToAllowedSpaces(query, ctx).orderBy("createdAt", "desc");
|
||||
}),
|
||||
bySpace: defineQuery(
|
||||
z.object({ searchSpaceId: z.number() }),
|
||||
({ args: { searchSpaceId }, ctx }) => {
|
||||
const query = zql.podcasts.where("searchSpaceId", searchSpaceId);
|
||||
if (!canReadSpace(ctx, searchSpaceId)) return denySpace(query).orderBy("createdAt", "desc");
|
||||
return constrainToAllowedSpaces(query, ctx).orderBy("createdAt", "desc");
|
||||
}
|
||||
),
|
||||
byId: defineQuery(z.object({ podcastId: z.number() }), ({ args: { podcastId }, ctx }) =>
|
||||
constrainToAllowedSpaces(zql.podcasts.where("id", podcastId), ctx).one()
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue