from __future__ import annotations import uuid from datetime import datetime from typing import Any from sqlalchemy import ( Boolean, DateTime, ForeignKey, Index, Integer, JSON, String, Text, UniqueConstraint, ) from sqlalchemy.orm import Mapped, mapped_column, relationship from govoplan_core.db.base import Base, TimestampMixin, utcnow def new_uuid() -> str: return str(uuid.uuid4()) class SearchIndexDocument(Base, TimestampMixin): __tablename__ = "search_index_documents" __table_args__ = ( UniqueConstraint( "tenant_id", "module_id", "resource_type", "resource_id", name="uq_search_document_resource", ), Index( "ix_search_document_scope", "tenant_id", "module_id", "resource_type", ), Index("ix_search_document_visibility", "tenant_id", "visibility"), Index( "ix_search_document_provider", "tenant_id", "provider_id", "resource_type", "active", ), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) module_id: Mapped[str] = mapped_column(String(100), nullable=False, index=True) provider_id: Mapped[str] = mapped_column( String(200), nullable=False, index=True, ) resource_type: Mapped[str] = mapped_column( String(100), nullable=False, index=True ) resource_id: Mapped[str] = mapped_column( String(255), nullable=False, index=True ) title: Mapped[str] = mapped_column(String(500), nullable=False) summary: Mapped[str | None] = mapped_column(Text, nullable=True) body: Mapped[str | None] = mapped_column(Text, nullable=True) keywords: Mapped[list[str]] = mapped_column(JSON, default=list, nullable=False) search_text: Mapped[str] = mapped_column(Text, nullable=False) url: Mapped[str] = mapped_column(String(1500), nullable=False) visibility: Mapped[str] = mapped_column( String(20), default="restricted", nullable=False ) external_reference: Mapped[dict[str, Any] | None] = mapped_column( JSON, nullable=True ) metadata_: Mapped[dict[str, Any]] = mapped_column( "metadata", JSON, default=dict, nullable=False ) content_hash: Mapped[str] = mapped_column(String(64), nullable=False) source_revision: Mapped[str] = mapped_column( String(255), nullable=False, ) change_cursor: Mapped[str | None] = mapped_column( String(500), nullable=True, ) source_updated_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) language: Mapped[str] = mapped_column( String(32), default="simple", nullable=False, ) index_version: Mapped[int] = mapped_column( Integer, default=1, nullable=False, ) requires_authorization_recheck: Mapped[bool] = mapped_column( Boolean, default=False, nullable=False, ) rebuild_id: Mapped[str | None] = mapped_column( String(36), nullable=True, index=True, ) active: Mapped[bool] = mapped_column( Boolean, default=True, nullable=False, index=True, ) indexed_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False ) acl_tokens: Mapped[list["SearchIndexAclToken"]] = relationship( back_populates="document", cascade="all, delete-orphan", ) class SearchIndexAclToken(Base): __tablename__ = "search_index_acl_tokens" __table_args__ = ( UniqueConstraint( "document_id", "token", name="uq_search_index_acl_document_token", ), Index("ix_search_index_acl_token", "token", "document_id"), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) document_id: Mapped[str] = mapped_column( ForeignKey("search_index_documents.id", ondelete="CASCADE"), nullable=False, index=True, ) token: Mapped[str] = mapped_column(String(500), nullable=False, index=True) document: Mapped[SearchIndexDocument] = relationship( back_populates="acl_tokens" ) class SearchIndexState(Base, TimestampMixin): __tablename__ = "search_index_states" __table_args__ = ( UniqueConstraint( "tenant_id", "provider_id", "resource_type", name="uq_search_index_state_source", ), Index( "ix_search_index_state_status", "tenant_id", "status", ), ) id: Mapped[str] = mapped_column( String(36), primary_key=True, default=new_uuid, ) tenant_id: Mapped[str] = mapped_column( String(36), nullable=False, index=True, ) provider_id: Mapped[str] = mapped_column( String(200), nullable=False, index=True, ) module_id: Mapped[str] = mapped_column( String(100), nullable=False, index=True, ) resource_type: Mapped[str] = mapped_column( String(100), nullable=False, index=True, ) index_version: Mapped[int] = mapped_column( Integer, default=1, nullable=False, ) status: Mapped[str] = mapped_column( String(30), default="idle", nullable=False, index=True, ) rebuild_id: Mapped[str | None] = mapped_column( String(36), nullable=True, ) checkpoint_cursor: Mapped[str | None] = mapped_column( String(500), nullable=True, ) high_watermark: Mapped[str | None] = mapped_column( String(500), nullable=True, ) last_change_cursor: Mapped[str | None] = mapped_column( String(500), nullable=True, ) indexed_documents: Mapped[int] = mapped_column( Integer, default=0, nullable=False, ) rejected_documents: Mapped[int] = mapped_column( Integer, default=0, nullable=False, ) rebuild_started_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) rebuild_completed_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) last_success_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) last_error: Mapped[str | None] = mapped_column(Text, nullable=True) class SearchIndexChangeQueue(Base, TimestampMixin): __tablename__ = "search_index_change_queue" __table_args__ = ( UniqueConstraint( "change_id", name="uq_search_index_change_queue_change", ), Index( "ix_search_index_change_queue_pending", "status", "available_at", "created_at", ), Index( "ix_search_index_change_queue_source", "tenant_id", "provider_id", "resource_type", ), ) id: Mapped[str] = mapped_column( String(36), primary_key=True, default=new_uuid, ) change_id: Mapped[str] = mapped_column( String(255), nullable=False, index=True, ) tenant_id: Mapped[str] = mapped_column( String(36), nullable=False, index=True, ) provider_id: Mapped[str] = mapped_column( String(200), nullable=False, index=True, ) module_id: Mapped[str] = mapped_column( String(100), nullable=False, index=True, ) resource_type: Mapped[str] = mapped_column( String(100), nullable=False, index=True, ) resource_id: Mapped[str] = mapped_column( String(255), nullable=False, index=True, ) kind: Mapped[str] = mapped_column( String(20), nullable=False, ) source_revision: Mapped[str] = mapped_column( String(255), nullable=False, ) source_cursor: Mapped[str] = mapped_column( String(500), nullable=False, ) document_: Mapped[dict[str, Any] | None] = mapped_column( "document", JSON, nullable=True, ) occurred_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) status: Mapped[str] = mapped_column( String(30), default="queued", nullable=False, index=True, ) attempts: Mapped[int] = mapped_column( Integer, default=0, nullable=False, ) available_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=utcnow, nullable=False, index=True, ) processed_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) error: Mapped[str | None] = mapped_column(Text, nullable=True) __all__ = [ "SearchIndexAclToken", "SearchIndexChangeQueue", "SearchIndexDocument", "SearchIndexState", "new_uuid", ]