first commit

This commit is contained in:
Anurag Jangid 2026-10-08 02:52:58 +05:30
commit 34ea15a54d
246 changed files with 18660 additions and 0 deletions

View 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",
]

View 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")

View 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
View 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"

View 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")

View 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())

View 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())