GlowSenseAI / models.py
Tayyaba11's picture
deploy backend
6e08e39
Raw
History Blame Contribute Delete
13 kB
from sqlalchemy import Column, Integer, String, Text, DateTime, Boolean, ForeignKey, Time, Float
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from pgvector.sqlalchemy import Vector
from database import Base
class Customer(Base):
__tablename__ = "customers"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True, nullable=False)
hashed_password = Column(String, nullable=True) # Nullable for Google users
full_name = Column(String)
phone = Column(String)
location = Column(String, nullable=True) # Customer location/city
profile_picture = Column(String, nullable=True) # URL from Google
is_active = Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
# Google OAuth
google_id = Column(String, nullable=True, index=True)
# Preferences
skin_type = Column(String, nullable=True) # e.g., "Oily", "Dry", "Combination", "Sensitive", "Normal"
categories = Column(Text, nullable=True) # JSON array: ["Skincare", "Haircare", "Makeup"]
budget_range = Column(String, nullable=True) # e.g., "0-50", "50-100", "100-200", "200+"
# Relationships
payments = relationship("Payment", back_populates="customer", foreign_keys="Payment.customer_id")
class ServiceProvider(Base):
__tablename__ = "service_providers"
id = Column(Integer, primary_key=True, index=True)
email = Column(String, unique=True, index=True, nullable=False)
hashed_password = Column(String, nullable=True) # Nullable for Google users
full_name = Column(String)
business_name = Column(String)
phone = Column(String)
city = Column(String)
bio = Column(Text)
profile_photo = Column(String, nullable=True) # File path or URL
is_active = Column(Boolean, default=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
# New fields for provider signup
cnic_id = Column(String, nullable=True) # CNIC/ID Number
certificates = Column(Text, nullable=True) # Certifications/Qualifications
business_license = Column(String, nullable=True) # Business license file path or number
# Google OAuth
google_id = Column(String, nullable=True, index=True) # Removed unique=True to allow multiple NULLs
profile_picture = Column(String, nullable=True) # URL from Google
# Timezone and Calendar Integration
timezone = Column(String, default="UTC") # e.g., "America/New_York", "Asia/Karachi"
google_calendar_id = Column(String, nullable=True) # Google Calendar ID for integration
google_calendar_access_token = Column(Text, nullable=True) # Encrypted access token
google_calendar_refresh_token = Column(Text, nullable=True) # Encrypted refresh token
# Provider Level (based on ratings)
level = Column(String, default="beginner") # beginner, skilled, expert
# Stripe Connect (payouts)
stripe_account_id = Column(String, nullable=True) # Connected account ID (acct_xxx)
stripe_onboarding_complete = Column(Boolean, default=False) # Bank/payout details completed
# AI Assistant / Provider Embeddings
embedding = Column(Vector(384), nullable=True) # pgvector mapping -> vector(384)
# Relationships
portfolio_items = relationship("PortfolioItem", back_populates="provider", cascade="all, delete-orphan")
services = relationship("Service", back_populates="provider", cascade="all, delete-orphan")
# Use provider_id as the primary FK linking bookings to this provider
bookings = relationship(
"Booking",
back_populates="provider",
cascade="all, delete-orphan",
foreign_keys="Booking.provider_id",
)
payments = relationship("Payment", back_populates="provider", foreign_keys="Payment.provider_id")
class PortfolioItem(Base):
__tablename__ = "portfolio_items"
id = Column(Integer, primary_key=True, index=True)
provider_id = Column(Integer, ForeignKey("service_providers.id"), nullable=False)
title = Column(String, nullable=False)
description = Column(Text, nullable=True)
experience_details = Column(Text, nullable=True)
image_url = Column(String, nullable=True)
video_url = Column(String, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
provider = relationship("ServiceProvider", back_populates="portfolio_items")
class Service(Base):
__tablename__ = "services"
id = Column(Integer, primary_key=True, index=True)
provider_id = Column(Integer, ForeignKey("service_providers.id"), nullable=False)
name = Column(String, nullable=False)
category = Column(String, nullable=True)
description = Column(Text, nullable=True)
price = Column(String, nullable=False)
duration = Column(String, nullable=True) # e.g., "60 minutes"
is_active = Column(Boolean, default=True)
availability_schedule = Column(Text, nullable=True) # JSON string for backward compatibility
created_at = Column(DateTime(timezone=True), server_default=func.now())
provider = relationship("ServiceProvider", back_populates="services")
bookings = relationship("Booking", back_populates="service", cascade="all, delete-orphan")
time_slots = relationship("TimeSlot", back_populates="service", cascade="all, delete-orphan")
class TimeSlot(Base):
__tablename__ = "time_slots"
id = Column(Integer, primary_key=True, index=True)
service_id = Column(Integer, ForeignKey("services.id"), nullable=False)
slot_date = Column(DateTime(timezone=True), nullable=False)
is_available = Column(Boolean, default=True) # Can be marked unavailable by provider
created_at = Column(DateTime(timezone=True), server_default=func.now())
service = relationship("Service", back_populates="time_slots")
booking = relationship("Booking", back_populates="time_slot", uselist=False) # One booking per slot
class Booking(Base):
__tablename__ = "bookings"
id = Column(Integer, primary_key=True, index=True)
customer_id = Column(Integer, ForeignKey("customers.id"), nullable=False)
provider_id = Column(Integer, ForeignKey("service_providers.id"), nullable=False)
service_id = Column(Integer, ForeignKey("services.id"), nullable=False)
time_slot_id = Column(Integer, ForeignKey("time_slots.id"), nullable=True) # Reference to time slot
booking_date = Column(DateTime(timezone=True), nullable=False) # Kept for backward compatibility
end_date = Column(DateTime(timezone=True), nullable=True) # Calculated from service duration
status = Column(String, default="pending") # pending, confirmed, completed, cancelled, rejected
# Optional duplicated status field for richer workflows
booking_status = Column(String, nullable=True) # Mirrors/extends status when using advanced flows
notes = Column(Text, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
# Google Calendar Integration
google_calendar_event_id = Column(String, nullable=True) # Google Calendar event ID
# Notification tracking
reminder_sent = Column(Boolean, default=False)
# Payment Integration (card capture + escrow + payouts)
payment_status = Column(
String,
default="UNPAID"
) # UNPAID, HELD_IN_ESCROW, RELEASED_TO_PROVIDER, REFUNDED, FAILED
payment_id = Column(Integer, ForeignKey("payments.id"), nullable=True) # Reference to payment
# Stripe identifiers (stored on booking for escrow / payouts / refunds)
stripe_payment_intent_id = Column(String, nullable=True, index=True)
stripe_charge_id = Column(String, nullable=True, index=True)
stripe_transfer_id = Column(String, nullable=True, index=True)
stripe_refund_id = Column(String, nullable=True, index=True)
# Provider assignment tracking (original/assigned provider)
original_provider_id = Column(Integer, ForeignKey("service_providers.id"), nullable=True)
assigned_provider_id = Column(Integer, ForeignKey("service_providers.id"), nullable=True)
customer = relationship("Customer")
provider = relationship(
"ServiceProvider",
back_populates="bookings",
foreign_keys=[provider_id],
)
# Relationships for advanced provider assignment
original_provider = relationship(
"ServiceProvider",
foreign_keys=[original_provider_id],
lazy="joined",
)
assigned_provider = relationship(
"ServiceProvider",
foreign_keys=[assigned_provider_id],
lazy="joined",
)
service = relationship("Service", back_populates="bookings")
time_slot = relationship("TimeSlot", back_populates="booking")
payment = relationship("Payment", back_populates="booking", uselist=False, foreign_keys="[Payment.booking_id]", primaryjoin="Booking.id == Payment.booking_id")
class Rating(Base):
"""Customer rating for a completed booking/service."""
__tablename__ = "ratings"
id = Column(Integer, primary_key=True, index=True)
booking_id = Column(Integer, ForeignKey("bookings.id"), unique=True, nullable=False)
customer_id = Column(Integer, ForeignKey("customers.id"), nullable=False)
provider_id = Column(Integer, ForeignKey("service_providers.id"), nullable=False)
service_id = Column(Integer, ForeignKey("services.id"), nullable=False)
rating = Column(Integer, nullable=False) # 1-5
comment = Column(Text, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
class Payment(Base):
__tablename__ = "payments"
id = Column(Integer, primary_key=True, index=True)
booking_id = Column(Integer, ForeignKey("bookings.id"), nullable=False)
customer_id = Column(Integer, ForeignKey("customers.id"), nullable=False)
provider_id = Column(Integer, ForeignKey("service_providers.id"), nullable=False)
# Payment Details
amount = Column(String, nullable=False) # Amount in currency (e.g., "50.00")
currency = Column(String, default="USD") # Currency code
payment_method = Column(String, nullable=True) # card, bank_transfer, etc.
# Stripe Integration
stripe_payment_intent_id = Column(String, nullable=True, index=True) # Stripe Payment Intent ID
stripe_charge_id = Column(String, nullable=True) # Stripe Charge ID
stripe_customer_id = Column(String, nullable=True) # Stripe Customer ID
# Payment Status
status = Column(String, default="pending") # pending, succeeded, failed, refunded, cancelled
failure_reason = Column(Text, nullable=True) # Reason if payment failed
# Timestamps
created_at = Column(DateTime(timezone=True), server_default=func.now())
paid_at = Column(DateTime(timezone=True), nullable=True)
refunded_at = Column(DateTime(timezone=True), nullable=True)
# Relationships
booking = relationship("Booking", back_populates="payment", foreign_keys=[booking_id])
customer = relationship("Customer", back_populates="payments", foreign_keys=[customer_id])
provider = relationship("ServiceProvider", back_populates="payments", foreign_keys=[provider_id])
class Notification(Base):
"""General-purpose notification for customers and providers."""
__tablename__ = "notifications"
id = Column(Integer, primary_key=True, index=True)
# One of customer_id or provider_id must be set
customer_id = Column(Integer, ForeignKey("customers.id"), nullable=True, index=True)
provider_id = Column(Integer, ForeignKey("service_providers.id"), nullable=True, index=True)
booking_id = Column(Integer, ForeignKey("bookings.id"), nullable=True)
type = Column(String, nullable=False) # booking_cancelled_by_provider, standby_added, refund_processed, standby_selected
title = Column(String, nullable=False)
message = Column(Text, nullable=False)
is_read = Column(Boolean, default=False)
data = Column(Text, nullable=True) # JSON string for extra payload (e.g. standby provider list)
created_at = Column(DateTime(timezone=True), server_default=func.now())
customer = relationship("Customer")
provider = relationship("ServiceProvider")
class SkinAnalysis(Base):
"""Stores AI skin analysis results for a customer (text only, no image)."""
__tablename__ = "skin_analyses"
id = Column(Integer, primary_key=True, index=True)
customer_id = Column(Integer, ForeignKey("customers.id"), nullable=False)
results = Column(Text, nullable=False) # JSON string: list of {label, display, score, rank}
image_url = Column(String, nullable=True) # Always None — kept for future use
notes = Column(Text, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
customer = relationship("Customer")
# Standby Support System Models - Re-enabled for provider cancellation workflow