feat: add onboarding, branding, and supporting data models, migrations, and schemas

Add onboarding_dismissed and branding columns (logo_data, logo_content_type,
company_display_name) to users and teams models. Create SessionSupportingData
model for attaching text snippets and screenshots to sessions. Add Pydantic
schemas for onboarding status, branding responses, and supporting data CRUD.
Update SessionExport to accept pdf format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-03-16 23:51:42 -04:00
parent ae6b7b3055
commit f16a686fb4
11 changed files with 185 additions and 4 deletions

View File

@@ -39,6 +39,7 @@ from .script_template import ScriptCategory, ScriptTemplate, ScriptGeneration
from .psa_connection import PsaConnection
from .psa_post_log import PsaPostLog
from .psa_member_mapping import PsaMemberMapping
from .supporting_data import SessionSupportingData
__all__ = [
"User",
@@ -92,4 +93,5 @@ __all__ = [
"PsaConnection",
"PsaPostLog",
"PsaMemberMapping",
"SessionSupportingData",
]

View File

@@ -82,6 +82,7 @@ class Session(Base):
assigned_to: Mapped[Optional["User"]] = relationship("User", foreign_keys=[assigned_to_id])
attachments: Mapped[list["Attachment"]] = relationship("Attachment", back_populates="session")
shares: Mapped[list["SessionShare"]] = relationship("SessionShare", back_populates="session", cascade="all, delete-orphan")
supporting_data = relationship("SessionSupportingData", back_populates="session", cascade="all, delete-orphan", order_by="SessionSupportingData.sort_order")
# PSA ticket link
psa_ticket_id: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)

View File

@@ -0,0 +1,25 @@
import uuid
from datetime import datetime, timezone
from typing import Optional
from sqlalchemy import DateTime, Enum, ForeignKey, Integer, String, Text
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.core.database import Base
class SessionSupportingData(Base):
__tablename__ = "session_supporting_data"
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
session_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("sessions.id", ondelete="CASCADE"), nullable=False, index=True)
label: Mapped[str] = mapped_column(String(255), nullable=False)
data_type: Mapped[str] = mapped_column(Enum("text_snippet", "screenshot", name="supporting_data_type"), nullable=False)
content: Mapped[str] = mapped_column(Text, nullable=False)
content_type: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
sort_order: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False)
session = relationship("Session", back_populates="supporting_data")

View File

@@ -1,7 +1,7 @@
import uuid
from datetime import datetime, timezone
from typing import TYPE_CHECKING
from sqlalchemy import String, DateTime
from typing import Optional, TYPE_CHECKING
from sqlalchemy import String, DateTime, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.dialects.postgresql import UUID
from app.core.database import Base
@@ -23,6 +23,12 @@ class Team(Base):
default=uuid.uuid4
)
name: Mapped[str] = mapped_column(String(255), nullable=False)
# Branding
logo_data: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
logo_content_type: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
company_display_name: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc)

View File

@@ -1,7 +1,7 @@
import uuid
from datetime import datetime, timezone
from typing import Optional, TYPE_CHECKING
from sqlalchemy import String, DateTime, ForeignKey, Boolean, CheckConstraint
from sqlalchemy import String, DateTime, ForeignKey, Boolean, CheckConstraint, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.dialects.postgresql import UUID
from app.core.database import Base
@@ -73,6 +73,15 @@ class User(Base):
job_title: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
timezone: Mapped[str] = mapped_column(String(100), nullable=False, default="UTC", server_default="UTC")
avatar_url: Mapped[Optional[str]] = mapped_column(String(500), nullable=True)
# Onboarding
onboarding_dismissed: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False, server_default="false")
# Branding (solo pros without a team)
logo_data: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
logo_content_type: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
company_display_name: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
email_verified_at: Mapped[Optional[datetime]] = mapped_column(
DateTime(timezone=True), nullable=True
)