import enum
from datetime import date, datetime
from datetime import timezone as dt_timezone
from sqlalchemy import (
JSON,
Boolean,
Date,
DateTime,
Float,
ForeignKey,
Index,
Integer,
String,
UniqueConstraint,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .database import Base
[docs]
class PeriodType(str, enum.Enum):
monthly = "monthly"
quarterly = "quarterly"
semiannual = "semiannual"
annual = "annual"
[docs]
class RedemptionType(str, enum.Enum):
binary = "binary"
continuous = "continuous"
[docs]
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
email: Mapped[str] = mapped_column(String, unique=True, index=True, nullable=False)
display_name: Mapped[str] = mapped_column(String, nullable=False)
hashed_password: Mapped[str | None] = mapped_column(String, nullable=True, default=None)
preferred_currency: Mapped[str] = mapped_column(String, default="USD", nullable=False)
timezone: Mapped[str] = mapped_column(String, default="UTC", nullable=False)
notification_preferences: Mapped[dict | None] = mapped_column(JSON, default=dict)
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
password_reset_token: Mapped[str | None] = mapped_column(String, nullable=True, index=True)
password_reset_expires: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
is_verified: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
verification_token: Mapped[str | None] = mapped_column(String, nullable=True, index=True)
verification_token_expires: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime, default=lambda: datetime.now(dt_timezone.utc), nullable=False
)
updated_at: Mapped[datetime] = mapped_column(
DateTime,
default=lambda: datetime.now(dt_timezone.utc),
onupdate=lambda: datetime.now(dt_timezone.utc),
nullable=False,
)
cards: Mapped[list["UserCard"]] = relationship(back_populates="user")
oauth_accounts: Mapped[list["UserOAuthAccount"]] = relationship(back_populates="user", cascade="all, delete-orphan")
[docs]
class UserOAuthAccount(Base):
__tablename__ = "user_oauth_accounts"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id", ondelete="CASCADE"), index=True)
provider: Mapped[str] = mapped_column(String, nullable=False)
provider_user_id: Mapped[str] = mapped_column(String, nullable=False)
provider_email: Mapped[str] = mapped_column(String, nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime, default=lambda: datetime.now(dt_timezone.utc).replace(tzinfo=None), nullable=False
)
user: Mapped["User"] = relationship(back_populates="oauth_accounts")
__table_args__ = (
UniqueConstraint("provider", "provider_user_id", name="uq_provider_user"),
)
[docs]
class CardTemplate(Base):
__tablename__ = "card_templates"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
name: Mapped[str] = mapped_column(String, unique=True, nullable=False)
issuer: Mapped[str] = mapped_column(String, nullable=False)
annual_fee: Mapped[float] = mapped_column(Float, nullable=False)
image_url: Mapped[str | None] = mapped_column(String, nullable=True)
benefits: Mapped[list["BenefitTemplate"]] = relationship(
back_populates="card_template", cascade="all, delete-orphan"
)
user_cards: Mapped[list["UserCard"]] = relationship(
back_populates="card_template"
)
[docs]
class BenefitTemplate(Base):
__tablename__ = "benefit_templates"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
card_template_id: Mapped[int] = mapped_column(
Integer, ForeignKey("card_templates.id", ondelete="CASCADE"), nullable=False
)
name: Mapped[str] = mapped_column(String, nullable=False)
description: Mapped[str | None] = mapped_column(String, nullable=True)
max_value: Mapped[float] = mapped_column(Float, nullable=False)
period_type: Mapped[str] = mapped_column(String, nullable=False)
redemption_type: Mapped[str] = mapped_column(String, nullable=False, default="continuous")
category: Mapped[str] = mapped_column(String, nullable=False)
card_template: Mapped["CardTemplate"] = relationship(back_populates="benefits")
[docs]
class UserCard(Base):
__tablename__ = "user_cards"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_id: Mapped[int] = mapped_column(
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True
)
card_template_id: Mapped[int] = mapped_column(
Integer, ForeignKey("card_templates.id"), nullable=False
)
nickname: Mapped[str | None] = mapped_column(String, nullable=True)
member_since_date: Mapped[date | None] = mapped_column(Date, nullable=True)
renewal_date: Mapped[date | None] = mapped_column(Date, nullable=True)
closed_date: Mapped[date | None] = mapped_column(Date, nullable=True)
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime, default=lambda: datetime.now(dt_timezone.utc), nullable=False
)
user: Mapped["User"] = relationship(back_populates="cards")
card_template: Mapped["CardTemplate"] = relationship(back_populates="user_cards")
usages: Mapped[list["BenefitUsage"]] = relationship(
back_populates="user_card", cascade="all, delete-orphan"
)
benefit_settings: Mapped[list["UserBenefitSetting"]] = relationship(
back_populates="user_card", cascade="all, delete-orphan"
)
[docs]
class UserBenefitSetting(Base):
__tablename__ = "user_benefit_settings"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_card_id: Mapped[int] = mapped_column(
Integer, ForeignKey("user_cards.id", ondelete="CASCADE"), nullable=False
)
benefit_template_id: Mapped[int] = mapped_column(
Integer, ForeignKey("benefit_templates.id", ondelete="CASCADE"), nullable=False
)
perceived_max_value: Mapped[float] = mapped_column(Float, nullable=False)
user_card: Mapped["UserCard"] = relationship(back_populates="benefit_settings")
benefit_template: Mapped["BenefitTemplate"] = relationship()
__table_args__ = (
UniqueConstraint("user_card_id", "benefit_template_id", name="uq_user_benefit_setting"),
)
[docs]
class BenefitUsage(Base):
__tablename__ = "benefit_usages"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_card_id: Mapped[int] = mapped_column(
Integer, ForeignKey("user_cards.id", ondelete="CASCADE"), nullable=False
)
benefit_template_id: Mapped[int] = mapped_column(
Integer, ForeignKey("benefit_templates.id", ondelete="CASCADE"), nullable=False
)
period_start_date: Mapped[date] = mapped_column(Date, nullable=False)
period_end_date: Mapped[date] = mapped_column(Date, nullable=False)
amount_used: Mapped[float] = mapped_column(Float, nullable=False)
notes: Mapped[str | None] = mapped_column(String, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime, default=lambda: datetime.now(dt_timezone.utc), nullable=False
)
user_card: Mapped["UserCard"] = relationship(back_populates="usages")
benefit_template: Mapped["BenefitTemplate"] = relationship()
__table_args__ = (
UniqueConstraint(
"user_card_id", "benefit_template_id", "period_start_date",
name="uq_usage_per_period"
),
)
[docs]
class Feedback(Base):
__tablename__ = "feedback"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_id: Mapped[int] = mapped_column(
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True
)
category: Mapped[str] = mapped_column(String, nullable=False)
message: Mapped[str] = mapped_column(String(1000), nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime, default=lambda: datetime.now(dt_timezone.utc), nullable=False
)
user: Mapped["User"] = relationship()
[docs]
class PushToken(Base):
__tablename__ = "push_tokens"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_id: Mapped[int] = mapped_column(
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True
)
token: Mapped[str] = mapped_column(String(255), unique=True, nullable=False)
device_name: Mapped[str | None] = mapped_column(String(100), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime, default=lambda: datetime.now(dt_timezone.utc), nullable=False
)
user: Mapped["User"] = relationship()
[docs]
class NotificationLog(Base):
__tablename__ = "notification_logs"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_id: Mapped[int] = mapped_column(
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True
)
notification_type: Mapped[str] = mapped_column(String(50), nullable=False)
channel: Mapped[str] = mapped_column(String(10), nullable=False)
reference_key: Mapped[str] = mapped_column(String(200), nullable=False)
sent_at: Mapped[datetime] = mapped_column(
DateTime, default=lambda: datetime.now(dt_timezone.utc), nullable=False
)
user: Mapped["User"] = relationship()
__table_args__ = (
Index(
"ix_notification_logs_dedup",
"user_id",
"notification_type",
"reference_key",
"channel",
),
)
[docs]
class Notification(Base):
__tablename__ = "notifications"
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"))
notification_type: Mapped[str] = mapped_column(String(50))
title: Mapped[str] = mapped_column(String(200))
body: Mapped[str] = mapped_column(String(500))
data: Mapped[dict | None] = mapped_column(JSON, nullable=True)
is_read: Mapped[bool] = mapped_column(default=False)
created_at: Mapped[datetime] = mapped_column(default=lambda: datetime.now(dt_timezone.utc))
user: Mapped["User"] = relationship()
__table_args__ = (
Index('ix_notifications_user_created', 'user_id', 'created_at'),
)
[docs]
class UnsubscribeToken(Base):
__tablename__ = "unsubscribe_tokens"
id: Mapped[int] = mapped_column(Integer, primary_key=True)
user_id: Mapped[int] = mapped_column(
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True
)
token_hash: Mapped[str] = mapped_column(String(64), unique=True, index=True, nullable=False)
notification_type: Mapped[str] = mapped_column(String(50), nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime, default=lambda: datetime.now(dt_timezone.utc), nullable=False
)
expires_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
used_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
user: Mapped["User"] = relationship()