mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-06 22:12:12 +02:00
refactor(backend): rename search_space -> workspace across app bulk (Phase 2 Wave D)
Scoped codemod over surfsense_backend/app (excluding routes/, Wave E): renames search_space_id -> workspace_id, search_space -> workspace, SearchSpace -> Workspace across services, utils, tasks, agents, gateway, event_bus, notifications, podcasts, automations, observability params, and prompt .md files. Also flips the camelCase payload key searchSpaceId -> workspaceId (no backend reader; hard cutover). Preserved carve-outs (verbatim): Celery task names "delete_search_space_background" and "ai_sort_search_space" (wire names), and the OTel/metric key "search_space.id" (dashboards depend on it). Enum values 'SEARCH_SPACE' and SearchSourceConnector untouched.
This commit is contained in:
parent
0c53d884eb
commit
7fb0707933
259 changed files with 1996 additions and 1996 deletions
|
|
@ -118,14 +118,14 @@ def generate_connector_name_with_identifier(
|
|||
async def count_connectors_of_type(
|
||||
session: AsyncSession,
|
||||
connector_type: SearchSourceConnectorType,
|
||||
search_space_id: int,
|
||||
workspace_id: int,
|
||||
user_id: UUID,
|
||||
) -> int:
|
||||
"""Count existing connectors of a type for a user in a search space."""
|
||||
"""Count existing connectors of a type for a user in a workspace."""
|
||||
result = await session.execute(
|
||||
select(func.count(SearchSourceConnector.id)).where(
|
||||
SearchSourceConnector.connector_type == connector_type,
|
||||
SearchSourceConnector.search_space_id == search_space_id,
|
||||
SearchSourceConnector.workspace_id == workspace_id,
|
||||
SearchSourceConnector.user_id == user_id,
|
||||
)
|
||||
)
|
||||
|
|
@ -135,7 +135,7 @@ async def count_connectors_of_type(
|
|||
async def check_duplicate_connector(
|
||||
session: AsyncSession,
|
||||
connector_type: SearchSourceConnectorType,
|
||||
search_space_id: int,
|
||||
workspace_id: int,
|
||||
user_id: UUID,
|
||||
identifier: str | None,
|
||||
) -> bool:
|
||||
|
|
@ -145,7 +145,7 @@ async def check_duplicate_connector(
|
|||
Args:
|
||||
session: Database session
|
||||
connector_type: The type of connector
|
||||
search_space_id: The search space ID
|
||||
workspace_id: The workspace ID
|
||||
user_id: The user ID
|
||||
identifier: User identifier (email, workspace name, etc.)
|
||||
|
||||
|
|
@ -159,7 +159,7 @@ async def check_duplicate_connector(
|
|||
result = await session.execute(
|
||||
select(func.count(SearchSourceConnector.id)).where(
|
||||
SearchSourceConnector.connector_type == connector_type,
|
||||
SearchSourceConnector.search_space_id == search_space_id,
|
||||
SearchSourceConnector.workspace_id == workspace_id,
|
||||
SearchSourceConnector.user_id == user_id,
|
||||
SearchSourceConnector.name == expected_name,
|
||||
)
|
||||
|
|
@ -170,11 +170,11 @@ async def check_duplicate_connector(
|
|||
async def ensure_unique_connector_name(
|
||||
session: AsyncSession,
|
||||
name: str,
|
||||
search_space_id: int,
|
||||
workspace_id: int,
|
||||
user_id: UUID,
|
||||
) -> str:
|
||||
"""
|
||||
Ensure a connector name is unique within a user's search space.
|
||||
Ensure a connector name is unique within a user's workspace.
|
||||
|
||||
If the name already exists, appends a counter suffix: (2), (3), etc.
|
||||
Uses the same suffix format as generate_unique_connector_name.
|
||||
|
|
@ -182,7 +182,7 @@ async def ensure_unique_connector_name(
|
|||
Args:
|
||||
session: Database session
|
||||
name: Desired connector name
|
||||
search_space_id: The search space ID
|
||||
workspace_id: The workspace ID
|
||||
user_id: The user ID
|
||||
|
||||
Returns:
|
||||
|
|
@ -190,7 +190,7 @@ async def ensure_unique_connector_name(
|
|||
"""
|
||||
result = await session.execute(
|
||||
select(SearchSourceConnector.name).where(
|
||||
SearchSourceConnector.search_space_id == search_space_id,
|
||||
SearchSourceConnector.workspace_id == workspace_id,
|
||||
SearchSourceConnector.user_id == user_id,
|
||||
)
|
||||
)
|
||||
|
|
@ -208,7 +208,7 @@ async def ensure_unique_connector_name(
|
|||
async def generate_unique_connector_name(
|
||||
session: AsyncSession,
|
||||
connector_type: SearchSourceConnectorType,
|
||||
search_space_id: int,
|
||||
workspace_id: int,
|
||||
user_id: UUID,
|
||||
identifier: str | None = None,
|
||||
) -> str:
|
||||
|
|
@ -221,7 +221,7 @@ async def generate_unique_connector_name(
|
|||
Args:
|
||||
session: Database session
|
||||
connector_type: The type of connector
|
||||
search_space_id: The search space ID
|
||||
workspace_id: The workspace ID
|
||||
user_id: The user ID
|
||||
identifier: Optional user identifier (email, workspace name, etc.)
|
||||
|
||||
|
|
@ -235,12 +235,12 @@ async def generate_unique_connector_name(
|
|||
return await ensure_unique_connector_name(
|
||||
session,
|
||||
name,
|
||||
search_space_id,
|
||||
workspace_id,
|
||||
user_id,
|
||||
)
|
||||
|
||||
count = await count_connectors_of_type(
|
||||
session, connector_type, search_space_id, user_id
|
||||
session, connector_type, workspace_id, user_id
|
||||
)
|
||||
|
||||
if count == 0:
|
||||
|
|
|
|||
|
|
@ -257,28 +257,28 @@ async def convert_document_to_markdown(elements):
|
|||
return "".join(markdown_parts)
|
||||
|
||||
|
||||
def generate_content_hash(content: str, search_space_id: int) -> str:
|
||||
"""Generate SHA-256 hash for the given content combined with search space ID."""
|
||||
combined_data = f"{search_space_id}:{content}"
|
||||
def generate_content_hash(content: str, workspace_id: int) -> str:
|
||||
"""Generate SHA-256 hash for the given content combined with workspace ID."""
|
||||
combined_data = f"{workspace_id}:{content}"
|
||||
return hashlib.sha256(combined_data.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def generate_unique_identifier_hash(
|
||||
document_type: DocumentType,
|
||||
unique_identifier: str | int | float,
|
||||
search_space_id: int,
|
||||
workspace_id: int,
|
||||
) -> str:
|
||||
"""
|
||||
Generate SHA-256 hash for a unique document identifier from connector sources.
|
||||
|
||||
This function creates a consistent hash based on the document type, its unique
|
||||
identifier from the source system, and the search space ID. This helps prevent
|
||||
identifier from the source system, and the workspace ID. This helps prevent
|
||||
duplicate documents when syncing from various connectors like Slack, Notion, Jira, etc.
|
||||
|
||||
Args:
|
||||
document_type: The type of document (e.g., SLACK_CONNECTOR, NOTION_CONNECTOR)
|
||||
unique_identifier: The unique ID from the source system (e.g., message ID, page ID)
|
||||
search_space_id: The search space this document belongs to
|
||||
workspace_id: The workspace this document belongs to
|
||||
|
||||
Returns:
|
||||
str: SHA-256 hash string representing the unique document identifier
|
||||
|
|
@ -294,7 +294,7 @@ def generate_unique_identifier_hash(
|
|||
# Convert unique_identifier to string to handle different types
|
||||
identifier_str = str(unique_identifier)
|
||||
|
||||
# Combine document type value, unique identifier, and search space ID
|
||||
combined_data = f"{document_type.value}:{identifier_str}:{search_space_id}"
|
||||
# Combine document type value, unique identifier, and workspace ID
|
||||
combined_data = f"{document_type.value}:{identifier_str}:{workspace_id}"
|
||||
|
||||
return hashlib.sha256(combined_data.encode("utf-8")).hexdigest()
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ class OAuthStateManager:
|
|||
Generate cryptographically signed state parameter.
|
||||
|
||||
Args:
|
||||
space_id: The search space ID
|
||||
space_id: The workspace ID
|
||||
user_id: The user ID
|
||||
**extra_fields: Additional fields to include in state (e.g., code_verifier for PKCE)
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ CONNECTOR_TASK_MAP = {
|
|||
|
||||
def create_periodic_schedule(
|
||||
connector_id: int,
|
||||
search_space_id: int,
|
||||
workspace_id: int,
|
||||
user_id: str,
|
||||
connector_type: SearchSourceConnectorType,
|
||||
frequency_minutes: int,
|
||||
|
|
@ -44,7 +44,7 @@ def create_periodic_schedule(
|
|||
|
||||
Args:
|
||||
connector_id: ID of the connector
|
||||
search_space_id: ID of the search space
|
||||
workspace_id: ID of the workspace
|
||||
user_id: User ID
|
||||
connector_type: Type of connector
|
||||
frequency_minutes: Frequency in minutes (used for logging)
|
||||
|
|
@ -94,7 +94,7 @@ def create_periodic_schedule(
|
|||
# Trigger the first run immediately
|
||||
task = task_map.get(connector_type)
|
||||
if task:
|
||||
task.delay(connector_id, search_space_id, user_id, None, None)
|
||||
task.delay(connector_id, workspace_id, user_id, None, None)
|
||||
logger.info(
|
||||
f"✓ First indexing run triggered for connector {connector_id}. "
|
||||
f"Periodic indexing will continue automatically every {frequency_minutes} minutes."
|
||||
|
|
@ -133,7 +133,7 @@ def delete_periodic_schedule(connector_id: int) -> bool:
|
|||
|
||||
def update_periodic_schedule(
|
||||
connector_id: int,
|
||||
search_space_id: int,
|
||||
workspace_id: int,
|
||||
user_id: str,
|
||||
connector_type: SearchSourceConnectorType,
|
||||
frequency_minutes: int,
|
||||
|
|
@ -146,7 +146,7 @@ def update_periodic_schedule(
|
|||
|
||||
Args:
|
||||
connector_id: ID of the connector
|
||||
search_space_id: ID of the search space
|
||||
workspace_id: ID of the workspace
|
||||
user_id: User ID
|
||||
connector_type: Type of connector
|
||||
frequency_minutes: New frequency in minutes
|
||||
|
|
@ -160,5 +160,5 @@ def update_periodic_schedule(
|
|||
)
|
||||
# Optionally trigger an immediate run with the new schedule
|
||||
# Uncomment the line below if you want immediate execution on schedule update
|
||||
# return create_periodic_schedule(connector_id, search_space_id, user_id, connector_type, frequency_minutes)
|
||||
# return create_periodic_schedule(connector_id, workspace_id, user_id, connector_type, frequency_minutes)
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"""
|
||||
RBAC (Role-Based Access Control) utility functions.
|
||||
Provides helpers for checking user permissions in search spaces.
|
||||
Provides helpers for checking user permissions in workspaces.
|
||||
"""
|
||||
|
||||
import secrets
|
||||
|
|
@ -14,9 +14,9 @@ from sqlalchemy.orm import selectinload
|
|||
from app.auth.context import AuthContext
|
||||
from app.db import (
|
||||
Permission,
|
||||
SearchSpace,
|
||||
SearchSpaceMembership,
|
||||
SearchSpaceRole,
|
||||
Workspace,
|
||||
WorkspaceMembership,
|
||||
WorkspaceRole,
|
||||
has_permission,
|
||||
)
|
||||
|
||||
|
|
@ -24,25 +24,25 @@ from app.db import (
|
|||
async def get_user_membership(
|
||||
session: AsyncSession,
|
||||
user_id: UUID,
|
||||
search_space_id: int,
|
||||
) -> SearchSpaceMembership | None:
|
||||
workspace_id: int,
|
||||
) -> WorkspaceMembership | None:
|
||||
"""
|
||||
Get the user's membership in a search space.
|
||||
Get the user's membership in a workspace.
|
||||
|
||||
Args:
|
||||
session: Database session
|
||||
user_id: User UUID
|
||||
search_space_id: Search space ID
|
||||
workspace_id: Workspace ID
|
||||
|
||||
Returns:
|
||||
SearchSpaceMembership if found, None otherwise
|
||||
WorkspaceMembership if found, None otherwise
|
||||
"""
|
||||
result = await session.execute(
|
||||
select(SearchSpaceMembership)
|
||||
.options(selectinload(SearchSpaceMembership.role))
|
||||
select(WorkspaceMembership)
|
||||
.options(selectinload(WorkspaceMembership.role))
|
||||
.filter(
|
||||
SearchSpaceMembership.user_id == user_id,
|
||||
SearchSpaceMembership.search_space_id == search_space_id,
|
||||
WorkspaceMembership.user_id == user_id,
|
||||
WorkspaceMembership.workspace_id == workspace_id,
|
||||
)
|
||||
)
|
||||
return result.scalars().first()
|
||||
|
|
@ -51,20 +51,20 @@ async def get_user_membership(
|
|||
async def get_user_permissions(
|
||||
session: AsyncSession,
|
||||
user_id: UUID,
|
||||
search_space_id: int,
|
||||
workspace_id: int,
|
||||
) -> list[str]:
|
||||
"""
|
||||
Get the user's permissions in a search space.
|
||||
Get the user's permissions in a workspace.
|
||||
|
||||
Args:
|
||||
session: Database session
|
||||
user_id: User UUID
|
||||
search_space_id: Search space ID
|
||||
workspace_id: Workspace ID
|
||||
|
||||
Returns:
|
||||
List of permission strings
|
||||
"""
|
||||
membership = await get_user_membership(session, user_id, search_space_id)
|
||||
membership = await get_user_membership(session, user_id, workspace_id)
|
||||
|
||||
if not membership:
|
||||
return []
|
||||
|
|
@ -84,19 +84,19 @@ async def get_allowed_read_space_ids(
|
|||
session: AsyncSession,
|
||||
auth: AuthContext,
|
||||
) -> list[int]:
|
||||
"""Return search spaces the principal may read through sync transports.
|
||||
"""Return workspaces the principal may read through sync transports.
|
||||
|
||||
This mirrors the basic REST search-space access rule: membership is required,
|
||||
This mirrors the basic REST workspace access rule: membership is required,
|
||||
and PAT principals are additionally constrained by the per-space API gate.
|
||||
"""
|
||||
stmt = (
|
||||
select(SearchSpaceMembership.search_space_id)
|
||||
.join(SearchSpace, SearchSpace.id == SearchSpaceMembership.search_space_id)
|
||||
.filter(SearchSpaceMembership.user_id == auth.user.id)
|
||||
.order_by(SearchSpaceMembership.search_space_id)
|
||||
select(WorkspaceMembership.workspace_id)
|
||||
.join(Workspace, Workspace.id == WorkspaceMembership.workspace_id)
|
||||
.filter(WorkspaceMembership.user_id == auth.user.id)
|
||||
.order_by(WorkspaceMembership.workspace_id)
|
||||
)
|
||||
if auth.is_gated:
|
||||
stmt = stmt.filter(SearchSpace.api_access_enabled == True) # noqa: E712
|
||||
stmt = stmt.filter(Workspace.api_access_enabled == True) # noqa: E712
|
||||
|
||||
result = await session.execute(stmt)
|
||||
return list(result.scalars().all())
|
||||
|
|
@ -105,57 +105,57 @@ async def get_allowed_read_space_ids(
|
|||
async def _enforce_api_access_gate(
|
||||
session: AsyncSession,
|
||||
auth: AuthContext,
|
||||
search_space_id: int,
|
||||
search_space: SearchSpace | None = None,
|
||||
) -> SearchSpace:
|
||||
if search_space is None:
|
||||
workspace_id: int,
|
||||
workspace: Workspace | None = None,
|
||||
) -> Workspace:
|
||||
if workspace is None:
|
||||
result = await session.execute(
|
||||
select(SearchSpace).filter(SearchSpace.id == search_space_id)
|
||||
select(Workspace).filter(Workspace.id == workspace_id)
|
||||
)
|
||||
search_space = result.scalars().first()
|
||||
workspace = result.scalars().first()
|
||||
|
||||
if not search_space:
|
||||
raise HTTPException(status_code=404, detail="Search space not found")
|
||||
if not workspace:
|
||||
raise HTTPException(status_code=404, detail="Workspace not found")
|
||||
|
||||
if auth.is_gated and not search_space.api_access_enabled:
|
||||
if auth.is_gated and not workspace.api_access_enabled:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="API access is not enabled for this search space.",
|
||||
detail="API access is not enabled for this workspace.",
|
||||
)
|
||||
|
||||
return search_space
|
||||
return workspace
|
||||
|
||||
|
||||
async def check_permission(
|
||||
session: AsyncSession,
|
||||
auth: AuthContext,
|
||||
search_space_id: int,
|
||||
workspace_id: int,
|
||||
required_permission: str,
|
||||
error_message: str = "You don't have permission to perform this action",
|
||||
) -> SearchSpaceMembership:
|
||||
) -> WorkspaceMembership:
|
||||
"""
|
||||
Check if a user has a specific permission in a search space.
|
||||
Check if a user has a specific permission in a workspace.
|
||||
Raises HTTPException if permission is denied.
|
||||
|
||||
Args:
|
||||
session: Database session
|
||||
user: User object
|
||||
search_space_id: Search space ID
|
||||
workspace_id: Workspace ID
|
||||
required_permission: Permission string to check
|
||||
error_message: Custom error message for permission denied
|
||||
|
||||
Returns:
|
||||
SearchSpaceMembership if permission granted
|
||||
WorkspaceMembership if permission granted
|
||||
|
||||
Raises:
|
||||
HTTPException: If user doesn't have access or permission
|
||||
"""
|
||||
membership = await get_user_membership(session, auth.user.id, search_space_id)
|
||||
membership = await get_user_membership(session, auth.user.id, workspace_id)
|
||||
|
||||
if not membership:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="You don't have access to this search space",
|
||||
detail="You don't have access to this workspace",
|
||||
)
|
||||
|
||||
# Get user's permissions
|
||||
|
|
@ -169,110 +169,110 @@ async def check_permission(
|
|||
if not has_permission(permissions, required_permission):
|
||||
raise HTTPException(status_code=403, detail=error_message)
|
||||
|
||||
await _enforce_api_access_gate(session, auth, search_space_id)
|
||||
await _enforce_api_access_gate(session, auth, workspace_id)
|
||||
|
||||
return membership
|
||||
|
||||
|
||||
async def check_search_space_access(
|
||||
async def check_workspace_access(
|
||||
session: AsyncSession,
|
||||
auth: AuthContext,
|
||||
search_space_id: int,
|
||||
) -> SearchSpaceMembership:
|
||||
workspace_id: int,
|
||||
) -> WorkspaceMembership:
|
||||
"""
|
||||
Check if a user has any access to a search space.
|
||||
Check if a user has any access to a workspace.
|
||||
This is used for basic access control (user is a member).
|
||||
|
||||
Args:
|
||||
session: Database session
|
||||
user: User object
|
||||
search_space_id: Search space ID
|
||||
workspace_id: Workspace ID
|
||||
|
||||
Returns:
|
||||
SearchSpaceMembership if user has access
|
||||
WorkspaceMembership if user has access
|
||||
|
||||
Raises:
|
||||
HTTPException: If user doesn't have access
|
||||
"""
|
||||
membership = await get_user_membership(session, auth.user.id, search_space_id)
|
||||
membership = await get_user_membership(session, auth.user.id, workspace_id)
|
||||
|
||||
if not membership:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="You don't have access to this search space",
|
||||
detail="You don't have access to this workspace",
|
||||
)
|
||||
|
||||
await _enforce_api_access_gate(session, auth, search_space_id)
|
||||
await _enforce_api_access_gate(session, auth, workspace_id)
|
||||
|
||||
return membership
|
||||
|
||||
|
||||
async def is_search_space_owner(
|
||||
async def is_workspace_owner(
|
||||
session: AsyncSession,
|
||||
user_id: UUID,
|
||||
search_space_id: int,
|
||||
workspace_id: int,
|
||||
) -> bool:
|
||||
"""
|
||||
Check if a user is the owner of a search space.
|
||||
Check if a user is the owner of a workspace.
|
||||
|
||||
Args:
|
||||
session: Database session
|
||||
user_id: User UUID
|
||||
search_space_id: Search space ID
|
||||
workspace_id: Workspace ID
|
||||
|
||||
Returns:
|
||||
True if user is the owner, False otherwise
|
||||
"""
|
||||
membership = await get_user_membership(session, user_id, search_space_id)
|
||||
membership = await get_user_membership(session, user_id, workspace_id)
|
||||
return membership is not None and membership.is_owner
|
||||
|
||||
|
||||
async def get_search_space_with_access_check(
|
||||
async def get_workspace_with_access_check(
|
||||
session: AsyncSession,
|
||||
auth: AuthContext,
|
||||
search_space_id: int,
|
||||
workspace_id: int,
|
||||
required_permission: str | None = None,
|
||||
) -> tuple[SearchSpace, SearchSpaceMembership]:
|
||||
) -> tuple[Workspace, WorkspaceMembership]:
|
||||
"""
|
||||
Get a search space with access and optional permission check.
|
||||
Get a workspace with access and optional permission check.
|
||||
|
||||
Args:
|
||||
session: Database session
|
||||
user: User object
|
||||
search_space_id: Search space ID
|
||||
workspace_id: Workspace ID
|
||||
required_permission: Optional permission to check
|
||||
|
||||
Returns:
|
||||
Tuple of (SearchSpace, SearchSpaceMembership)
|
||||
Tuple of (Workspace, WorkspaceMembership)
|
||||
|
||||
Raises:
|
||||
HTTPException: If search space not found or user lacks access/permission
|
||||
HTTPException: If workspace not found or user lacks access/permission
|
||||
"""
|
||||
# Get the search space
|
||||
# Get the workspace
|
||||
result = await session.execute(
|
||||
select(SearchSpace).filter(SearchSpace.id == search_space_id)
|
||||
select(Workspace).filter(Workspace.id == workspace_id)
|
||||
)
|
||||
search_space = result.scalars().first()
|
||||
workspace = result.scalars().first()
|
||||
|
||||
if not search_space:
|
||||
raise HTTPException(status_code=404, detail="Search space not found")
|
||||
if not workspace:
|
||||
raise HTTPException(status_code=404, detail="Workspace not found")
|
||||
|
||||
# Check access
|
||||
if required_permission:
|
||||
membership = await check_permission(
|
||||
session, auth, search_space_id, required_permission
|
||||
session, auth, workspace_id, required_permission
|
||||
)
|
||||
else:
|
||||
membership = await check_search_space_access(session, auth, search_space_id)
|
||||
membership = await check_workspace_access(session, auth, workspace_id)
|
||||
|
||||
await _enforce_api_access_gate(session, auth, search_space_id, search_space)
|
||||
await _enforce_api_access_gate(session, auth, workspace_id, workspace)
|
||||
|
||||
return search_space, membership
|
||||
return workspace, membership
|
||||
|
||||
|
||||
def generate_invite_code() -> str:
|
||||
"""
|
||||
Generate a unique invite code for search space invites.
|
||||
Generate a unique invite code for workspace invites.
|
||||
|
||||
Returns:
|
||||
A 32-character URL-safe invite code
|
||||
|
|
@ -282,22 +282,22 @@ def generate_invite_code() -> str:
|
|||
|
||||
async def get_default_role(
|
||||
session: AsyncSession,
|
||||
search_space_id: int,
|
||||
) -> SearchSpaceRole | None:
|
||||
workspace_id: int,
|
||||
) -> WorkspaceRole | None:
|
||||
"""
|
||||
Get the default role for a search space (used when accepting invites without a specific role).
|
||||
Get the default role for a workspace (used when accepting invites without a specific role).
|
||||
|
||||
Args:
|
||||
session: Database session
|
||||
search_space_id: Search space ID
|
||||
workspace_id: Workspace ID
|
||||
|
||||
Returns:
|
||||
Default SearchSpaceRole or None
|
||||
Default WorkspaceRole or None
|
||||
"""
|
||||
result = await session.execute(
|
||||
select(SearchSpaceRole).filter(
|
||||
SearchSpaceRole.search_space_id == search_space_id,
|
||||
SearchSpaceRole.is_default == True, # noqa: E712
|
||||
select(WorkspaceRole).filter(
|
||||
WorkspaceRole.workspace_id == workspace_id,
|
||||
WorkspaceRole.is_default == True, # noqa: E712
|
||||
)
|
||||
)
|
||||
return result.scalars().first()
|
||||
|
|
@ -305,22 +305,22 @@ async def get_default_role(
|
|||
|
||||
async def get_owner_role(
|
||||
session: AsyncSession,
|
||||
search_space_id: int,
|
||||
) -> SearchSpaceRole | None:
|
||||
workspace_id: int,
|
||||
) -> WorkspaceRole | None:
|
||||
"""
|
||||
Get the Owner role for a search space.
|
||||
Get the Owner role for a workspace.
|
||||
|
||||
Args:
|
||||
session: Database session
|
||||
search_space_id: Search space ID
|
||||
workspace_id: Workspace ID
|
||||
|
||||
Returns:
|
||||
Owner SearchSpaceRole or None
|
||||
Owner WorkspaceRole or None
|
||||
"""
|
||||
result = await session.execute(
|
||||
select(SearchSpaceRole).filter(
|
||||
SearchSpaceRole.search_space_id == search_space_id,
|
||||
SearchSpaceRole.name == "Owner",
|
||||
select(WorkspaceRole).filter(
|
||||
WorkspaceRole.workspace_id == workspace_id,
|
||||
WorkspaceRole.name == "Owner",
|
||||
)
|
||||
)
|
||||
return result.scalars().first()
|
||||
|
|
|
|||
|
|
@ -13,59 +13,59 @@ import validators
|
|||
from fastapi import HTTPException
|
||||
|
||||
|
||||
def validate_search_space_id(search_space_id: Any) -> int:
|
||||
def validate_workspace_id(workspace_id: Any) -> int:
|
||||
"""
|
||||
Validate and convert search_space_id to integer.
|
||||
Validate and convert workspace_id to integer.
|
||||
|
||||
Args:
|
||||
search_space_id: The search space ID to validate
|
||||
workspace_id: The workspace ID to validate
|
||||
|
||||
Returns:
|
||||
int: Validated search space ID
|
||||
int: Validated workspace ID
|
||||
|
||||
Raises:
|
||||
HTTPException: If validation fails
|
||||
"""
|
||||
if search_space_id is None:
|
||||
raise HTTPException(status_code=400, detail="search_space_id is required")
|
||||
if workspace_id is None:
|
||||
raise HTTPException(status_code=400, detail="workspace_id is required")
|
||||
|
||||
if isinstance(search_space_id, bool):
|
||||
if isinstance(workspace_id, bool):
|
||||
raise HTTPException(
|
||||
status_code=400, detail="search_space_id must be an integer, not a boolean"
|
||||
status_code=400, detail="workspace_id must be an integer, not a boolean"
|
||||
)
|
||||
|
||||
if isinstance(search_space_id, int):
|
||||
if search_space_id <= 0:
|
||||
if isinstance(workspace_id, int):
|
||||
if workspace_id <= 0:
|
||||
raise HTTPException(
|
||||
status_code=400, detail="search_space_id must be a positive integer"
|
||||
status_code=400, detail="workspace_id must be a positive integer"
|
||||
)
|
||||
return search_space_id
|
||||
return workspace_id
|
||||
|
||||
if isinstance(search_space_id, str):
|
||||
if isinstance(workspace_id, str):
|
||||
# Check if it's a valid integer string
|
||||
if not search_space_id.strip():
|
||||
if not workspace_id.strip():
|
||||
raise HTTPException(
|
||||
status_code=400, detail="search_space_id cannot be empty"
|
||||
status_code=400, detail="workspace_id cannot be empty"
|
||||
)
|
||||
|
||||
# Check for valid integer format (no leading zeros, no decimal points)
|
||||
if not re.match(r"^[1-9]\d*$", search_space_id.strip()):
|
||||
if not re.match(r"^[1-9]\d*$", workspace_id.strip()):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="search_space_id must be a valid positive integer",
|
||||
detail="workspace_id must be a valid positive integer",
|
||||
)
|
||||
|
||||
value = int(search_space_id.strip())
|
||||
value = int(workspace_id.strip())
|
||||
# Regex already guarantees value > 0, but check retained for clarity
|
||||
if value <= 0:
|
||||
raise HTTPException(
|
||||
status_code=400, detail="search_space_id must be a positive integer"
|
||||
status_code=400, detail="workspace_id must be a positive integer"
|
||||
)
|
||||
return value
|
||||
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="search_space_id must be an integer or string representation of an integer",
|
||||
detail="workspace_id must be an integer or string representation of an integer",
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue