Initial Commit

This commit is contained in:
belviskhoremk
2026-05-12 00:34:21 +00:00
commit d2dc43b16f
57 changed files with 6056 additions and 0 deletions

0
app/models/__init__.py Normal file
View File

77
app/models/admin.py Normal file
View File

@@ -0,0 +1,77 @@
from pydantic import BaseModel, EmailStr, Field
from uuid import UUID
from datetime import datetime
from typing import Any
class DashboardStats(BaseModel):
revenue_today: float
revenue_week: float
revenue_month: float
orders_pending: int
bookings_upcoming: int
low_stock_count: int
new_customers_month: int
class RevenueStats(BaseModel):
period: str
revenue: float
orders_count: int
bookings_count: int
class ActivityItem(BaseModel):
id: UUID
actor_name: str | None = None
action: str
entity_type: str
entity_id: UUID | None = None
metadata: dict | None = None
created_at: datetime
class CustomerOut(BaseModel):
id: UUID
email: str
full_name: str | None = None
phone: str | None = None
is_blocked: bool
orders_count: int = 0
bookings_count: int = 0
total_spent: float = 0.0
created_at: datetime
class UpdateCustomer(BaseModel):
is_blocked: bool | None = None
full_name: str | None = Field(None, max_length=100)
class StoreSettingOut(BaseModel):
key: str
value: Any
updated_at: datetime
class StoreSettingUpdate(BaseModel):
value: Any
class AdminUserCreate(BaseModel):
email: EmailStr
password: str = Field(min_length=8)
full_name: str
class EmailTemplateOut(BaseModel):
name: str
subject: str
body_html: str
body_text: str | None = None
class EmailTemplateUpdate(BaseModel):
subject: str
body_html: str
body_text: str | None = None

50
app/models/auth.py Normal file
View File

@@ -0,0 +1,50 @@
from pydantic import BaseModel, EmailStr, Field
class RegisterRequest(BaseModel):
email: EmailStr
password: str = Field(min_length=8)
# Accept either `name` (frontend) or `full_name`
name: str | None = Field(None, min_length=2, max_length=100)
full_name: str | None = Field(None, min_length=2, max_length=100)
phone: str | None = None
def resolved_name(self) -> str:
return self.name or self.full_name or ""
class LoginRequest(BaseModel):
email: EmailStr
password: str
class TokenResponse(BaseModel):
access_token: str
refresh_token: str
token_type: str = "bearer"
expires_in: int
class RefreshRequest(BaseModel):
refresh_token: str
class ForgotPasswordRequest(BaseModel):
email: EmailStr
class ResetPasswordRequest(BaseModel):
new_password: str = Field(min_length=8)
class ProfileOut(BaseModel):
id: str
email: str
full_name: str | None = None
phone: str | None = None
role: str
class UpdateProfileRequest(BaseModel):
full_name: str | None = Field(None, min_length=2, max_length=100)
phone: str | None = None

99
app/models/bookings.py Normal file
View File

@@ -0,0 +1,99 @@
from typing import Literal
from pydantic import BaseModel, Field
from uuid import UUID
from datetime import datetime, date, time
BookingStatus = Literal["pending", "confirmed", "cancelled", "completed", "no_show"]
class SlotCreate(BaseModel):
date: date
start_time: time
end_time: time
class WeeklyScheduleCreate(BaseModel):
day_of_week: int = Field(ge=0, le=6, description="0=Monday, 6=Sunday")
start_time: time
end_time: time
slot_duration_minutes: int = Field(ge=15, le=480, default=60)
class WeeklyScheduleOut(BaseModel):
id: UUID
day_of_week: int
start_time: time
end_time: time
slot_duration_minutes: int
is_active: bool
class GenerateSlotsRequest(BaseModel):
from_date: date
to_date: date = Field(description="Max 90 days from from_date")
class SlotOut(BaseModel):
id: UUID
date: date
start_time: time
end_time: time
is_blocked: bool
block_reason: str | None = None
is_booked: bool = False
class UpdateSlotRequest(BaseModel):
is_blocked: bool
block_reason: str | None = None
class BlockedDateCreate(BaseModel):
date: date
reason: str | None = None
class BlockedDateOut(BaseModel):
id: UUID
date: date
reason: str | None = None
class BookingCreate(BaseModel):
slot_id: UUID
service_note: str | None = Field(None, max_length=500)
# Guest fields — required when not authenticated
guest_name: str | None = Field(None, max_length=100)
guest_email: str | None = None
guest_phone: str | None = None
class BookingOut(BaseModel):
id: UUID
user_id: UUID | None = None
slot_id: UUID
slot_date: date
slot_start: str # "HH:MM"
slot_end: str # "HH:MM"
service_note: str | None = None
# Resolved client info (from profile or guest fields)
client_name: str | None = None
client_email: str | None = None
client_phone: str | None = None
status: str
amount_paid: float | None = None
stripe_payment_intent_id: str | None = None
admin_notes: str | None = None
created_at: datetime
updated_at: datetime
class BookingCheckoutResponse(BaseModel):
booking_id: UUID
client_secret: str
amount: float
class UpdateBookingStatus(BaseModel):
status: BookingStatus
admin_notes: str | None = None

