mirror of
http://88.130.71.182:3000/BlitTech/deals24togo_be.git
synced 2026-06-12 23:33:21 +00:00
Initial commit
This commit is contained in:
7
app/schemas/__init__.py
Normal file
7
app/schemas/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from app.schemas.auth import * # noqa: F401, F403
|
||||
from app.schemas.user import * # noqa: F401, F403
|
||||
from app.schemas.agency import * # noqa: F401, F403
|
||||
from app.schemas.category import * # noqa: F401, F403
|
||||
from app.schemas.listing import * # noqa: F401, F403
|
||||
from app.schemas.message import * # noqa: F401, F403
|
||||
from app.schemas.favorite import * # noqa: F401, F403
|
||||
55
app/schemas/agency.py
Normal file
55
app/schemas/agency.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""Agency request / response schemas."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, EmailStr, Field, HttpUrl
|
||||
|
||||
|
||||
class AgencyBase(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=255)
|
||||
description: str = Field(..., min_length=1, max_length=2000)
|
||||
address: str = Field(..., min_length=1, max_length=500)
|
||||
phone: str = Field(..., min_length=1, max_length=20)
|
||||
email: EmailStr
|
||||
website: Optional[str] = Field(None, max_length=500)
|
||||
|
||||
|
||||
class AgencyCreate(AgencyBase):
|
||||
pass
|
||||
|
||||
|
||||
class AgencyUpdate(BaseModel):
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=255)
|
||||
description: Optional[str] = Field(None, min_length=1, max_length=2000)
|
||||
address: Optional[str] = Field(None, min_length=1, max_length=500)
|
||||
phone: Optional[str] = Field(None, min_length=1, max_length=20)
|
||||
email: Optional[EmailStr] = None
|
||||
website: Optional[str] = Field(None, max_length=500)
|
||||
logo: Optional[str] = None
|
||||
|
||||
|
||||
class AgencyResponse(BaseModel):
|
||||
id: str
|
||||
user_id: str
|
||||
name: str
|
||||
description: str
|
||||
logo: Optional[str] = None
|
||||
address: str
|
||||
phone: str
|
||||
email: str
|
||||
website: Optional[str] = None
|
||||
verified: bool
|
||||
created_at: datetime
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class AgencyListResponse(BaseModel):
|
||||
agencies: list[AgencyResponse]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int
|
||||
57
app/schemas/auth.py
Normal file
57
app/schemas/auth.py
Normal file
@@ -0,0 +1,57 @@
|
||||
"""Auth-related request / response schemas."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict
|
||||
|
||||
from pydantic import BaseModel, EmailStr, Field
|
||||
|
||||
|
||||
# ── Requests ──────────────────────────────────────────────
|
||||
|
||||
|
||||
class RegisterRequest(BaseModel):
|
||||
email: EmailStr
|
||||
password: str = Field(..., min_length=8, max_length=128)
|
||||
name: str = Field(..., min_length=1, max_length=255)
|
||||
role: str = Field(default="visitor", pattern="^(visitor|agency)$")
|
||||
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
email: EmailStr
|
||||
password: str
|
||||
|
||||
|
||||
class RefreshTokenRequest(BaseModel):
|
||||
refresh_token: str
|
||||
|
||||
|
||||
class PasswordResetRequest(BaseModel):
|
||||
email: EmailStr
|
||||
|
||||
|
||||
class PasswordResetConfirm(BaseModel):
|
||||
new_password: str = Field(..., min_length=8, max_length=128)
|
||||
|
||||
|
||||
class ChangePasswordRequest(BaseModel):
|
||||
current_password: str
|
||||
new_password: str = Field(..., min_length=8, max_length=128)
|
||||
|
||||
|
||||
# ── Responses ─────────────────────────────────────────────
|
||||
|
||||
|
||||
class TokenResponse(BaseModel):
|
||||
access_token: str
|
||||
refresh_token: str
|
||||
token_type: str = "bearer"
|
||||
|
||||
|
||||
class RegisterResponse(BaseModel):
|
||||
message: str
|
||||
user: Dict[str, Any]
|
||||
|
||||
|
||||
class MessageResponse(BaseModel):
|
||||
message: str
|
||||
44
app/schemas/category.py
Normal file
44
app/schemas/category.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""Category request / response schemas."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class CategoryBase(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=100)
|
||||
description: str = Field(..., min_length=1, max_length=500)
|
||||
icon: str = Field(default="tag", max_length=50)
|
||||
slug: str = Field(..., min_length=1, max_length=100, pattern="^[a-z0-9]+(?:-[a-z0-9]+)*$")
|
||||
|
||||
|
||||
class CategoryCreate(CategoryBase):
|
||||
pass
|
||||
|
||||
|
||||
class CategoryUpdate(BaseModel):
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=100)
|
||||
description: Optional[str] = Field(None, min_length=1, max_length=500)
|
||||
icon: Optional[str] = Field(None, max_length=50)
|
||||
slug: Optional[str] = Field(None, min_length=1, max_length=100, pattern="^[a-z0-9]+(?:-[a-z0-9]+)*$")
|
||||
|
||||
|
||||
class CategoryResponse(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
description: str
|
||||
icon: str
|
||||
slug: str
|
||||
listing_count: int = 0
|
||||
created_at: datetime
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class CategoryListResponse(BaseModel):
|
||||
categories: list[CategoryResponse]
|
||||
total: int
|
||||
25
app/schemas/favorite.py
Normal file
25
app/schemas/favorite.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""Favorite / wishlist schemas."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class FavoriteCreate(BaseModel):
|
||||
listing_id: str
|
||||
|
||||
|
||||
class FavoriteResponse(BaseModel):
|
||||
id: str
|
||||
user_id: str
|
||||
listing_id: str
|
||||
created_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class FavoriteListResponse(BaseModel):
|
||||
favorites: list[FavoriteResponse]
|
||||
total: int
|
||||
102
app/schemas/listing.py
Normal file
102
app/schemas/listing.py
Normal file
@@ -0,0 +1,102 @@
|
||||
"""Listing request / response schemas."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
import math
|
||||
|
||||
|
||||
class ListingBase(BaseModel):
|
||||
title: str = Field(..., min_length=1, max_length=255)
|
||||
description: str = Field(..., min_length=1, max_length=5000)
|
||||
price: float = Field(..., gt=0)
|
||||
category_id: str
|
||||
location: str = Field(..., min_length=1, max_length=255)
|
||||
images: list[str] = Field(default_factory=list, max_length=20)
|
||||
listing_type: str = Field(default="sale", pattern="^(sale|rent)$")
|
||||
condition: Optional[str] = Field(None, pattern="^(new|used|refurbished)$")
|
||||
negotiable: bool = False
|
||||
|
||||
@field_validator("price")
|
||||
@classmethod
|
||||
def price_must_be_finite(cls, v: float) -> float:
|
||||
if not math.isfinite(v):
|
||||
raise ValueError("price must be a finite number")
|
||||
return v
|
||||
|
||||
@field_validator("images")
|
||||
@classmethod
|
||||
def images_must_be_http(cls, v: list) -> list:
|
||||
for url in v:
|
||||
if not (url.startswith("http://") or url.startswith("https://")):
|
||||
raise ValueError(f"Image URL must start with http:// or https://: {url!r}")
|
||||
return v
|
||||
|
||||
|
||||
class ListingCreate(ListingBase):
|
||||
pass
|
||||
|
||||
|
||||
class ListingUpdate(BaseModel):
|
||||
title: Optional[str] = Field(None, min_length=1, max_length=255)
|
||||
description: Optional[str] = Field(None, min_length=1, max_length=5000)
|
||||
price: Optional[float] = Field(None, ge=0)
|
||||
category_id: Optional[str] = None
|
||||
location: Optional[str] = Field(None, min_length=1, max_length=255)
|
||||
images: Optional[list[str]] = Field(None, max_length=20)
|
||||
listing_type: Optional[str] = Field(None, pattern="^(sale|rent)$")
|
||||
condition: Optional[str] = Field(None, pattern="^(new|used|refurbished)$")
|
||||
negotiable: Optional[bool] = None
|
||||
|
||||
|
||||
class ListingStatusUpdate(BaseModel):
|
||||
status: str = Field(..., pattern="^(approved|rejected)$")
|
||||
rejection_reason: Optional[str] = Field(None, max_length=1000)
|
||||
|
||||
|
||||
class ListingResponse(BaseModel):
|
||||
id: str
|
||||
title: str
|
||||
description: str
|
||||
price: float
|
||||
images: list[str]
|
||||
status: str
|
||||
agency_id: str
|
||||
category_id: str
|
||||
location: str
|
||||
listing_type: str
|
||||
condition: Optional[str] = None
|
||||
negotiable: bool
|
||||
views_count: int = 0
|
||||
rejection_reason: Optional[str] = None
|
||||
created_at: datetime
|
||||
updated_at: Optional[datetime] = None
|
||||
# Joined fields (optional)
|
||||
agency_name: Optional[str] = None
|
||||
category_name: Optional[str] = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class ListingListResponse(BaseModel):
|
||||
listings: list[ListingResponse]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int
|
||||
|
||||
|
||||
class ListingSearchParams(BaseModel):
|
||||
search: Optional[str] = None
|
||||
category: Optional[str] = None
|
||||
min_price: Optional[float] = None
|
||||
max_price: Optional[float] = None
|
||||
location: Optional[str] = None
|
||||
listing_type: Optional[str] = None
|
||||
condition: Optional[str] = None
|
||||
sort_by: str = Field(default="newest", pattern="^(newest|oldest|price_asc|price_desc|popular)$")
|
||||
status: Optional[str] = None
|
||||
page: int = Field(default=1, ge=1)
|
||||
page_size: int = Field(default=20, ge=1, le=100)
|
||||
43
app/schemas/message.py
Normal file
43
app/schemas/message.py
Normal file
@@ -0,0 +1,43 @@
|
||||
"""Message / contact-form schemas."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, EmailStr, Field
|
||||
|
||||
|
||||
class MessageCreate(BaseModel):
|
||||
listing_id: str
|
||||
name: str = Field(..., min_length=1, max_length=255)
|
||||
email: EmailStr
|
||||
phone: Optional[str] = Field(None, max_length=20)
|
||||
message: str = Field(..., min_length=1, max_length=2000)
|
||||
|
||||
|
||||
class MessageResponse(BaseModel):
|
||||
id: str
|
||||
listing_id: str
|
||||
agency_id: str
|
||||
name: str
|
||||
email: str
|
||||
phone: Optional[str] = None
|
||||
message: str
|
||||
read: bool
|
||||
created_at: datetime
|
||||
# Joined
|
||||
listing_title: Optional[str] = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class MessageListResponse(BaseModel):
|
||||
messages: list[MessageResponse]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int
|
||||
|
||||
|
||||
class MessageMarkRead(BaseModel):
|
||||
read: bool = True
|
||||
64
app/schemas/payment.py
Normal file
64
app/schemas/payment.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""Payment-related Pydantic schemas."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class PaymentInitiate(BaseModel):
|
||||
type: str # 'subscription' | 'purchase'
|
||||
plan: Optional[str] = None # 'monthly' | 'yearly' (subscription only)
|
||||
listing_id: Optional[str] = None # purchase only
|
||||
|
||||
|
||||
class PaymentResponse(BaseModel):
|
||||
id: str
|
||||
transaction_id: str
|
||||
type: str
|
||||
payer_id: Optional[str] = None
|
||||
amount: float
|
||||
currency: str
|
||||
status: str
|
||||
payment_method: Optional[str] = None
|
||||
operator_id: Optional[str] = None
|
||||
metadata: Optional[Dict[str, Any]] = None
|
||||
created_at: datetime
|
||||
paid_at: Optional[datetime] = None
|
||||
|
||||
|
||||
class PaymentInitiateResponse(BaseModel):
|
||||
payment_url: str
|
||||
transaction_id: str
|
||||
|
||||
|
||||
class PaymentReceiptResponse(BaseModel):
|
||||
id: str
|
||||
transaction_id: str
|
||||
type: str
|
||||
amount: float
|
||||
currency: str
|
||||
status: str
|
||||
payment_method: Optional[str] = None
|
||||
operator_id: Optional[str] = None
|
||||
metadata: Optional[Dict[str, Any]] = None
|
||||
created_at: datetime
|
||||
paid_at: Optional[datetime] = None
|
||||
# Enriched fields
|
||||
payer_name: Optional[str] = None
|
||||
payer_email: Optional[str] = None
|
||||
plan_label: Optional[str] = None
|
||||
listing_title: Optional[str] = None
|
||||
|
||||
|
||||
class SubscriptionResponse(BaseModel):
|
||||
id: str
|
||||
agency_id: str
|
||||
plan: str
|
||||
status: str
|
||||
starts_at: datetime
|
||||
ends_at: datetime
|
||||
payment_id: Optional[str] = None
|
||||
created_at: datetime
|
||||
46
app/schemas/user.py
Normal file
46
app/schemas/user.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""User request / response schemas."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, EmailStr, Field
|
||||
|
||||
|
||||
class UserBase(BaseModel):
|
||||
email: EmailStr
|
||||
name: str = Field(..., min_length=1, max_length=255)
|
||||
role: str = Field(default="visitor")
|
||||
|
||||
|
||||
class UserCreate(UserBase):
|
||||
password: str = Field(..., min_length=8, max_length=128)
|
||||
|
||||
|
||||
class UserUpdate(BaseModel):
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=255)
|
||||
email: Optional[EmailStr] = None
|
||||
phone: Optional[str] = Field(None, max_length=20)
|
||||
avatar_url: Optional[str] = None
|
||||
|
||||
|
||||
class UserResponse(BaseModel):
|
||||
id: str
|
||||
email: str
|
||||
name: str
|
||||
role: str
|
||||
verified: bool
|
||||
phone: Optional[str] = None
|
||||
avatar_url: Optional[str] = None
|
||||
created_at: datetime
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class UserListResponse(BaseModel):
|
||||
users: list[UserResponse]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int
|
||||
Reference in New Issue
Block a user