diff --git a/backend/alembic/versions/c982a3fc4bf1_add_stripe_events.py b/backend/alembic/versions/c982a3fc4bf1_add_stripe_events.py new file mode 100644 index 00000000..6deb419a --- /dev/null +++ b/backend/alembic/versions/c982a3fc4bf1_add_stripe_events.py @@ -0,0 +1,45 @@ +"""add stripe_events + +Revision ID: c982a3fc4bf1 +Revises: f7da3f93b519 +Create Date: 2026-05-06 07:32:08.027633 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects.postgresql import JSONB + + +# revision identifiers, used by Alembic. +revision: str = 'c982a3fc4bf1' +down_revision: Union[str, None] = 'f7da3f93b519' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.create_table( + "stripe_events", + sa.Column("id", sa.String(length=255), primary_key=True, nullable=False), + sa.Column("event_type", sa.String(length=100), nullable=False), + sa.Column( + "processed_at", + sa.DateTime(timezone=True), + nullable=False, + server_default=sa.func.now(), + ), + sa.Column( + "payload_excerpt", + JSONB, + nullable=False, + server_default=sa.text("'{}'::jsonb"), + ), + ) + op.create_index("ix_stripe_events_event_type", "stripe_events", ["event_type"]) + + +def downgrade() -> None: + op.drop_index("ix_stripe_events_event_type", table_name="stripe_events") + op.drop_table("stripe_events") diff --git a/backend/alembic/versions/f7da3f93b519_add_sales_leads.py b/backend/alembic/versions/f7da3f93b519_add_sales_leads.py new file mode 100644 index 00000000..154a4db0 --- /dev/null +++ b/backend/alembic/versions/f7da3f93b519_add_sales_leads.py @@ -0,0 +1,57 @@ +"""add sales_leads + +Revision ID: f7da3f93b519 +Revises: f236a91224d0 +Create Date: 2026-05-06 07:31:39.533305 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects.postgresql import UUID + + +# revision identifiers, used by Alembic. +revision: str = 'f7da3f93b519' +down_revision: Union[str, None] = 'f236a91224d0' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.create_table( + "sales_leads", + sa.Column("id", UUID(as_uuid=True), primary_key=True, nullable=False), + sa.Column("email", sa.String(length=255), nullable=False), + sa.Column("name", sa.String(length=255), nullable=False), + sa.Column("company", sa.String(length=255), nullable=False), + sa.Column("team_size", sa.String(length=20), nullable=True), + sa.Column("message", sa.Text(), nullable=True), + sa.Column("source", sa.String(length=50), nullable=False), + sa.Column("posthog_distinct_id", sa.String(length=255), nullable=True), + sa.Column( + "status", + sa.String(length=20), + nullable=False, + server_default=sa.text("'new'"), + ), + sa.Column( + "created_at", + sa.DateTime(timezone=True), + nullable=False, + server_default=sa.func.now(), + ), + sa.Column( + "updated_at", + sa.DateTime(timezone=True), + nullable=False, + server_default=sa.func.now(), + ), + ) + op.create_index("ix_sales_leads_email", "sales_leads", ["email"]) + + +def downgrade() -> None: + op.drop_index("ix_sales_leads_email", table_name="sales_leads") + op.drop_table("sales_leads") diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py index 47e3ca3a..e1c90f7c 100644 --- a/backend/app/models/__init__.py +++ b/backend/app/models/__init__.py @@ -64,6 +64,8 @@ from .draft_template import DraftTemplate from .account_settings import AccountSettings from .oauth_identity import OAuthIdentity # noqa: F401 from .plan_billing import PlanBilling # noqa: F401 +from .sales_lead import SalesLead # noqa: F401 +from .stripe_event import StripeEvent # noqa: F401 __all__ = [ "User", @@ -142,4 +144,6 @@ __all__ = [ "AccountSettings", "OAuthIdentity", "PlanBilling", + "SalesLead", + "StripeEvent", ] diff --git a/backend/app/models/sales_lead.py b/backend/app/models/sales_lead.py new file mode 100644 index 00000000..f9ffc071 --- /dev/null +++ b/backend/app/models/sales_lead.py @@ -0,0 +1,28 @@ +import uuid +from datetime import datetime, timezone +from typing import Optional +from sqlalchemy import String, DateTime, Text, Index +from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy.dialects.postgresql import UUID +from app.core.database import Base + + +class SalesLead(Base): + __tablename__ = "sales_leads" + __table_args__ = (Index("ix_sales_leads_email", "email"),) + + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + email: Mapped[str] = mapped_column(String(255), nullable=False) + name: Mapped[str] = mapped_column(String(255), nullable=False) + company: Mapped[str] = mapped_column(String(255), nullable=False) + team_size: Mapped[Optional[str]] = mapped_column(String(20), nullable=True) + message: Mapped[Optional[str]] = mapped_column(Text, nullable=True) + source: Mapped[str] = mapped_column(String(50), nullable=False) + posthog_distinct_id: Mapped[Optional[str]] = mapped_column(String(255), nullable=True) + status: Mapped[str] = mapped_column(String(20), nullable=False, default="new") + 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), + ) diff --git a/backend/app/models/stripe_event.py b/backend/app/models/stripe_event.py new file mode 100644 index 00000000..d362d814 --- /dev/null +++ b/backend/app/models/stripe_event.py @@ -0,0 +1,17 @@ +from datetime import datetime, timezone +from sqlalchemy import String, DateTime, Index +from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy.dialects.postgresql import JSONB +from app.core.database import Base + + +class StripeEvent(Base): + __tablename__ = "stripe_events" + __table_args__ = (Index("ix_stripe_events_event_type", "event_type"),) + + id: Mapped[str] = mapped_column(String(255), primary_key=True) # Stripe event id + event_type: Mapped[str] = mapped_column(String(100), nullable=False) + processed_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), default=lambda: datetime.now(timezone.utc) + ) + payload_excerpt: Mapped[dict] = mapped_column(JSONB, nullable=False, default=dict)