57
app/models/orders.py Normal file
View File

@@ -0,0 +1,57 @@
from typing import Literal
from pydantic import BaseModel, Field
from uuid import UUID
from datetime import datetime
OrderStatus = Literal["pending", "paid", "processing", "shipped", "delivered", "cancelled", "refunded"]
class OrderItemCreate(BaseModel):
product_id: UUID
quantity: int = Field(ge=1)
class OrderCreate(BaseModel):
items: list[OrderItemCreate] = Field(min_length=1)
shipping_address: dict | None = None
notes: str | None = None
class OrderItemOut(BaseModel):
id: UUID
product_id: UUID
product_name: str
quantity: int
unit_price: float
@property
def subtotal(self) -> float:
return round(self.quantity * self.unit_price, 2)
class OrderOut(BaseModel):
id: UUID
user_id: UUID
status: str
total_amount: float
items: list[OrderItemOut] = []
shipping_address: dict | None = None
notes: str | None = None
stripe_payment_intent_id: str | None = None
created_at: datetime
updated_at: datetime
class CheckoutResponse(BaseModel):
order_id: UUID
client_secret: str
amount: float
class UpdateOrderStatus(BaseModel):
status: OrderStatus
class RefundRequest(BaseModel):
reason: str | None = None
amount: float | None = Field(None, gt=0)

70
app/models/products.py Normal file
View File

@@ -0,0 +1,70 @@
from typing import Literal
from pydantic import BaseModel, Field
from uuid import UUID
from datetime import datetime
ProductCategory = Literal["clip-in", "tape-in", "ponytail", "keratin"]
class ProductCreate(BaseModel):
name: str = Field(min_length=1, max_length=200)
description: str | None = None
price: float = Field(gt=0)
original_price: float | None = Field(None, gt=0)
category: ProductCategory | None = None
colors: list[str] = []
lengths: list[str] = []
features: list[str] = []
stock_quantity: int = Field(ge=0, default=0)
is_featured: bool = False
is_hidden: bool = False
is_new: bool = False
is_bestseller: bool = False
class ProductUpdate(BaseModel):
name: str | None = Field(None, min_length=1, max_length=200)
description: str | None = None
price: float | None = Field(None, gt=0)
original_price: float | None = Field(None, gt=0)
category: ProductCategory | None = None
colors: list[str] | None = None
lengths: list[str] | None = None
features: list[str] | None = None
stock_quantity: int | None = Field(None, ge=0)
is_featured: bool | None = None
is_hidden: bool | None = None
is_new: bool | None = None
is_bestseller: bool | None = None
class StockUpdate(BaseModel):
id: UUID
stock_quantity: int = Field(ge=0)
class BulkStockUpdateRequest(BaseModel):
updates: list[StockUpdate]
class ProductOut(BaseModel):
id: UUID
name: str
description: str | None = None
price: float
original_price: float | None = None
category: str | None = None
image: str = ""
images: list[str] = []
colors: list[str] = []
lengths: list[str] = []
features: list[str] = []
stock_quantity: int
is_featured: bool
is_hidden: bool
is_new: bool
is_bestseller: bool
rating: float = 0
review_count: int = 0
created_at: datetime
updated_at: datetime