All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
by how many add refresh button choose column to display to the guest page
139 lines
5.8 KiB
Python
139 lines
5.8 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
|
|
invitation_image_url = Column(String, nullable=True) # Background image for invitations
|
|
guest_form_fields = Column(String, nullable=True) # JSON array of visible fields on guest RSVP page
|
|
|
|
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 / Companions
|
|
has_plus_one = Column(Boolean, default=False)
|
|
plus_one_name = Column(String, nullable=True)
|
|
companion_count = Column(Integer, default=0, nullable=True) # additional people coming
|
|
|
|
# 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")
|