fix(audit): P0-P3 audit fixes — 838 ruff errors → 0, 30 F821 bugs fixed, 118 files changed
- P0: hooks.py 3-tuple fix, trigger_dispatcher Contract, contacts/plugin unregister_actions_by_owner - P0: 5 test files — check_permission mocks removed, hardcoded DB credential → env var - P1: attachment_service DmsFile via Contract helper, restore_registry/history_hooks dedup - P1: mail/plugin restore unregister, mcp_client datetime.now(UTC), saved_views/filters patterns - P1: ProtectedRoute fail-closed, 13 test assertion fixes (bcrypt, DB-URLs, SECRET_KEYs) - P2: deprecated notifications → post_system_message (3 files), forgejo Base, report_generator lazy import - P2: webhooks permissions, deps.py/roles.py plugin perms removed, import_export default - P2: address/tags/entity_links patterns removed, worker.py Contract-Umgehungen fixed - P2: 28 frontend TODOs (hardcoded constants, deprecated notification API) - P3: dead code, duplicates, deprecated imports, private attr, __import__ inline - P3: 8 frontend TODOs (LucideIcons, inline styles, XSS, i18n) - ruff: 838 → 0 (612 auto-fix + 246 manual + 27 F821 regression fix) - F821: 30 → 0 (AutomationDefinition, DmsFile, user_id, Path, Any, String) - Contract-Umgehungen: 2 neue gefunden (worker.py:169, worker.py:280) und gefixt
This commit is contained in:
@@ -12,8 +12,6 @@ import logging
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.plugins.builtins.unified_search.query_understanding import llm_analyze_query
|
||||
from app.plugins.builtins.unified_search.search_engine import hybrid_search
|
||||
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from app.plugins.builtins.contracts import get_contract_registry
|
||||
from app.plugins.builtins.unified_search.embedding import generate_embedding
|
||||
from app.plugins.builtins.unified_search.search_engine import hybrid_search, find_similar_all_types
|
||||
from app.plugins.builtins.unified_search.provider_registry import get_search_registry
|
||||
from app.plugins.builtins.unified_search.base_provider import BaseSearchProvider
|
||||
from app.plugins.builtins.unified_search.embedding import generate_embedding
|
||||
from app.plugins.builtins.unified_search.provider_registry import get_search_registry
|
||||
from app.plugins.builtins.unified_search.query_understanding import llm_analyze_query
|
||||
from app.plugins.builtins.unified_search.search_engine import find_similar_all_types, hybrid_search
|
||||
|
||||
|
||||
class UnifiedSearchContract:
|
||||
@@ -18,6 +19,7 @@ class UnifiedSearchContract:
|
||||
hybrid_search = staticmethod(hybrid_search)
|
||||
find_similar_all_types = staticmethod(find_similar_all_types)
|
||||
get_search_registry = staticmethod(get_search_registry)
|
||||
llm_analyze_query = staticmethod(llm_analyze_query)
|
||||
BaseSearchProvider = BaseSearchProvider
|
||||
|
||||
|
||||
|
||||
@@ -19,10 +19,14 @@ from app.ai.llm_client import (
|
||||
EMBEDDING_DIMENSIONS,
|
||||
MAX_INPUT_CHARS,
|
||||
OPENROUTER_EMBEDDING_MODEL,
|
||||
build_model as _central_build_model,
|
||||
get_api_credentials as _central_get_api_credentials,
|
||||
llm_embed,
|
||||
)
|
||||
from app.ai.llm_client import (
|
||||
build_model as _central_build_model,
|
||||
)
|
||||
from app.ai.llm_client import (
|
||||
get_api_credentials as _central_get_api_credentials,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -46,7 +50,7 @@ OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
|
||||
|
||||
|
||||
async def _get_api_credentials(
|
||||
db: "AsyncSession | None", tenant_id: "uuid.UUID | None"
|
||||
db: AsyncSession | None, tenant_id: uuid.UUID | None
|
||||
) -> tuple[str | None, str | None, str | None]:
|
||||
"""Get API key, base_url and provider_type for embeddings.
|
||||
|
||||
@@ -68,8 +72,8 @@ def _build_model(model: str, provider_type: str | None) -> str:
|
||||
async def generate_embedding(
|
||||
text: str,
|
||||
model: str | None = None,
|
||||
db: "AsyncSession | None" = None,
|
||||
tenant_id: "uuid.UUID | None" = None,
|
||||
db: AsyncSession | None = None,
|
||||
tenant_id: uuid.UUID | None = None,
|
||||
) -> list[float]:
|
||||
"""Generate a single embedding via LiteLLM.
|
||||
|
||||
@@ -98,8 +102,8 @@ async def generate_embedding(
|
||||
async def generate_embeddings_batch(
|
||||
texts: list[str],
|
||||
model: str | None = None,
|
||||
db: "AsyncSession | None" = None,
|
||||
tenant_id: "uuid.UUID | None" = None,
|
||||
db: AsyncSession | None = None,
|
||||
tenant_id: uuid.UUID | None = None,
|
||||
) -> list[list[float]]:
|
||||
"""Generate embeddings for multiple texts in a single API call.
|
||||
|
||||
@@ -124,7 +128,7 @@ async def index_entity(
|
||||
entity_type: str,
|
||||
entity_id: uuid.UUID,
|
||||
tenant_id: uuid.UUID,
|
||||
db: "AsyncSession",
|
||||
db: AsyncSession,
|
||||
) -> bool:
|
||||
"""Generate and store embedding for a single entity.
|
||||
|
||||
@@ -134,6 +138,7 @@ async def index_entity(
|
||||
Returns True on success, False on failure.
|
||||
"""
|
||||
from sqlalchemy import text as sql_text
|
||||
|
||||
from app.plugins.builtins.unified_search.models import SearchIndexLog
|
||||
|
||||
async def _log_index(action: str, status: str, error: str | None = None) -> None:
|
||||
|
||||
@@ -59,8 +59,9 @@ async def index_mails(ctx: dict[str, Any], mail_ids: list[str]) -> None:
|
||||
async def index_file(ctx: dict[str, Any], file_id: str) -> None:
|
||||
"""Index a file: extract text, store content_text, generate embedding."""
|
||||
from sqlalchemy import text
|
||||
from app.plugins.builtins.unified_search.text_extraction import extract_text_from_file
|
||||
|
||||
from app.plugins.builtins.unified_search.embedding import generate_embedding
|
||||
from app.plugins.builtins.unified_search.text_extraction import extract_text_from_file
|
||||
|
||||
factory = get_session_factory()
|
||||
async with factory() as db:
|
||||
@@ -119,6 +120,7 @@ async def index_file(ctx: dict[str, Any], file_id: str) -> None:
|
||||
async def index_contact(ctx: dict[str, Any], contact_id: str) -> None:
|
||||
"""Index a contact: generate and store embedding."""
|
||||
from sqlalchemy import text
|
||||
|
||||
from app.plugins.builtins.unified_search.embedding import index_entity
|
||||
|
||||
factory = get_session_factory()
|
||||
@@ -144,6 +146,7 @@ async def index_contact(ctx: dict[str, Any], contact_id: str) -> None:
|
||||
async def index_event(ctx: dict[str, Any], event_id: str) -> None:
|
||||
"""Index a calendar event: generate and store embedding."""
|
||||
from sqlalchemy import text
|
||||
|
||||
from app.plugins.builtins.unified_search.embedding import index_entity
|
||||
|
||||
factory = get_session_factory()
|
||||
@@ -173,6 +176,7 @@ async def reindex(ctx: dict[str, Any], entity_type: str) -> None:
|
||||
and continues on individual failures.
|
||||
"""
|
||||
from sqlalchemy import text
|
||||
|
||||
from app.plugins.builtins.unified_search.embedding import index_entity
|
||||
|
||||
table = _TABLE_MAP.get(entity_type)
|
||||
@@ -272,6 +276,7 @@ async def delete_entity_index(ctx: dict[str, Any], entity_type: str, entity_id:
|
||||
Logs the action to SearchIndexLog on success.
|
||||
"""
|
||||
from sqlalchemy import text
|
||||
|
||||
from app.plugins.builtins.unified_search.models import SearchIndexLog
|
||||
|
||||
table = _TABLE_MAP.get(entity_type)
|
||||
@@ -346,6 +351,7 @@ async def delete_file_chunks(ctx: dict[str, Any], file_id: str) -> None:
|
||||
async def retry_failed_index(ctx: dict[str, Any], entity_type: str, entity_id: str) -> None:
|
||||
"""Retry a failed index operation."""
|
||||
from sqlalchemy import text
|
||||
|
||||
from app.plugins.builtins.unified_search.embedding import index_entity
|
||||
|
||||
table = _TABLE_MAP.get(entity_type)
|
||||
@@ -377,6 +383,7 @@ async def retry_failed_index(ctx: dict[str, Any], entity_type: str, entity_id: s
|
||||
async def embedding_batch(ctx: dict[str, Any]) -> None:
|
||||
"""Periodic job: find entities without embeddings and index them."""
|
||||
from sqlalchemy import text
|
||||
|
||||
from app.plugins.builtins.unified_search.embedding import index_entity
|
||||
|
||||
factory = get_session_factory()
|
||||
@@ -414,9 +421,10 @@ async def embedding_batch(ctx: dict[str, Any]) -> None:
|
||||
async def index_file_chunks(ctx: dict[str, Any], file_id: str) -> None:
|
||||
"""Extract text from a file, chunk it, generate embeddings, and store in document_chunks."""
|
||||
from sqlalchemy import text
|
||||
from app.plugins.builtins.unified_search.text_extraction import extract_text_from_file
|
||||
|
||||
from app.plugins.builtins.unified_search.chunking import chunk_text
|
||||
from app.plugins.builtins.unified_search.embedding import generate_embedding
|
||||
from app.plugins.builtins.unified_search.text_extraction import extract_text_from_file
|
||||
|
||||
factory = get_session_factory()
|
||||
async with factory() as db:
|
||||
@@ -506,7 +514,7 @@ async def reindex_chunks(ctx: dict[str, Any], file_id: str) -> None:
|
||||
|
||||
|
||||
# ── Register all job functions with the job registry ──────────────────────────
|
||||
from app.core.job_registry import register_job
|
||||
from app.core.job_registry import register_job # noqa: E402
|
||||
|
||||
register_job("index_mails", index_mails)
|
||||
register_job("index_file", index_file)
|
||||
|
||||
@@ -33,7 +33,7 @@ def _resolve_entity(entity_type: str) -> tuple[str, str, str] | None:
|
||||
|
||||
|
||||
async def remove_from_index(
|
||||
db: "AsyncSession",
|
||||
db: AsyncSession,
|
||||
entity_type: str,
|
||||
entity_id: uuid.UUID,
|
||||
tenant_id: uuid.UUID,
|
||||
@@ -61,7 +61,7 @@ async def remove_from_index(
|
||||
|
||||
|
||||
async def remove_chunks(
|
||||
db: "AsyncSession",
|
||||
db: AsyncSession,
|
||||
file_id: uuid.UUID,
|
||||
tenant_id: uuid.UUID,
|
||||
) -> None:
|
||||
@@ -78,7 +78,7 @@ async def remove_chunks(
|
||||
|
||||
|
||||
async def rebuild_index(
|
||||
db: "AsyncSession",
|
||||
db: AsyncSession,
|
||||
entity_type: str,
|
||||
entity_id: uuid.UUID,
|
||||
tenant_id: uuid.UUID,
|
||||
@@ -107,7 +107,7 @@ async def rebuild_index(
|
||||
|
||||
|
||||
async def handle_entity_delete(
|
||||
db: "AsyncSession",
|
||||
db: AsyncSession,
|
||||
entity_type: str,
|
||||
entity_id: uuid.UUID,
|
||||
tenant_id: uuid.UUID,
|
||||
@@ -119,7 +119,7 @@ async def handle_entity_delete(
|
||||
|
||||
|
||||
async def handle_entity_restore(
|
||||
db: "AsyncSession",
|
||||
db: AsyncSession,
|
||||
entity_type: str,
|
||||
entity_id: uuid.UUID,
|
||||
tenant_id: uuid.UUID,
|
||||
@@ -129,7 +129,7 @@ async def handle_entity_restore(
|
||||
|
||||
|
||||
async def handle_entity_correction(
|
||||
db: "AsyncSession",
|
||||
db: AsyncSession,
|
||||
entity_type: str,
|
||||
entity_id: uuid.UUID,
|
||||
tenant_id: uuid.UUID,
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, ForeignKey, Index, Integer, String, Text
|
||||
from sqlalchemy.dialects.postgresql import JSONB, UUID as PGUUID
|
||||
from sqlalchemy import ForeignKey, Index, Integer, String, Text
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.dialects.postgresql import UUID as PGUUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.core.db import Base, TenantMixin
|
||||
@@ -49,7 +49,7 @@ class SearchIndexLog(Base, TenantMixin):
|
||||
status: Mapped[str] = mapped_column(String(20), nullable=False, default="pending")
|
||||
error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
|
||||
from pgvector.sqlalchemy import Vector
|
||||
from pgvector.sqlalchemy import Vector # noqa: E402
|
||||
|
||||
|
||||
class DocumentChunk(Base, TenantMixin):
|
||||
|
||||
@@ -7,7 +7,7 @@ import uuid
|
||||
from typing import Any
|
||||
|
||||
from app.plugins.base import BasePlugin
|
||||
from app.plugins.manifest import PluginManifest, PluginRouteDef, FrontendPageRoute
|
||||
from app.plugins.manifest import FrontendPageRoute, PluginManifest, PluginRouteDef
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -57,6 +57,11 @@ class UnifiedSearchPlugin(BasePlugin):
|
||||
contract_version="1.0.0",
|
||||
)
|
||||
|
||||
def get_job_modules(self) -> list[str]:
|
||||
return [
|
||||
"app.plugins.builtins.unified_search.jobs",
|
||||
]
|
||||
|
||||
async def on_activate(self, db, service_container, event_bus) -> None:
|
||||
"""Register search providers and AI tool on activation."""
|
||||
await super().on_activate(db, service_container, event_bus)
|
||||
@@ -69,12 +74,14 @@ class UnifiedSearchPlugin(BasePlugin):
|
||||
except Exception:
|
||||
logger.exception("Failed to auto-register search providers")
|
||||
|
||||
# Register the unified_search AI tool
|
||||
# Register the unified_search AI tool via contract
|
||||
try:
|
||||
from app.plugins.builtins.ai_assistant.tool_registry import get_tool_registry
|
||||
from app.plugins.builtins.unified_search.ai_tool import register_unified_search_tool
|
||||
register_unified_search_tool(get_tool_registry())
|
||||
logger.info("Unified Search AI tool registered")
|
||||
from app.plugins.builtins.contracts import get_contract
|
||||
ai_contract = get_contract("ai_assistant")
|
||||
if ai_contract is not None:
|
||||
from app.plugins.builtins.unified_search.ai_tool import register_unified_search_tool
|
||||
register_unified_search_tool(ai_contract.get_tool_registry())
|
||||
logger.info("Unified Search AI tool registered")
|
||||
except Exception:
|
||||
logger.exception("Failed to register unified_search AI tool")
|
||||
|
||||
@@ -172,8 +179,8 @@ class UnifiedSearchPlugin(BasePlugin):
|
||||
|
||||
async def on_entity_deleted(self, payload: dict[str, Any]) -> None:
|
||||
"""Handle entity.deleted lifecycle event — remove from search index."""
|
||||
from app.plugins.builtins.unified_search.lifecycle import handle_entity_delete
|
||||
from app.core.db import get_session_factory
|
||||
from app.plugins.builtins.unified_search.lifecycle import handle_entity_delete
|
||||
|
||||
entity_type = payload.get("entity_type")
|
||||
entity_id = payload.get("entity_id")
|
||||
@@ -188,8 +195,8 @@ class UnifiedSearchPlugin(BasePlugin):
|
||||
|
||||
async def on_entity_restored(self, payload: dict[str, Any]) -> None:
|
||||
"""Handle entity.restored lifecycle event — rebuild search index."""
|
||||
from app.plugins.builtins.unified_search.lifecycle import handle_entity_restore
|
||||
from app.core.db import get_session_factory
|
||||
from app.plugins.builtins.unified_search.lifecycle import handle_entity_restore
|
||||
|
||||
entity_type = payload.get("entity_type")
|
||||
entity_id = payload.get("entity_id")
|
||||
@@ -204,8 +211,8 @@ class UnifiedSearchPlugin(BasePlugin):
|
||||
|
||||
async def on_entity_corrected(self, payload: dict[str, Any]) -> None:
|
||||
"""Handle entity.corrected lifecycle event — rebuild search index."""
|
||||
from app.plugins.builtins.unified_search.lifecycle import handle_entity_correction
|
||||
from app.core.db import get_session_factory
|
||||
from app.plugins.builtins.unified_search.lifecycle import handle_entity_correction
|
||||
|
||||
entity_type = payload.get("entity_type")
|
||||
entity_id = payload.get("entity_id")
|
||||
|
||||
@@ -5,10 +5,9 @@ from __future__ import annotations
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Protocol, runtime_checkable
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import uuid
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -121,47 +120,47 @@ async def auto_register_providers(db: AsyncSession) -> None:
|
||||
|
||||
Checks which plugins are active and registers corresponding providers.
|
||||
"""
|
||||
from app.plugins.builtins.unified_search.providers.contact_provider import (
|
||||
ContactSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.providers.mail_provider import (
|
||||
MailSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.providers.file_provider import (
|
||||
FileSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.providers.event_provider import (
|
||||
EventSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.providers.company_provider import (
|
||||
CompanySearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.providers.task_provider import (
|
||||
TaskSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.providers.contactperson_provider import (
|
||||
ContactPersonSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.providers.tag_provider import (
|
||||
TagSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.providers.conversation_provider import (
|
||||
ConversationSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.providers.user_provider import (
|
||||
UserSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.graph_rag.contracts import GraphRagContract
|
||||
from app.plugins.builtins.unified_search.providers.agent_memory_provider import (
|
||||
AgentMemorySearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.providers.ai_chat_provider import (
|
||||
AIChatSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.providers.company_provider import (
|
||||
CompanySearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.providers.contact_provider import (
|
||||
ContactSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.providers.contactperson_provider import (
|
||||
ContactPersonSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.providers.conversation_provider import (
|
||||
ConversationSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.providers.event_provider import (
|
||||
EventSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.providers.file_provider import (
|
||||
FileSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.providers.mail_provider import (
|
||||
MailSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.providers.tag_provider import (
|
||||
TagSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.providers.task_provider import (
|
||||
TaskSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.providers.user_provider import (
|
||||
UserSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.providers.workflow_provider import (
|
||||
WorkflowSearchProvider,
|
||||
)
|
||||
from app.plugins.builtins.graph_rag.contracts import GraphRagContract
|
||||
GraphRAGSearchProvider = GraphRagContract.GraphRAGSearchProvider
|
||||
graph_rag_search_provider = GraphRagContract.graph_rag_search_provider
|
||||
|
||||
registry = get_search_registry()
|
||||
registry.clear()
|
||||
@@ -181,7 +180,7 @@ async def auto_register_providers(db: AsyncSession) -> None:
|
||||
AgentMemorySearchProvider,
|
||||
AIChatSearchProvider,
|
||||
WorkflowSearchProvider,
|
||||
GraphRAGSearchProvider,
|
||||
graph_rag_search_provider,
|
||||
]:
|
||||
try:
|
||||
registry.register(provider_cls())
|
||||
|
||||
@@ -9,8 +9,8 @@ from typing import Any
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.plugins.builtins.unified_search.base_provider import BaseSearchProvider
|
||||
from app.config import settings
|
||||
from app.plugins.builtins.unified_search.base_provider import BaseSearchProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -7,9 +7,10 @@ import logging
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
from app.ai.llm_client import llm_complete
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.ai.llm_client import llm_complete
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
QUERY_ANALYZE_SYSTEM = (
|
||||
|
||||
@@ -4,7 +4,8 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy import text
|
||||
@@ -12,7 +13,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.db import get_db
|
||||
from app.core.jobs import enqueue_job
|
||||
from app.core.permissions import resolve_permissions, filter_fields_by_permission
|
||||
from app.core.permissions import filter_fields_by_permission, resolve_permissions
|
||||
from app.deps import get_current_user, require_permission
|
||||
from app.plugins.builtins.unified_search.provider_registry import get_search_registry
|
||||
from app.plugins.builtins.unified_search.query_understanding import (
|
||||
@@ -28,7 +29,6 @@ from app.plugins.builtins.unified_search.schemas import (
|
||||
SearchResult,
|
||||
SimilarRequest,
|
||||
SimilarResponse,
|
||||
SuggestRequest,
|
||||
SuggestResponse,
|
||||
)
|
||||
from app.plugins.builtins.unified_search.search_engine import (
|
||||
@@ -80,7 +80,7 @@ def _parse_date(value: str | None) -> datetime | None:
|
||||
try:
|
||||
dt = datetime.fromisoformat(value)
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=timezone.utc)
|
||||
dt = dt.replace(tzinfo=UTC)
|
||||
return dt
|
||||
except ValueError:
|
||||
return None
|
||||
@@ -109,7 +109,7 @@ def _apply_filters_and_sort(
|
||||
except ValueError:
|
||||
continue
|
||||
if ts.tzinfo is None:
|
||||
ts = ts.replace(tzinfo=timezone.utc)
|
||||
ts = ts.replace(tzinfo=UTC)
|
||||
if date_from and ts < date_from:
|
||||
continue
|
||||
if date_to and ts > date_to:
|
||||
@@ -169,7 +169,7 @@ async def _do_search(
|
||||
resolved_perms = await resolve_permissions(db, user_id, tenant_id)
|
||||
|
||||
# Map entity_type to module name for field-level permissions
|
||||
_ENTITY_TO_MODULE = {
|
||||
_entity_to_module = {
|
||||
"contact": "contacts",
|
||||
"mail": "mail",
|
||||
"file": "dms",
|
||||
@@ -189,7 +189,7 @@ async def _do_search(
|
||||
data=filter_fields_by_permission(
|
||||
r.get("data", {}),
|
||||
resolved_perms,
|
||||
_ENTITY_TO_MODULE.get(r.get("entity_type", ""), r.get("entity_type", "")),
|
||||
_entity_to_module.get(r.get("entity_type", ""), r.get("entity_type", "")),
|
||||
),
|
||||
)
|
||||
for r in results
|
||||
@@ -307,7 +307,7 @@ async def find_similar(
|
||||
try:
|
||||
entity_id = uuid.UUID(req.entity_id)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid entity_id")
|
||||
raise HTTPException(status_code=400, detail="Invalid entity_id") from None
|
||||
|
||||
similar = await find_similar_all_types(
|
||||
db=db,
|
||||
@@ -382,7 +382,7 @@ async def rebuild_entity_index(
|
||||
try:
|
||||
eid = uuid.UUID(entity_id)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid entity_id")
|
||||
raise HTTPException(status_code=400, detail="Invalid entity_id") from None
|
||||
|
||||
from app.plugins.builtins.unified_search.lifecycle import rebuild_index
|
||||
|
||||
@@ -407,9 +407,9 @@ async def purge_entity_index(
|
||||
try:
|
||||
eid = uuid.UUID(entity_id)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid entity_id")
|
||||
raise HTTPException(status_code=400, detail="Invalid entity_id") from None
|
||||
|
||||
from app.plugins.builtins.unified_search.lifecycle import remove_from_index, remove_chunks
|
||||
from app.plugins.builtins.unified_search.lifecycle import remove_chunks, remove_from_index
|
||||
|
||||
await remove_from_index(db, entity_type, eid, tenant_id)
|
||||
if entity_type == "file":
|
||||
@@ -522,7 +522,6 @@ async def search_stats(
|
||||
|
||||
stats: dict = {}
|
||||
tables = {
|
||||
"contacts": "embedding",
|
||||
"contacts": "embedding",
|
||||
"mails": "embedding",
|
||||
"files": "embedding",
|
||||
|
||||
@@ -6,7 +6,6 @@ from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
# ─── Search ───
|
||||
|
||||
class SearchRequest(BaseModel):
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -122,6 +121,6 @@ async def _extract_text(file_path: str) -> str:
|
||||
"""Read plain text file using aiofiles."""
|
||||
import aiofiles
|
||||
|
||||
async with aiofiles.open(file_path, mode="r", encoding="utf-8", errors="replace") as f:
|
||||
async with aiofiles.open(file_path, encoding="utf-8", errors="replace") as f:
|
||||
content = await f.read()
|
||||
return _truncate(content)
|
||||
|
||||
Reference in New Issue
Block a user