feat(billing): add sales_leads and stripe_events tables
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
45
backend/alembic/versions/c982a3fc4bf1_add_stripe_events.py
Normal file
45
backend/alembic/versions/c982a3fc4bf1_add_stripe_events.py
Normal file
@@ -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")
|
||||||
57
backend/alembic/versions/f7da3f93b519_add_sales_leads.py
Normal file
57
backend/alembic/versions/f7da3f93b519_add_sales_leads.py
Normal file
@@ -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")
|
||||||
@@ -64,6 +64,8 @@ from .draft_template import DraftTemplate
|
|||||||
from .account_settings import AccountSettings
|
from .account_settings import AccountSettings
|
||||||
from .oauth_identity import OAuthIdentity # noqa: F401
|
from .oauth_identity import OAuthIdentity # noqa: F401
|
||||||
from .plan_billing import PlanBilling # noqa: F401
|
from .plan_billing import PlanBilling # noqa: F401
|
||||||
|
from .sales_lead import SalesLead # noqa: F401
|
||||||
|
from .stripe_event import StripeEvent # noqa: F401
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"User",
|
"User",
|
||||||
@@ -142,4 +144,6 @@ __all__ = [
|
|||||||
"AccountSettings",
|
"AccountSettings",
|
||||||
"OAuthIdentity",
|
"OAuthIdentity",
|
||||||
"PlanBilling",
|
"PlanBilling",
|
||||||
|
"SalesLead",
|
||||||
|
"StripeEvent",
|
||||||
]
|
]
|
||||||
|
|||||||
28
backend/app/models/sales_lead.py
Normal file
28
backend/app/models/sales_lead.py
Normal file
@@ -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),
|
||||||
|
)
|
||||||
17
backend/app/models/stripe_event.py
Normal file
17
backend/app/models/stripe_event.py
Normal file
@@ -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)
|
||||||
Reference in New Issue
Block a user