from flask_sqlalchemy import SQLAlchemy from flask_login import UserMixin from datetime import datetime import uuid db = SQLAlchemy() def generate_uuid(): return str(uuid.uuid4()) class User(UserMixin, db.Model): __tablename__ = 'users' id = db.Column(db.String(36), primary_key=True, default=generate_uuid) oauth_id = db.Column(db.String(255), unique=True, nullable=True) username = db.Column(db.String(100), nullable=False) first_name = db.Column(db.String(100), nullable=True) last_name = db.Column(db.String(100), nullable=True) email = db.Column(db.String(255), nullable=True) profile_picture = db.Column(db.LargeBinary, nullable=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) polls = db.relationship('Poll', backref='creator', lazy='dynamic') responses = db.relationship('PollResponse', backref='user', lazy='dynamic') class Poll(db.Model): __tablename__ = 'polls' id = db.Column(db.String(36), primary_key=True, default=generate_uuid) title = db.Column(db.String(200), nullable=False) description = db.Column(db.Text, nullable=True) creator_id = db.Column(db.String(36), db.ForeignKey('users.id'), nullable=False) allow_anonymous = db.Column(db.Boolean, default=False, nullable=False) allowed_dates = db.Column(db.Text, nullable=True) # JSON array of date strings is_locked = db.Column(db.Boolean, default=False, nullable=False) is_day_wise = db.Column(db.Boolean, default=False, nullable=False) chosen_slots = db.Column(db.Text, nullable=True) # JSON array of {date, start} objects created_at = db.Column(db.DateTime, default=datetime.utcnow) responses = db.relationship('PollResponse', backref='poll', lazy='dynamic', cascade='all, delete-orphan') participants = db.relationship('PollParticipant', backref='poll', lazy='dynamic', cascade='all, delete-orphan') class PollParticipant(db.Model): __tablename__ = 'poll_participants' id = db.Column(db.String(36), primary_key=True, default=generate_uuid) poll_id = db.Column(db.String(36), db.ForeignKey('polls.id'), nullable=False) user_id = db.Column(db.String(36), db.ForeignKey('users.id'), nullable=True) guest_name = db.Column(db.String(100), nullable=True) session_key = db.Column(db.String(64), nullable=True) joined_at = db.Column(db.DateTime, default=datetime.utcnow) user = db.relationship('User', backref='participations') @property def display_name(self): if self.user: if self.user.first_name and self.user.last_name: return f"{self.user.first_name} {self.user.last_name}" return self.user.username return self.guest_name class PollResponse(db.Model): __tablename__ = 'poll_responses' id = db.Column(db.String(36), primary_key=True, default=generate_uuid) poll_id = db.Column(db.String(36), db.ForeignKey('polls.id'), nullable=False) user_id = db.Column(db.String(36), db.ForeignKey('users.id'), nullable=True) # For non-oauth users guest_name = db.Column(db.String(100), nullable=True) guest_email = db.Column(db.String(255), nullable=True) guest_profile_picture = db.Column(db.LargeBinary, nullable=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) time_slots = db.relationship('TimeSlot', backref='response', lazy='dynamic', cascade='all, delete-orphan') @property def display_name(self): if self.user: if self.user.first_name and self.user.last_name: return f"{self.user.first_name} {self.user.last_name}" return self.user.username return self.guest_name @property def display_email(self): if self.user: return self.user.email return self.guest_email class TimeSlot(db.Model): __tablename__ = 'time_slots' id = db.Column(db.String(36), primary_key=True, default=generate_uuid) response_id = db.Column(db.String(36), db.ForeignKey('poll_responses.id'), nullable=False) date = db.Column(db.Date, nullable=False) start_minutes = db.Column(db.Integer, nullable=False) # minutes from midnight (0-1439) end_minutes = db.Column(db.Integer, nullable=False) # minutes from midnight (0-1440) note = db.Column(db.String(500), nullable=True)