first commit
This commit is contained in:
commit
34ea15a54d
246 changed files with 18660 additions and 0 deletions
3
backend/app/schemas/__init__.py
Normal file
3
backend/app/schemas/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from app.schemas.auth import LoginRequest, RegisterRequest, Token, UserPublic
|
||||
from app.schemas.domain import * # noqa: F403
|
||||
from app.schemas.health import HealthResponse
|
||||
181
backend/app/schemas/admin.py
Normal file
181
backend/app/schemas/admin.py
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, EmailStr, Field
|
||||
|
||||
from app.models.enums import AdminRole, EventStatus, EventType, OrderStatus, TicketTypeStatus
|
||||
|
||||
|
||||
class AdminLoginRequest(BaseModel):
|
||||
email: EmailStr
|
||||
password: str = Field(min_length=8, max_length=128)
|
||||
|
||||
|
||||
class AdminToken(BaseModel):
|
||||
access_token: str
|
||||
token_type: str = "bearer"
|
||||
|
||||
|
||||
class AdminUserPublic(BaseModel):
|
||||
id: int
|
||||
email: EmailStr
|
||||
role: AdminRole
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class DashboardMetrics(BaseModel):
|
||||
total_revenue_paise: int
|
||||
tickets_sold: int
|
||||
tickets_remaining: int
|
||||
orders_total: int
|
||||
orders_paid: int
|
||||
orders_pending: int
|
||||
attendance: int
|
||||
upcoming_events: int
|
||||
|
||||
|
||||
class EventStatsRow(BaseModel):
|
||||
event_id: int
|
||||
event_title: str
|
||||
starts_at: datetime
|
||||
tickets_sold: int
|
||||
tickets_remaining: int
|
||||
revenue_paise: int
|
||||
attendance: int
|
||||
|
||||
|
||||
class VenueAdmin(BaseModel):
|
||||
id: int
|
||||
slug: str
|
||||
name: str
|
||||
area: str
|
||||
city: str
|
||||
address: str
|
||||
map_url: str | None = None
|
||||
description: str | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class VenueCreate(BaseModel):
|
||||
slug: str = Field(min_length=2, max_length=120)
|
||||
name: str = Field(min_length=2, max_length=200)
|
||||
area: str = Field(min_length=2, max_length=120)
|
||||
city: str = Field(default="Bengaluru", max_length=80)
|
||||
address: str = Field(min_length=5, max_length=512)
|
||||
map_url: str | None = Field(default=None, max_length=512)
|
||||
description: str | None = None
|
||||
|
||||
|
||||
class VenueUpdate(BaseModel):
|
||||
slug: str | None = Field(default=None, max_length=120)
|
||||
name: str | None = Field(default=None, max_length=200)
|
||||
area: str | None = Field(default=None, max_length=120)
|
||||
city: str | None = Field(default=None, max_length=80)
|
||||
address: str | None = Field(default=None, max_length=512)
|
||||
map_url: str | None = Field(default=None, max_length=512)
|
||||
description: str | None = None
|
||||
|
||||
|
||||
class EventAdmin(BaseModel):
|
||||
id: int
|
||||
slug: str
|
||||
title: str
|
||||
category: EventType
|
||||
status: EventStatus
|
||||
venue_id: int
|
||||
description: str | None
|
||||
starts_at: datetime
|
||||
ends_at: datetime | None
|
||||
is_featured: bool
|
||||
cover_image_url: str | None
|
||||
cover_image_alt: str | None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class EventCreate(BaseModel):
|
||||
slug: str = Field(min_length=2, max_length=200)
|
||||
title: str = Field(min_length=2, max_length=240)
|
||||
category: EventType
|
||||
status: EventStatus = EventStatus.draft
|
||||
venue_id: int
|
||||
description: str | None = None
|
||||
starts_at: datetime
|
||||
ends_at: datetime | None = None
|
||||
is_featured: bool = False
|
||||
cover_image_url: str | None = None
|
||||
cover_image_alt: str | None = None
|
||||
|
||||
|
||||
class EventUpdate(BaseModel):
|
||||
slug: str | None = Field(default=None, max_length=200)
|
||||
title: str | None = Field(default=None, max_length=240)
|
||||
category: EventType | None = None
|
||||
status: EventStatus | None = None
|
||||
venue_id: int | None = None
|
||||
description: str | None = None
|
||||
starts_at: datetime | None = None
|
||||
ends_at: datetime | None = None
|
||||
is_featured: bool | None = None
|
||||
cover_image_url: str | None = None
|
||||
cover_image_alt: str | None = None
|
||||
|
||||
|
||||
class TicketTypeAdmin(BaseModel):
|
||||
id: int
|
||||
event_id: int
|
||||
name: str
|
||||
description: str | None
|
||||
price_paise: int
|
||||
quantity_total: int
|
||||
quantity_sold: int
|
||||
remaining: int
|
||||
status: TicketTypeStatus
|
||||
sort_order: int
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class TicketTypeCreate(BaseModel):
|
||||
event_id: int
|
||||
name: str = Field(min_length=1, max_length=120)
|
||||
description: str | None = Field(default=None, max_length=512)
|
||||
price_paise: int = Field(ge=0)
|
||||
quantity_total: int = Field(ge=0)
|
||||
status: TicketTypeStatus = TicketTypeStatus.active
|
||||
sort_order: int = 0
|
||||
|
||||
|
||||
class TicketTypeUpdate(BaseModel):
|
||||
name: str | None = Field(default=None, max_length=120)
|
||||
description: str | None = Field(default=None, max_length=512)
|
||||
price_paise: int | None = Field(default=None, ge=0)
|
||||
quantity_total: int | None = Field(default=None, ge=0)
|
||||
status: TicketTypeStatus | None = None
|
||||
sort_order: int | None = None
|
||||
|
||||
|
||||
class OrderAdmin(BaseModel):
|
||||
id: int
|
||||
user_id: int
|
||||
event_id: int
|
||||
status: OrderStatus
|
||||
total_paise: int
|
||||
currency: str
|
||||
provider_order_id: str | None
|
||||
provider_payment_id: str | None
|
||||
paid_at: datetime | None
|
||||
created_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class PaymentAdmin(BaseModel):
|
||||
order_id: int
|
||||
event_id: int
|
||||
status: OrderStatus
|
||||
total_paise: int
|
||||
provider_payment_id: str | None
|
||||
payment_reference: str | None
|
||||
paid_at: datetime | None
|
||||
47
backend/app/schemas/auth.py
Normal file
47
backend/app/schemas/auth.py
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, EmailStr, Field, field_validator
|
||||
|
||||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str = "bearer"
|
||||
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
email: EmailStr
|
||||
password: str = Field(min_length=8, max_length=128)
|
||||
|
||||
@field_validator("email")
|
||||
@classmethod
|
||||
def normalize_email(cls, value: str) -> str:
|
||||
return value.strip().lower()
|
||||
|
||||
|
||||
class RegisterRequest(BaseModel):
|
||||
email: EmailStr
|
||||
password: str = Field(min_length=8, max_length=128)
|
||||
full_name: str | None = Field(default=None, max_length=120)
|
||||
|
||||
@field_validator("email")
|
||||
@classmethod
|
||||
def normalize_email(cls, value: str) -> str:
|
||||
return value.strip().lower()
|
||||
|
||||
@field_validator("full_name")
|
||||
@classmethod
|
||||
def strip_name(cls, value: str | None) -> str | None:
|
||||
if value is None:
|
||||
return None
|
||||
stripped = value.strip()
|
||||
return stripped or None
|
||||
|
||||
|
||||
class UserPublic(BaseModel):
|
||||
id: int
|
||||
email: EmailStr
|
||||
full_name: str | None
|
||||
is_active: bool
|
||||
created_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
62
backend/app/schemas/catalog.py
Normal file
62
backend/app/schemas/catalog.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.models.enums import EventStatus, EventType, TicketTypeStatus
|
||||
|
||||
|
||||
class VenuePublic(BaseModel):
|
||||
id: int
|
||||
slug: str
|
||||
name: str
|
||||
area: str
|
||||
city: str
|
||||
address: str
|
||||
map_url: str | None = None
|
||||
description: str | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class EventMediaPublic(BaseModel):
|
||||
id: int
|
||||
image_url: str
|
||||
alt_text: str | None
|
||||
sort_order: int
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class TicketTypePublic(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
description: str | None
|
||||
price_paise: int = Field(ge=0)
|
||||
status: TicketTypeStatus
|
||||
sort_order: int
|
||||
remaining_quantity: int = Field(ge=0)
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class EventSummary(BaseModel):
|
||||
id: int
|
||||
slug: str
|
||||
title: str
|
||||
category: EventType
|
||||
status: EventStatus
|
||||
starts_at: datetime
|
||||
ends_at: datetime | None
|
||||
is_featured: bool
|
||||
cover_image_url: str | None
|
||||
cover_image_alt: str | None
|
||||
venue: VenuePublic
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class EventDetail(EventSummary):
|
||||
description: str | None
|
||||
meta_description: str | None
|
||||
media: list[EventMediaPublic]
|
||||
ticket_types: list[TicketTypePublic]
|
||||
170
backend/app/schemas/domain.py
Normal file
170
backend/app/schemas/domain.py
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, EmailStr, Field
|
||||
|
||||
from app.models.enums import AdminRole, EventType, PlatformKind
|
||||
|
||||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str = "bearer"
|
||||
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
email: EmailStr
|
||||
password: str
|
||||
|
||||
|
||||
class SubscribeRequest(BaseModel):
|
||||
email: EmailStr
|
||||
neighborhood: str | None = Field(default=None, max_length=120)
|
||||
|
||||
|
||||
class SubscriberPublic(BaseModel):
|
||||
id: int
|
||||
email: str
|
||||
neighborhood: str | None
|
||||
created_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class AdminUserPublic(BaseModel):
|
||||
id: int
|
||||
email: str
|
||||
role: AdminRole
|
||||
created_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class AdminUserCreate(BaseModel):
|
||||
email: EmailStr
|
||||
password: str = Field(min_length=8, max_length=128)
|
||||
role: AdminRole = AdminRole.editor
|
||||
|
||||
|
||||
class PlatformLinkPublic(BaseModel):
|
||||
id: int
|
||||
kind: PlatformKind
|
||||
label: str
|
||||
url: str
|
||||
sort_order: int
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class PlatformLinkCreate(BaseModel):
|
||||
kind: PlatformKind
|
||||
label: str = Field(max_length=120)
|
||||
url: str = Field(max_length=512)
|
||||
sort_order: int = 0
|
||||
is_active: bool = True
|
||||
|
||||
|
||||
class PlatformLinkUpdate(BaseModel):
|
||||
kind: PlatformKind | None = None
|
||||
label: str | None = Field(default=None, max_length=120)
|
||||
url: str | None = Field(default=None, max_length=512)
|
||||
sort_order: int | None = None
|
||||
is_active: bool | None = None
|
||||
|
||||
|
||||
class EditionTicketsPublic(BaseModel):
|
||||
bookmyshow: str | None = None
|
||||
district: str | None = None
|
||||
instagram: str | None = None
|
||||
|
||||
|
||||
class MediaAssetPublic(BaseModel):
|
||||
id: int
|
||||
url: str
|
||||
thumb_url: str | None = None
|
||||
caption: str | None
|
||||
sort_order: int
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class ClickSummaryItem(BaseModel):
|
||||
link_kind: str
|
||||
count: int
|
||||
|
||||
|
||||
class OutboundClickPublic(BaseModel):
|
||||
id: int
|
||||
link_kind: str
|
||||
destination_url: str
|
||||
edition_id: int | None
|
||||
created_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class EventEditionPublic(BaseModel):
|
||||
id: int
|
||||
title: str
|
||||
slug: str
|
||||
event_type: EventType
|
||||
city: str
|
||||
venue_name: str | None
|
||||
event_date: datetime | None
|
||||
description: str | None
|
||||
is_featured: bool
|
||||
cover_image_url: str | None
|
||||
cover_thumb_url: str | None = None
|
||||
tickets: EditionTicketsPublic
|
||||
meta_description: str | None
|
||||
media: list[MediaAssetPublic]
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class EventEditionAdmin(EventEditionPublic):
|
||||
is_published: bool
|
||||
sort_order: int
|
||||
|
||||
|
||||
class EventEditionCreate(BaseModel):
|
||||
title: str = Field(max_length=200)
|
||||
slug: str = Field(max_length=200)
|
||||
event_type: EventType
|
||||
venue_name: str | None = Field(default=None, max_length=200)
|
||||
event_date: datetime | None = None
|
||||
description: str | None = None
|
||||
is_published: bool = False
|
||||
is_featured: bool = False
|
||||
sort_order: int = 0
|
||||
bookmyshow_url: str | None = Field(default=None, max_length=512)
|
||||
district_url: str | None = Field(default=None, max_length=512)
|
||||
instagram_url: str | None = Field(default=None, max_length=512)
|
||||
meta_description: str | None = Field(default=None, max_length=320)
|
||||
|
||||
|
||||
class EventEditionUpdate(BaseModel):
|
||||
title: str | None = Field(default=None, max_length=200)
|
||||
slug: str | None = Field(default=None, max_length=200)
|
||||
event_type: EventType | None = None
|
||||
venue_name: str | None = None
|
||||
event_date: datetime | None = None
|
||||
description: str | None = None
|
||||
is_published: bool | None = None
|
||||
is_featured: bool | None = None
|
||||
sort_order: int | None = None
|
||||
bookmyshow_url: str | None = Field(default=None, max_length=512)
|
||||
district_url: str | None = Field(default=None, max_length=512)
|
||||
instagram_url: str | None = Field(default=None, max_length=512)
|
||||
meta_description: str | None = Field(default=None, max_length=320)
|
||||
|
||||
|
||||
class SiteConfigPublic(BaseModel):
|
||||
city: str = "Bengaluru"
|
||||
tagline: str
|
||||
platform_links: list[PlatformLinkPublic]
|
||||
featured_edition: EventEditionPublic | None
|
||||
recent_editions: list[EventEditionPublic]
|
||||
|
||||
|
||||
class MediaReorderItem(BaseModel):
|
||||
id: int
|
||||
sort_order: int
|
||||
7
backend/app/schemas/health.py
Normal file
7
backend/app/schemas/health.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class HealthResponse(BaseModel):
|
||||
status: str = Field(examples=["ok"])
|
||||
service: str = Field(examples=["desi-clans-api"])
|
||||
database: str = Field(examples=["connected"])
|
||||
90
backend/app/schemas/orders.py
Normal file
90
backend/app/schemas/orders.py
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.models.enums import OrderStatus, TicketStatus, TicketTypeStatus
|
||||
|
||||
|
||||
class PaymentCheckoutPublic(BaseModel):
|
||||
provider: str
|
||||
key_id: str
|
||||
provider_order_id: str
|
||||
amount_paise: int
|
||||
currency: str
|
||||
order_id: int
|
||||
expires_at: datetime | None = None
|
||||
timeout_minutes: int
|
||||
|
||||
|
||||
class EventTicketTypePublic(BaseModel):
|
||||
id: int
|
||||
event_id: int
|
||||
name: str
|
||||
description: str | None
|
||||
price_paise: int = Field(ge=0)
|
||||
status: TicketTypeStatus
|
||||
sort_order: int
|
||||
remaining_quantity: int = Field(ge=0)
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class OrderLineCreate(BaseModel):
|
||||
ticket_type_id: int = Field(gt=0)
|
||||
quantity: int = Field(ge=1, le=20)
|
||||
|
||||
|
||||
class OrderCreate(BaseModel):
|
||||
event_id: int = Field(gt=0)
|
||||
items: list[OrderLineCreate] = Field(min_length=1, max_length=20)
|
||||
|
||||
|
||||
class OrderItemPublic(BaseModel):
|
||||
id: int
|
||||
ticket_type_id: int
|
||||
quantity: int
|
||||
unit_price_paise: int
|
||||
line_total_paise: int
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class OrderPublic(BaseModel):
|
||||
id: int
|
||||
event_id: int
|
||||
status: OrderStatus
|
||||
total_paise: int
|
||||
currency: str
|
||||
payment_reference: str | None
|
||||
provider_order_id: str | None = None
|
||||
provider_payment_id: str | None = None
|
||||
expires_at: datetime | None = None
|
||||
paid_at: datetime | None = None
|
||||
created_at: datetime
|
||||
items: list[OrderItemPublic]
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class OrderCheckoutResponse(OrderPublic):
|
||||
payment: PaymentCheckoutPublic
|
||||
|
||||
|
||||
class PaymentVerifyRequest(BaseModel):
|
||||
provider_order_id: str = Field(min_length=1, max_length=64)
|
||||
provider_payment_id: str = Field(min_length=1, max_length=64)
|
||||
signature: str = Field(min_length=1, max_length=256)
|
||||
|
||||
|
||||
class TicketPublic(BaseModel):
|
||||
id: int
|
||||
code: str
|
||||
order_id: int
|
||||
ticket_type_id: int
|
||||
event_id: int
|
||||
status: TicketStatus
|
||||
created_at: datetime
|
||||
ticket_type_name: str | None = None
|
||||
event_title: str | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
41
backend/app/schemas/tickets.py
Normal file
41
backend/app/schemas/tickets.py
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.models.enums import TicketStatus
|
||||
|
||||
|
||||
class TicketVerifyRequest(BaseModel):
|
||||
qr_token: str = Field(min_length=16, max_length=128)
|
||||
|
||||
|
||||
class TicketVerifyResponse(BaseModel):
|
||||
valid: bool
|
||||
status: TicketStatus
|
||||
ticket_id: int
|
||||
ticket_code: str
|
||||
event_title: str
|
||||
message: str
|
||||
|
||||
|
||||
class TicketDetailPublic(BaseModel):
|
||||
id: int
|
||||
code: str
|
||||
status: TicketStatus
|
||||
order_id: int
|
||||
event_id: int
|
||||
event_title: str
|
||||
event_starts_at: datetime
|
||||
event_ends_at: datetime | None
|
||||
venue_name: str
|
||||
venue_area: str
|
||||
venue_city: str
|
||||
attendee_name: str
|
||||
attendee_email: str
|
||||
ticket_type_name: str
|
||||
ticket_type_id: int
|
||||
qr_token: str
|
||||
qr_payload: str
|
||||
qr_image_base64: str
|
||||
used_at: datetime | None
|
||||
created_at: datetime
|
||||
Loading…
Add table
Add a link
Reference in a new issue