diff --git a/backend/alembic/env.py b/backend/alembic/env.py index ecf4525d..fbc41435 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -18,6 +18,7 @@ from app.models.survey_response import SurveyResponse from app.models.survey_invite import SurveyInvite from app.models.ai_suggestion import AISuggestion # noqa: F401 from app.models.kb_import import KBImport, KBImportNode # noqa: F401 +from app.models.script_template import ScriptCategory, ScriptTemplate, ScriptGeneration # noqa: F401 from app.core.config import settings # this is the Alembic Config object diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 2103b987..06003af7 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -35,6 +35,7 @@ from .assistant_chat import AssistantChat from .survey_response import SurveyResponse from .survey_invite import SurveyInvite from .kb_import import KBImport, KBImportNode +from .script_template import ScriptCategory, ScriptTemplate, ScriptGeneration __all__ = [ "User", @@ -82,4 +83,7 @@ __all__ = [ "SurveyInvite", "KBImport", "KBImportNode", + "ScriptCategory", + "ScriptTemplate", + "ScriptGeneration", ] diff --git a/backend/app/models/script_template.py b/backend/app/models/script_template.py new file mode 100644 index 00000000..c90c2da7 --- /dev/null +++ b/backend/app/models/script_template.py @@ -0,0 +1,107 @@ +import uuid +from datetime import datetime, timezone +from typing import Optional, TYPE_CHECKING +from sqlalchemy import String, Text, DateTime, ForeignKey, Boolean, Integer, Enum as SAEnum +from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.dialects.postgresql import UUID, JSONB +from app.core.database import Base + +if TYPE_CHECKING: + from app.models.user import User + from app.models.team import Team + from app.models.session import Session + + +class ScriptCategory(Base): + __tablename__ = "script_categories" + + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + name: Mapped[str] = mapped_column(String(100), nullable=False) + slug: Mapped[str] = mapped_column(String(100), nullable=False, unique=True, index=True) + description: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + icon: Mapped[Optional[str]] = mapped_column(String(50), nullable=True) + sort_order: Mapped[int] = mapped_column(Integer, nullable=False, default=0) + is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), default=lambda: datetime.now(timezone.utc) + ) + updated_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + default=lambda: datetime.now(timezone.utc), + onupdate=lambda: datetime.now(timezone.utc), + ) + + templates: Mapped[list["ScriptTemplate"]] = relationship("ScriptTemplate", back_populates="category") + + +class ScriptTemplate(Base): + __tablename__ = "script_templates" + + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + category_id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), ForeignKey("script_categories.id", ondelete="RESTRICT"), nullable=False, index=True + ) + team_id: Mapped[Optional[uuid.UUID]] = mapped_column( + UUID(as_uuid=True), ForeignKey("teams.id", ondelete="CASCADE"), nullable=True, index=True + ) + created_by: Mapped[Optional[uuid.UUID]] = mapped_column( + UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True + ) + name: Mapped[str] = mapped_column(String(200), nullable=False) + slug: Mapped[str] = mapped_column(String(200), nullable=False, index=True) + description: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + use_case: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + script_body: Mapped[str] = mapped_column(Text, nullable=False) + parameters_schema: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict) + default_values: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict) + validation_rules: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict) + tags: Mapped[list] = mapped_column(JSONB, nullable=False, default=list) + complexity: Mapped[str] = mapped_column( + SAEnum("beginner", "intermediate", "advanced", name="script_complexity"), nullable=False, default="beginner" + ) + estimated_runtime: Mapped[Optional[str]] = mapped_column(String(50), nullable=True) + requires_elevation: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) + requires_modules: Mapped[list] = mapped_column(JSONB, nullable=False, default=list) + version: Mapped[int] = mapped_column(Integer, nullable=False, default=1) + is_verified: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) + is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) + usage_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0) + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), default=lambda: datetime.now(timezone.utc) + ) + updated_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + default=lambda: datetime.now(timezone.utc), + onupdate=lambda: datetime.now(timezone.utc), + ) + + category: Mapped["ScriptCategory"] = relationship("ScriptCategory", back_populates="templates") + team: Mapped[Optional["Team"]] = relationship("Team") + creator: Mapped[Optional["User"]] = relationship("User", foreign_keys=[created_by]) + generations: Mapped[list["ScriptGeneration"]] = relationship("ScriptGeneration", back_populates="template") + + +class ScriptGeneration(Base): + __tablename__ = "script_generations" + + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + template_id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), ForeignKey("script_templates.id", ondelete="RESTRICT"), nullable=False, index=True + ) + user_id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True + ) + team_id: Mapped[Optional[uuid.UUID]] = mapped_column( + UUID(as_uuid=True), ForeignKey("teams.id", ondelete="SET NULL"), nullable=True, index=True + ) + session_id: Mapped[Optional[uuid.UUID]] = mapped_column( + UUID(as_uuid=True), ForeignKey("sessions.id", ondelete="SET NULL"), nullable=True, index=True + ) + parameters_used: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict) + generated_script: Mapped[str] = mapped_column(Text, nullable=False) + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), default=lambda: datetime.now(timezone.utc) + ) + + template: Mapped["ScriptTemplate"] = relationship("ScriptTemplate", back_populates="generations") + user: Mapped["User"] = relationship("User")