79 lines
2.3 KiB
Python
79 lines
2.3 KiB
Python
"""Activity timeline ORM model and schemas."""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from enum import StrEnum
|
|
from typing import Any
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
from sqlalchemy import DateTime, Enum as SqlEnum, ForeignKey, Integer, func, text
|
|
from sqlalchemy.dialects.postgresql import JSONB
|
|
from sqlalchemy.types import JSON as GenericJSON, TypeDecorator
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from app.models.base import Base
|
|
|
|
|
|
class ActivityType(StrEnum):
|
|
COMMENT = "comment"
|
|
STATUS_CHANGED = "status_changed"
|
|
STAGE_CHANGED = "stage_changed"
|
|
TASK_CREATED = "task_created"
|
|
SYSTEM = "system"
|
|
|
|
|
|
class JSONBCompat(TypeDecorator):
|
|
"""Uses JSONB on Postgres and plain JSON elsewhere for testability."""
|
|
|
|
impl = JSONB
|
|
cache_ok = True
|
|
|
|
def load_dialect_impl(self, dialect): # type: ignore[override]
|
|
if dialect.name == "sqlite":
|
|
from sqlalchemy.dialects.sqlite import JSON as SQLiteJSON # local import
|
|
|
|
return dialect.type_descriptor(SQLiteJSON())
|
|
return dialect.type_descriptor(JSONB())
|
|
|
|
|
|
class Activity(Base):
|
|
"""Represents a timeline event for a deal."""
|
|
|
|
__tablename__ = "activities"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
deal_id: Mapped[int] = mapped_column(ForeignKey("deals.id", ondelete="CASCADE"))
|
|
author_id: Mapped[int | None] = mapped_column(
|
|
ForeignKey("users.id", ondelete="SET NULL"), nullable=True
|
|
)
|
|
type: Mapped[ActivityType] = mapped_column(SqlEnum(ActivityType, name="activity_type"), nullable=False)
|
|
payload: Mapped[dict[str, Any]] = mapped_column(
|
|
JSONBCompat().with_variant(GenericJSON(), "sqlite"),
|
|
nullable=False,
|
|
server_default=text("'{}'"),
|
|
)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), server_default=func.now(), nullable=False
|
|
)
|
|
|
|
deal = relationship("Deal", back_populates="activities")
|
|
author = relationship("User", back_populates="activities")
|
|
|
|
|
|
class ActivityBase(BaseModel):
|
|
deal_id: int
|
|
author_id: int | None = None
|
|
type: ActivityType
|
|
payload: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
class ActivityCreate(ActivityBase):
|
|
pass
|
|
|
|
|
|
class ActivityRead(ActivityBase):
|
|
id: int
|
|
created_at: datetime
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|