first commit
This commit is contained in:
commit
34ea15a54d
246 changed files with 18660 additions and 0 deletions
50
backend/app/models/__init__.py
Normal file
50
backend/app/models/__init__.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
from app.db.base import Base
|
||||
from app.models.catalog import Event, EventMedia, TicketType, Venue
|
||||
from app.models.user import User
|
||||
from app.models.entities import (
|
||||
AdminUser,
|
||||
AlertDispatch,
|
||||
EventEdition,
|
||||
MediaAsset,
|
||||
NewsletterSubscriber,
|
||||
OutboundClick,
|
||||
PlatformLink,
|
||||
)
|
||||
from app.models.enums import (
|
||||
AdminRole,
|
||||
EventStatus,
|
||||
EventType,
|
||||
OrderStatus,
|
||||
PlatformKind,
|
||||
TicketStatus,
|
||||
TicketTypeStatus,
|
||||
)
|
||||
from app.models.orders import Order, OrderItem, Ticket
|
||||
from app.models.payments import PaymentWebhookEvent
|
||||
|
||||
__all__ = [
|
||||
"Base",
|
||||
"AdminRole",
|
||||
"AdminUser",
|
||||
"AlertDispatch",
|
||||
"Event",
|
||||
"EventEdition",
|
||||
"EventMedia",
|
||||
"EventStatus",
|
||||
"EventType",
|
||||
"MediaAsset",
|
||||
"NewsletterSubscriber",
|
||||
"Order",
|
||||
"OrderItem",
|
||||
"OrderStatus",
|
||||
"OutboundClick",
|
||||
"PaymentWebhookEvent",
|
||||
"PlatformKind",
|
||||
"PlatformLink",
|
||||
"Ticket",
|
||||
"TicketStatus",
|
||||
"TicketType",
|
||||
"TicketTypeStatus",
|
||||
"User",
|
||||
"Venue",
|
||||
]
|
||||
115
backend/app/models/catalog.py
Normal file
115
backend/app/models/catalog.py
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import (
|
||||
Boolean,
|
||||
DateTime,
|
||||
Enum,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Integer,
|
||||
String,
|
||||
Text,
|
||||
func,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.db.base import Base
|
||||
from app.models.enums import EventStatus, EventType, TicketTypeStatus
|
||||
|
||||
|
||||
class Venue(Base):
|
||||
__tablename__ = "venues"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
slug: Mapped[str] = mapped_column(String(120), unique=True, index=True)
|
||||
name: Mapped[str] = mapped_column(String(200))
|
||||
area: Mapped[str] = mapped_column(String(120))
|
||||
city: Mapped[str] = mapped_column(String(80), default="Bengaluru")
|
||||
address: Mapped[str] = mapped_column(String(512))
|
||||
map_url: Mapped[str | None] = mapped_column(String(512), nullable=True)
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
server_default=func.now(),
|
||||
onupdate=func.now(),
|
||||
)
|
||||
|
||||
events: Mapped[list["Event"]] = relationship("Event", back_populates="venue")
|
||||
|
||||
|
||||
class Event(Base):
|
||||
__tablename__ = "events"
|
||||
__table_args__ = (
|
||||
Index("ix_events_status_starts_at", "status", "starts_at"),
|
||||
Index("ix_events_category_status", "category", "status"),
|
||||
)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
slug: Mapped[str] = mapped_column(String(200), unique=True, index=True)
|
||||
title: Mapped[str] = mapped_column(String(240))
|
||||
category: Mapped[EventType] = mapped_column(Enum(EventType), index=True)
|
||||
status: Mapped[EventStatus] = mapped_column(Enum(EventStatus), default=EventStatus.draft, index=True)
|
||||
venue_id: Mapped[int] = mapped_column(ForeignKey("venues.id", ondelete="RESTRICT"), index=True)
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
starts_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True)
|
||||
ends_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
is_featured: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
cover_image_url: Mapped[str | None] = mapped_column(String(1024), nullable=True)
|
||||
cover_image_alt: Mapped[str | None] = mapped_column(String(300), nullable=True)
|
||||
meta_description: Mapped[str | None] = mapped_column(String(320), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
server_default=func.now(),
|
||||
onupdate=func.now(),
|
||||
)
|
||||
|
||||
venue: Mapped["Venue"] = relationship("Venue", back_populates="events")
|
||||
media: Mapped[list["EventMedia"]] = relationship(
|
||||
"EventMedia",
|
||||
back_populates="event",
|
||||
cascade="all, delete-orphan",
|
||||
order_by="EventMedia.sort_order",
|
||||
)
|
||||
ticket_types: Mapped[list["TicketType"]] = relationship(
|
||||
"TicketType",
|
||||
back_populates="event",
|
||||
cascade="all, delete-orphan",
|
||||
order_by="TicketType.sort_order",
|
||||
)
|
||||
|
||||
|
||||
class EventMedia(Base):
|
||||
__tablename__ = "event_media"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
event_id: Mapped[int] = mapped_column(ForeignKey("events.id", ondelete="CASCADE"), index=True)
|
||||
image_url: Mapped[str] = mapped_column(String(1024))
|
||||
alt_text: Mapped[str | None] = mapped_column(String(300), nullable=True)
|
||||
sort_order: Mapped[int] = mapped_column(Integer, default=0)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
event: Mapped["Event"] = relationship("Event", back_populates="media")
|
||||
|
||||
|
||||
class TicketType(Base):
|
||||
__tablename__ = "ticket_types"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
event_id: Mapped[int] = mapped_column(ForeignKey("events.id", ondelete="CASCADE"), index=True)
|
||||
name: Mapped[str] = mapped_column(String(120))
|
||||
description: Mapped[str | None] = mapped_column(String(512), nullable=True)
|
||||
price_paise: Mapped[int] = mapped_column(Integer)
|
||||
quantity_total: Mapped[int] = mapped_column(Integer)
|
||||
quantity_sold: Mapped[int] = mapped_column(Integer, default=0)
|
||||
status: Mapped[TicketTypeStatus] = mapped_column(Enum(TicketTypeStatus), default=TicketTypeStatus.active)
|
||||
sort_order: Mapped[int] = mapped_column(Integer, default=0)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
server_default=func.now(),
|
||||
onupdate=func.now(),
|
||||
)
|
||||
|
||||
event: Mapped["Event"] = relationship("Event", back_populates="ticket_types")
|
||||
105
backend/app/models/entities.py
Normal file
105
backend/app/models/entities.py
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, Enum, ForeignKey, Integer, String, Text, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.db.base import Base
|
||||
from app.models.enums import AdminRole, EventType, PlatformKind
|
||||
|
||||
|
||||
class AdminUser(Base):
|
||||
__tablename__ = "admin_users"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
|
||||
hashed_password: Mapped[str] = mapped_column(String(255))
|
||||
role: Mapped[AdminRole] = mapped_column(Enum(AdminRole), default=AdminRole.super_admin)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
|
||||
class NewsletterSubscriber(Base):
|
||||
__tablename__ = "newsletter_subscribers"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
|
||||
neighborhood: Mapped[str | None] = mapped_column(String(120), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
|
||||
class AlertDispatch(Base):
|
||||
__tablename__ = "alert_dispatches"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
alert_key: Mapped[str] = mapped_column(String(120), unique=True, index=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
|
||||
class PlatformLink(Base):
|
||||
__tablename__ = "platform_links"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
kind: Mapped[PlatformKind] = mapped_column(Enum(PlatformKind), index=True)
|
||||
label: Mapped[str] = mapped_column(String(120))
|
||||
url: Mapped[str] = mapped_column(String(512))
|
||||
sort_order: Mapped[int] = mapped_column(Integer, default=0)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
|
||||
class EventEdition(Base):
|
||||
__tablename__ = "event_editions"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
title: Mapped[str] = mapped_column(String(200))
|
||||
slug: Mapped[str] = mapped_column(String(200), unique=True, index=True)
|
||||
event_type: Mapped[EventType] = mapped_column(Enum(EventType), index=True)
|
||||
city: Mapped[str] = mapped_column(String(80), default="Bengaluru")
|
||||
venue_name: Mapped[str | None] = mapped_column(String(200), nullable=True)
|
||||
event_date: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
is_published: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
is_featured: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
cover_image_path: Mapped[str | None] = mapped_column(String(512), nullable=True)
|
||||
bookmyshow_url: Mapped[str | None] = mapped_column(String(512), nullable=True)
|
||||
district_url: Mapped[str | None] = mapped_column(String(512), nullable=True)
|
||||
instagram_url: Mapped[str | None] = mapped_column(String(512), nullable=True)
|
||||
meta_description: Mapped[str | None] = mapped_column(String(320), nullable=True)
|
||||
sort_order: Mapped[int] = mapped_column(Integer, default=0)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
media: Mapped[list["MediaAsset"]] = relationship(
|
||||
"MediaAsset",
|
||||
back_populates="edition",
|
||||
cascade="all, delete-orphan",
|
||||
order_by="MediaAsset.sort_order",
|
||||
)
|
||||
|
||||
|
||||
class OutboundClick(Base):
|
||||
__tablename__ = "outbound_clicks"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
link_kind: Mapped[str] = mapped_column(String(64), index=True)
|
||||
destination_url: Mapped[str] = mapped_column(String(512))
|
||||
edition_id: Mapped[int | None] = mapped_column(
|
||||
ForeignKey("event_editions.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
index=True,
|
||||
)
|
||||
referrer: Mapped[str | None] = mapped_column(String(512), nullable=True)
|
||||
user_agent: Mapped[str | None] = mapped_column(String(512), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), index=True)
|
||||
|
||||
|
||||
class MediaAsset(Base):
|
||||
__tablename__ = "media_assets"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
edition_id: Mapped[int] = mapped_column(ForeignKey("event_editions.id", ondelete="CASCADE"), index=True)
|
||||
file_path: Mapped[str] = mapped_column(String(512))
|
||||
caption: Mapped[str | None] = mapped_column(String(300), nullable=True)
|
||||
sort_order: Mapped[int] = mapped_column(Integer, default=0)
|
||||
is_published: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
edition: Mapped["EventEdition"] = relationship("EventEdition", back_populates="media")
|
||||
101
backend/app/models/enums.py
Normal file
101
backend/app/models/enums.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import enum
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class EventType(str, enum.Enum):
|
||||
|
||||
holi = "holi"
|
||||
|
||||
garba = "garba"
|
||||
|
||||
dandiya = "dandiya"
|
||||
|
||||
bhajan_clubbing = "bhajan_clubbing"
|
||||
|
||||
special = "special"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class PlatformKind(str, enum.Enum):
|
||||
|
||||
bookmyshow = "bookmyshow"
|
||||
|
||||
district = "district"
|
||||
|
||||
instagram = "instagram"
|
||||
|
||||
highape = "highape"
|
||||
|
||||
youtube = "youtube"
|
||||
|
||||
whatsapp = "whatsapp"
|
||||
|
||||
website = "website"
|
||||
|
||||
other = "other"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class AdminRole(str, enum.Enum):
|
||||
|
||||
super_admin = "super_admin"
|
||||
|
||||
editor = "editor"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class EventStatus(str, enum.Enum):
|
||||
|
||||
draft = "draft"
|
||||
|
||||
published = "published"
|
||||
|
||||
cancelled = "cancelled"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class TicketTypeStatus(str, enum.Enum):
|
||||
|
||||
active = "active"
|
||||
|
||||
sold_out = "sold_out"
|
||||
|
||||
inactive = "inactive"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class OrderStatus(str, enum.Enum):
|
||||
|
||||
pending = "pending"
|
||||
|
||||
paid = "paid"
|
||||
|
||||
cancelled = "cancelled"
|
||||
|
||||
failed = "failed"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class TicketStatus(str, enum.Enum):
|
||||
|
||||
valid = "valid"
|
||||
|
||||
used = "used"
|
||||
|
||||
cancelled = "cancelled"
|
||||
|
||||
|
||||
89
backend/app/models/orders.py
Normal file
89
backend/app/models/orders.py
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import (
|
||||
DateTime,
|
||||
Enum,
|
||||
ForeignKey,
|
||||
Index,
|
||||
Integer,
|
||||
String,
|
||||
func,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.db.base import Base
|
||||
from app.models.enums import OrderStatus, TicketStatus
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from app.models.catalog import Event, TicketType
|
||||
|
||||
|
||||
class Order(Base):
|
||||
__tablename__ = "orders"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="RESTRICT"), index=True)
|
||||
event_id: Mapped[int] = mapped_column(ForeignKey("events.id", ondelete="RESTRICT"), index=True)
|
||||
status: Mapped[OrderStatus] = mapped_column(Enum(OrderStatus), default=OrderStatus.pending, index=True)
|
||||
total_paise: Mapped[int] = mapped_column(Integer)
|
||||
currency: Mapped[str] = mapped_column(String(3), default="INR")
|
||||
payment_reference: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
||||
provider_order_id: Mapped[str | None] = mapped_column(String(64), unique=True, nullable=True, index=True)
|
||||
provider_payment_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
|
||||
idempotency_key: Mapped[str | None] = mapped_column(String(64), unique=True, nullable=True, index=True)
|
||||
expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True)
|
||||
paid_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
server_default=func.now(),
|
||||
onupdate=func.now(),
|
||||
)
|
||||
|
||||
items: Mapped[list["OrderItem"]] = relationship(
|
||||
"OrderItem",
|
||||
back_populates="order",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
tickets: Mapped[list["Ticket"]] = relationship(
|
||||
"Ticket",
|
||||
back_populates="order",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
event: Mapped["Event"] = relationship("Event")
|
||||
|
||||
|
||||
class OrderItem(Base):
|
||||
__tablename__ = "order_items"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
order_id: Mapped[int] = mapped_column(ForeignKey("orders.id", ondelete="CASCADE"), index=True)
|
||||
ticket_type_id: Mapped[int] = mapped_column(ForeignKey("ticket_types.id", ondelete="RESTRICT"), index=True)
|
||||
quantity: Mapped[int] = mapped_column(Integer)
|
||||
unit_price_paise: Mapped[int] = mapped_column(Integer)
|
||||
line_total_paise: Mapped[int] = mapped_column(Integer)
|
||||
|
||||
order: Mapped["Order"] = relationship("Order", back_populates="items")
|
||||
ticket_type: Mapped["TicketType"] = relationship("TicketType")
|
||||
tickets: Mapped[list["Ticket"]] = relationship("Ticket", back_populates="order_item")
|
||||
|
||||
|
||||
class Ticket(Base):
|
||||
__tablename__ = "tickets"
|
||||
__table_args__ = (Index("ix_tickets_user_status", "user_id", "status"),)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
code: Mapped[str] = mapped_column(String(32), unique=True, index=True)
|
||||
order_id: Mapped[int] = mapped_column(ForeignKey("orders.id", ondelete="CASCADE"), index=True)
|
||||
order_item_id: Mapped[int] = mapped_column(ForeignKey("order_items.id", ondelete="CASCADE"), index=True)
|
||||
ticket_type_id: Mapped[int] = mapped_column(ForeignKey("ticket_types.id", ondelete="RESTRICT"), index=True)
|
||||
event_id: Mapped[int] = mapped_column(ForeignKey("events.id", ondelete="RESTRICT"), index=True)
|
||||
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="RESTRICT"), index=True)
|
||||
status: Mapped[TicketStatus] = mapped_column(Enum(TicketStatus), default=TicketStatus.valid, index=True)
|
||||
qr_token: Mapped[str] = mapped_column(String(64), unique=True, index=True)
|
||||
used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
order: Mapped["Order"] = relationship("Order", back_populates="tickets")
|
||||
order_item: Mapped["OrderItem"] = relationship("OrderItem", back_populates="tickets")
|
||||
19
backend/app/models/payments.py
Normal file
19
backend/app/models/payments.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, String, Text, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
|
||||
class PaymentWebhookEvent(Base):
|
||||
"""Idempotent record of processed payment provider webhook events."""
|
||||
|
||||
__tablename__ = "payment_webhook_events"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
provider: Mapped[str] = mapped_column(String(32), index=True)
|
||||
provider_event_id: Mapped[str] = mapped_column(String(128), unique=True, index=True)
|
||||
event_type: Mapped[str] = mapped_column(String(64))
|
||||
payload: Mapped[str] = mapped_column(Text)
|
||||
processed_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
19
backend/app/models/user.py
Normal file
19
backend/app/models/user.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, String, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
|
||||
class User(Base):
|
||||
"""Attendee account (not admin)."""
|
||||
|
||||
__tablename__ = "users"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
|
||||
hashed_password: Mapped[str] = mapped_column(String(255))
|
||||
full_name: Mapped[str | None] = mapped_column(String(120), nullable=True)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True, server_default="true")
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
Loading…
Add table
Add a link
Reference in a new issue