Backend: - Add Stripe API endpoints (checkout, portal, webhook) in subscription.py - Add password reset (forgot-password, reset-password) in auth.py - Add email verification endpoints - Add rate limiting with slowapi - Add contact form and newsletter API (contact.py) - Add webhook endpoint for Stripe (webhooks.py) - Add NewsletterSubscriber model - Extend User model with password reset and email verification tokens - Extend email_service with new templates (password reset, verification, contact, newsletter) - Update env.example with all new environment variables Frontend: - Add /forgot-password page - Add /reset-password page with token handling - Add /verify-email page with auto-verification - Add forgot password link to login page - Connect contact form to API - Add API methods for all new endpoints Documentation: - Update README with new API endpoints - Update environment variables documentation - Update pages overview
35 lines
1.2 KiB
Python
35 lines
1.2 KiB
Python
"""Newsletter subscriber model."""
|
|
from datetime import datetime
|
|
from typing import Optional
|
|
from sqlalchemy import String, Boolean, DateTime
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from app.database import Base
|
|
|
|
|
|
class NewsletterSubscriber(Base):
|
|
"""Newsletter subscriber model."""
|
|
|
|
__tablename__ = "newsletter_subscribers"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True, index=True)
|
|
email: Mapped[str] = mapped_column(String(255), unique=True, index=True, nullable=False)
|
|
|
|
# Status
|
|
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
|
|
|
# Unsubscribe token for one-click unsubscribe
|
|
unsubscribe_token: Mapped[str] = mapped_column(String(255), unique=True, nullable=False)
|
|
|
|
# Timestamps
|
|
subscribed_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
|
unsubscribed_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
|
|
|
|
# Optional tracking
|
|
source: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) # e.g., "homepage", "blog", "footer"
|
|
|
|
def __repr__(self) -> str:
|
|
status = "active" if self.is_active else "inactive"
|
|
return f"<NewsletterSubscriber {self.email} ({status})>"
|
|
|