136 lines
5.5 KiB
Python
136 lines
5.5 KiB
Python
from sqlalchemy import Boolean, Column, Integer, String, DateTime, ForeignKey, Text, Enum as SQLEnum
|
|
from sqlalchemy.dialects.postgresql import UUID
|
|
from sqlalchemy.orm import relationship
|
|
from sqlalchemy.sql import func
|
|
from database import Base
|
|
import uuid
|
|
import enum
|
|
|
|
|
|
class User(Base):
|
|
__tablename__ = "users"
|
|
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
|
|
email = Column(String, unique=True, nullable=False, index=True)
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
# Relationships
|
|
event_memberships = relationship("EventMember", back_populates="user", cascade="all, delete-orphan")
|
|
guests_added = relationship("Guest", back_populates="added_by_user", foreign_keys="Guest.added_by_user_id")
|
|
|
|
|
|
class Event(Base):
|
|
__tablename__ = "events"
|
|
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
|
|
name = Column(String, nullable=False)
|
|
date = Column(DateTime(timezone=True), nullable=True)
|
|
location = Column(String, nullable=True)
|
|
|
|
# WhatsApp Invitation template fields
|
|
partner1_name = Column(String, nullable=True) # e.g., "Dvir"
|
|
partner2_name = Column(String, nullable=True) # e.g., "Vered"
|
|
venue = Column(String, nullable=True) # Hall name/address
|
|
event_time = Column(String, nullable=True) # HH:mm format, e.g., "19:00"
|
|
guest_link = Column(String, nullable=True) # Custom RSVP link or auto-generated
|
|
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
|
|
|
# Relationships
|
|
members = relationship("EventMember", back_populates="event", cascade="all, delete-orphan")
|
|
guests = relationship("Guest", back_populates="event", cascade="all, delete-orphan")
|
|
|
|
|
|
class RoleEnum(str, enum.Enum):
|
|
admin = "admin"
|
|
editor = "editor"
|
|
viewer = "viewer"
|
|
|
|
|
|
class EventMember(Base):
|
|
__tablename__ = "event_members"
|
|
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
|
|
event_id = Column(UUID(as_uuid=True), ForeignKey("events.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
user_id = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
role = Column(SQLEnum(RoleEnum), default=RoleEnum.admin, nullable=False)
|
|
display_name = Column(String, nullable=True)
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
|
|
# Relationships
|
|
event = relationship("Event", back_populates="members")
|
|
user = relationship("User", back_populates="event_memberships")
|
|
|
|
__table_args__ = (
|
|
__import__('sqlalchemy').UniqueConstraint('event_id', 'user_id', name='uq_event_user'),
|
|
)
|
|
|
|
|
|
class GuestStatus(str, enum.Enum):
|
|
invited = "invited"
|
|
confirmed = "confirmed"
|
|
declined = "declined"
|
|
|
|
|
|
class Guest(Base):
|
|
__tablename__ = "guests_v2"
|
|
|
|
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
|
|
event_id = Column(UUID(as_uuid=True), ForeignKey("events.id", ondelete="CASCADE"), nullable=False, index=True)
|
|
added_by_user_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False, index=True)
|
|
|
|
# Guest Information
|
|
first_name = Column(String, nullable=False)
|
|
last_name = Column(String, nullable=False)
|
|
email = Column(String, nullable=True)
|
|
phone = Column(String, nullable=True) # Legacy field - use phone_number instead
|
|
phone_number = Column(String, nullable=True)
|
|
|
|
# RSVP & Preferences
|
|
rsvp_status = Column(SQLEnum(GuestStatus), default=GuestStatus.invited, nullable=False)
|
|
meal_preference = Column(String, nullable=True)
|
|
|
|
# Plus One
|
|
has_plus_one = Column(Boolean, default=False)
|
|
plus_one_name = Column(String, nullable=True)
|
|
|
|
# Event Details
|
|
table_number = Column(String, nullable=True)
|
|
side = Column(String, nullable=True) # e.g. "groom side", "bride side"
|
|
|
|
# Source Information
|
|
owner_email = Column(String, nullable=True) # Email of person who added this guest
|
|
source = Column(String, default="manual", nullable=False) # 'google', 'manual', 'self-service'
|
|
|
|
# Notes & Metadata
|
|
notes = Column(Text, nullable=True)
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
|
|
|
# Relationships
|
|
event = relationship("Event", back_populates="guests")
|
|
added_by_user = relationship("User", back_populates="guests_added", foreign_keys=[added_by_user_id])
|
|
|
|
|
|
# ── RSVP tokens ────────────────────────────────────────────────────────────
|
|
|
|
class RsvpToken(Base):
|
|
"""
|
|
One-time token generated per guest per WhatsApp send.
|
|
Encodes event + guest context so the /guest page knows which RSVP
|
|
to update without exposing UUIDs in the URL.
|
|
"""
|
|
__tablename__ = "rsvp_tokens"
|
|
|
|
token = Column(String, primary_key=True, index=True)
|
|
event_id = Column(UUID(as_uuid=True), ForeignKey("events.id", ondelete="CASCADE"), nullable=False)
|
|
guest_id = Column(UUID(as_uuid=True), ForeignKey("guests_v2.id", ondelete="SET NULL"), nullable=True)
|
|
phone = Column(String, nullable=True)
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
expires_at = Column(DateTime(timezone=True), nullable=True)
|
|
used_at = Column(DateTime(timezone=True), nullable=True)
|
|
|
|
event = relationship("Event")
|
|
guest = relationship("Guest")
|