mirror of
http://88.130.71.182:3000/BlitTech/badoHair_be.git
synced 2026-06-12 23:23:22 +00:00
Initial Commit
This commit is contained in:
0
app/models/__init__.py
Normal file
0
app/models/__init__.py
Normal file
77
app/models/admin.py
Normal file
77
app/models/admin.py
Normal 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
50
app/models/auth.py
Normal 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
99
app/models/bookings.py
Normal 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
57
app/models/orders.py
Normal 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
70
app/models/products.py
Normal 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
|
||||
Reference in New Issue
Block a user