mirror of
http://88.130.71.182:3000/BlitTech/deals24togo_be.git
synced 2026-06-13 09:07:15 +00:00
Initial commit
This commit is contained in:
0
app/api/v1/__init__.py
Normal file
0
app/api/v1/__init__.py
Normal file
0
app/api/v1/endpoints/__init__.py
Normal file
0
app/api/v1/endpoints/__init__.py
Normal file
82
app/api/v1/endpoints/agencies.py
Normal file
82
app/api/v1/endpoints/agencies.py
Normal file
@@ -0,0 +1,82 @@
|
||||
"""Agency CRUD endpoints."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
|
||||
from app.middleware.auth import get_current_user, require_admin, require_agency_or_admin
|
||||
from app.schemas.agency import (
|
||||
AgencyCreate,
|
||||
AgencyListResponse,
|
||||
AgencyResponse,
|
||||
AgencyUpdate,
|
||||
)
|
||||
from app.services.agency_service import AgencyService
|
||||
|
||||
router = APIRouter(prefix="/agencies", tags=["Agencies"])
|
||||
|
||||
|
||||
@router.get("/", response_model=AgencyListResponse)
|
||||
def list_agencies(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
verified_only: bool = Query(False),
|
||||
):
|
||||
svc = AgencyService()
|
||||
return svc.list_agencies(page=page, page_size=page_size, verified_only=verified_only)
|
||||
|
||||
|
||||
@router.get("/me", response_model=AgencyResponse)
|
||||
def get_my_agency(user: dict = Depends(get_current_user)):
|
||||
svc = AgencyService()
|
||||
return svc.get_agency_by_user(user["id"])
|
||||
|
||||
|
||||
@router.get("/{agency_id}", response_model=AgencyResponse)
|
||||
def get_agency(agency_id: str):
|
||||
svc = AgencyService()
|
||||
return svc.get_agency(agency_id)
|
||||
|
||||
|
||||
@router.post("/", response_model=AgencyResponse, status_code=201)
|
||||
def create_agency(body: AgencyCreate, user: dict = Depends(get_current_user)):
|
||||
svc = AgencyService()
|
||||
# Prevent duplicate agencies for the same user
|
||||
try:
|
||||
existing = svc.get_agency_by_user(user["id"])
|
||||
return existing
|
||||
except Exception:
|
||||
pass
|
||||
return svc.create_agency(user["id"], body.model_dump())
|
||||
|
||||
|
||||
@router.patch("/{agency_id}", response_model=AgencyResponse)
|
||||
def update_agency(
|
||||
agency_id: str,
|
||||
body: AgencyUpdate,
|
||||
user: dict = Depends(get_current_user),
|
||||
):
|
||||
svc = AgencyService()
|
||||
return svc.update_agency(
|
||||
agency_id, user["id"], user["role"], body.model_dump(exclude_unset=True)
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{agency_id}/verify", response_model=AgencyResponse)
|
||||
def verify_agency(agency_id: str, admin: dict = Depends(require_admin)):
|
||||
svc = AgencyService()
|
||||
return svc.verify_agency(agency_id, admin["role"])
|
||||
|
||||
|
||||
@router.post("/{agency_id}/revoke", response_model=AgencyResponse)
|
||||
def revoke_agency_verification(agency_id: str, admin: dict = Depends(require_admin)):
|
||||
svc = AgencyService()
|
||||
return svc.revoke_verification(agency_id, admin["role"])
|
||||
|
||||
|
||||
@router.delete("/{agency_id}")
|
||||
def delete_agency(agency_id: str, _admin: dict = Depends(require_admin)):
|
||||
svc = AgencyService()
|
||||
return svc.delete_agency(agency_id)
|
||||
81
app/api/v1/endpoints/auth.py
Normal file
81
app/api/v1/endpoints/auth.py
Normal file
@@ -0,0 +1,81 @@
|
||||
"""Authentication endpoints — register, login, refresh, password reset."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from app.middleware.auth import get_current_user
|
||||
from app.schemas.auth import (
|
||||
ChangePasswordRequest,
|
||||
LoginRequest,
|
||||
MessageResponse,
|
||||
PasswordResetConfirm,
|
||||
PasswordResetRequest,
|
||||
RefreshTokenRequest,
|
||||
RegisterRequest,
|
||||
RegisterResponse,
|
||||
)
|
||||
from app.services.auth_service import AuthService
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["Authentication"])
|
||||
|
||||
|
||||
@router.post("/register", status_code=201, response_model=RegisterResponse)
|
||||
def register(body: RegisterRequest):
|
||||
svc = AuthService()
|
||||
return svc.register(
|
||||
email=body.email,
|
||||
password=body.password,
|
||||
name=body.name,
|
||||
role=body.role,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/login")
|
||||
def login(body: LoginRequest):
|
||||
svc = AuthService()
|
||||
return svc.login(email=body.email, password=body.password)
|
||||
|
||||
|
||||
@router.post("/refresh")
|
||||
def refresh(body: RefreshTokenRequest):
|
||||
svc = AuthService()
|
||||
return svc.refresh(body.refresh_token)
|
||||
|
||||
|
||||
@router.post("/password-reset/request", response_model=MessageResponse)
|
||||
def request_password_reset(body: PasswordResetRequest):
|
||||
svc = AuthService()
|
||||
msg = svc.request_password_reset(body.email)
|
||||
return {"message": msg}
|
||||
|
||||
|
||||
@router.post("/password-reset/confirm", response_model=MessageResponse)
|
||||
def confirm_password_reset(
|
||||
body: PasswordResetConfirm,
|
||||
user: dict = Depends(get_current_user),
|
||||
):
|
||||
svc = AuthService()
|
||||
return svc.reset_password(user["id"], body.new_password)
|
||||
|
||||
|
||||
@router.post("/change-password", response_model=MessageResponse)
|
||||
def change_password(
|
||||
body: ChangePasswordRequest,
|
||||
user: dict = Depends(get_current_user),
|
||||
):
|
||||
svc = AuthService()
|
||||
return svc.change_password(
|
||||
user["id"], user["email"], body.current_password, body.new_password
|
||||
)
|
||||
|
||||
|
||||
@router.post("/resend-verification", response_model=MessageResponse)
|
||||
def resend_verification(user: dict = Depends(get_current_user)):
|
||||
svc = AuthService()
|
||||
return svc.resend_verification(user["email"])
|
||||
|
||||
|
||||
@router.get("/me")
|
||||
def get_me(user: dict = Depends(get_current_user)):
|
||||
return user
|
||||
56
app/api/v1/endpoints/categories.py
Normal file
56
app/api/v1/endpoints/categories.py
Normal file
@@ -0,0 +1,56 @@
|
||||
"""Category CRUD endpoints."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from app.middleware.auth import require_admin
|
||||
from app.schemas.category import (
|
||||
CategoryCreate,
|
||||
CategoryListResponse,
|
||||
CategoryResponse,
|
||||
CategoryUpdate,
|
||||
)
|
||||
from app.services.category_service import CategoryService
|
||||
|
||||
router = APIRouter(prefix="/categories", tags=["Categories"])
|
||||
|
||||
|
||||
@router.get("/", response_model=CategoryListResponse)
|
||||
def list_categories():
|
||||
svc = CategoryService()
|
||||
return svc.list_categories()
|
||||
|
||||
|
||||
@router.get("/{category_id}", response_model=CategoryResponse)
|
||||
def get_category(category_id: str):
|
||||
svc = CategoryService()
|
||||
return svc.get_category(category_id)
|
||||
|
||||
|
||||
@router.get("/slug/{slug}", response_model=CategoryResponse)
|
||||
def get_category_by_slug(slug: str):
|
||||
svc = CategoryService()
|
||||
return svc.get_category_by_slug(slug)
|
||||
|
||||
|
||||
@router.post("/", response_model=CategoryResponse, status_code=201)
|
||||
def create_category(body: CategoryCreate, _admin: dict = Depends(require_admin)):
|
||||
svc = CategoryService()
|
||||
return svc.create_category(body.model_dump())
|
||||
|
||||
|
||||
@router.patch("/{category_id}", response_model=CategoryResponse)
|
||||
def update_category(
|
||||
category_id: str,
|
||||
body: CategoryUpdate,
|
||||
_admin: dict = Depends(require_admin),
|
||||
):
|
||||
svc = CategoryService()
|
||||
return svc.update_category(category_id, body.model_dump(exclude_unset=True))
|
||||
|
||||
|
||||
@router.delete("/{category_id}")
|
||||
def delete_category(category_id: str, _admin: dict = Depends(require_admin)):
|
||||
svc = CategoryService()
|
||||
return svc.delete_category(category_id)
|
||||
35
app/api/v1/endpoints/favorites.py
Normal file
35
app/api/v1/endpoints/favorites.py
Normal file
@@ -0,0 +1,35 @@
|
||||
"""Favorites / wishlist endpoints."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from app.middleware.auth import get_current_user
|
||||
from app.schemas.favorite import FavoriteCreate, FavoriteListResponse, FavoriteResponse
|
||||
from app.services.favorite_service import FavoriteService
|
||||
|
||||
router = APIRouter(prefix="/favorites", tags=["Favorites"])
|
||||
|
||||
|
||||
@router.post("/", response_model=FavoriteResponse, status_code=201)
|
||||
def add_favorite(body: FavoriteCreate, user: dict = Depends(get_current_user)):
|
||||
svc = FavoriteService()
|
||||
return svc.add_favorite(user["id"], body.listing_id)
|
||||
|
||||
|
||||
@router.delete("/{listing_id}")
|
||||
def remove_favorite(listing_id: str, user: dict = Depends(get_current_user)):
|
||||
svc = FavoriteService()
|
||||
return svc.remove_favorite(user["id"], listing_id)
|
||||
|
||||
|
||||
@router.get("/", response_model=FavoriteListResponse)
|
||||
def list_favorites(user: dict = Depends(get_current_user)):
|
||||
svc = FavoriteService()
|
||||
return svc.list_favorites(user["id"])
|
||||
|
||||
|
||||
@router.get("/check/{listing_id}")
|
||||
def check_favorite(listing_id: str, user: dict = Depends(get_current_user)):
|
||||
svc = FavoriteService()
|
||||
return {"is_favorited": svc.is_favorited(user["id"], listing_id)}
|
||||
185
app/api/v1/endpoints/listings.py
Normal file
185
app/api/v1/endpoints/listings.py
Normal file
@@ -0,0 +1,185 @@
|
||||
"""Listing CRUD + search endpoints."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, Query
|
||||
|
||||
from app.middleware.auth import get_current_user, get_optional_user, require_admin
|
||||
from app.schemas.listing import (
|
||||
ListingCreate,
|
||||
ListingListResponse,
|
||||
ListingResponse,
|
||||
ListingStatusUpdate,
|
||||
ListingUpdate,
|
||||
)
|
||||
from app.services.agency_service import AgencyService
|
||||
from app.services.listing_service import ListingService
|
||||
|
||||
router = APIRouter(prefix="/listings", tags=["Listings"])
|
||||
|
||||
|
||||
# ── Public ───────────────────────────────────────────────
|
||||
|
||||
|
||||
@router.get("/", response_model=ListingListResponse)
|
||||
def list_listings(
|
||||
search: Optional[str] = None,
|
||||
category: Optional[str] = None,
|
||||
agency_id: 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 = Query("newest"),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
):
|
||||
svc = ListingService()
|
||||
return svc.list_listings(
|
||||
search=search,
|
||||
category=category,
|
||||
agency_id=agency_id,
|
||||
min_price=min_price,
|
||||
max_price=max_price,
|
||||
location=location,
|
||||
listing_type=listing_type,
|
||||
condition=condition,
|
||||
sort_by=sort_by,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
status="approved",
|
||||
)
|
||||
|
||||
|
||||
@router.get("/featured", response_model=ListingListResponse)
|
||||
def featured_listings():
|
||||
"""Return top 8 most viewed approved listings."""
|
||||
svc = ListingService()
|
||||
return svc.list_listings(
|
||||
sort_by="popular",
|
||||
page=1,
|
||||
page_size=8,
|
||||
status="approved",
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{listing_id}", response_model=ListingResponse)
|
||||
def get_listing(
|
||||
listing_id: str,
|
||||
background_tasks: BackgroundTasks,
|
||||
user: Optional[dict] = Depends(get_optional_user),
|
||||
):
|
||||
svc = ListingService()
|
||||
listing = svc.get_listing(listing_id)
|
||||
background_tasks.add_task(svc.increment_views, listing_id)
|
||||
return listing
|
||||
|
||||
|
||||
# ── Agency ───────────────────────────────────────────────
|
||||
|
||||
|
||||
@router.get("/agency/mine", response_model=ListingListResponse)
|
||||
def my_listings(
|
||||
status: Optional[str] = None,
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
user: dict = Depends(get_current_user),
|
||||
):
|
||||
agency_svc = AgencyService()
|
||||
agency = agency_svc.get_agency_by_user(user["id"])
|
||||
|
||||
svc = ListingService()
|
||||
return svc.list_listings(
|
||||
agency_id=agency["id"],
|
||||
status=status,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/", response_model=ListingResponse, status_code=201)
|
||||
def create_listing(body: ListingCreate, user: dict = Depends(get_current_user)):
|
||||
agency_svc = AgencyService()
|
||||
agency = agency_svc.get_agency_by_user(user["id"])
|
||||
|
||||
svc = ListingService()
|
||||
return svc.create_listing(agency["id"], body.model_dump())
|
||||
|
||||
|
||||
@router.patch("/{listing_id}", response_model=ListingResponse)
|
||||
def update_listing(
|
||||
listing_id: str,
|
||||
body: ListingUpdate,
|
||||
user: dict = Depends(get_current_user),
|
||||
):
|
||||
svc = ListingService()
|
||||
return svc.update_listing(
|
||||
listing_id, user["id"], user["role"], body.model_dump(exclude_unset=True)
|
||||
)
|
||||
|
||||
|
||||
@router.delete("/{listing_id}")
|
||||
def delete_listing(listing_id: str, user: dict = Depends(get_current_user)):
|
||||
svc = ListingService()
|
||||
return svc.delete_listing(listing_id, user["id"], user["role"])
|
||||
|
||||
|
||||
# ── Admin ────────────────────────────────────────────────
|
||||
|
||||
|
||||
@router.get("/admin/all", response_model=ListingListResponse)
|
||||
def admin_list_all(
|
||||
status: Optional[str] = None,
|
||||
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,
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
_admin: dict = Depends(require_admin),
|
||||
):
|
||||
svc = ListingService()
|
||||
return svc.list_listings(
|
||||
search=search,
|
||||
status=status,
|
||||
category=category,
|
||||
min_price=min_price,
|
||||
max_price=max_price,
|
||||
location=location,
|
||||
listing_type=listing_type,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
|
||||
@router.patch("/{listing_id}/status", response_model=ListingResponse)
|
||||
def update_listing_status(
|
||||
listing_id: str,
|
||||
body: ListingStatusUpdate,
|
||||
_admin: dict = Depends(require_admin),
|
||||
):
|
||||
svc = ListingService()
|
||||
return svc.update_status(listing_id, body.status, body.rejection_reason)
|
||||
|
||||
|
||||
@router.get("/stats/overview")
|
||||
def listing_stats(
|
||||
agency_id: Optional[str] = None,
|
||||
user: dict = Depends(get_current_user),
|
||||
):
|
||||
svc = ListingService()
|
||||
if user["role"] != "admin":
|
||||
# Non-admins always see their own agency's stats only
|
||||
agency_svc = AgencyService()
|
||||
try:
|
||||
agency = agency_svc.get_agency_by_user(user["id"])
|
||||
agency_id = agency["id"]
|
||||
except Exception:
|
||||
return {"total": 0, "pending": 0, "approved": 0, "rejected": 0}
|
||||
|
||||
return svc.get_stats(agency_id)
|
||||
74
app/api/v1/endpoints/messages.py
Normal file
74
app/api/v1/endpoints/messages.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""Contact / message endpoints."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
|
||||
from app.middleware.auth import get_current_user
|
||||
from app.schemas.message import (
|
||||
MessageCreate,
|
||||
MessageListResponse,
|
||||
MessageMarkRead,
|
||||
MessageResponse,
|
||||
)
|
||||
from app.services.agency_service import AgencyService
|
||||
from app.services.message_service import MessageService
|
||||
|
||||
router = APIRouter(prefix="/messages", tags=["Messages"])
|
||||
|
||||
|
||||
@router.post("/", response_model=MessageResponse, status_code=201)
|
||||
def send_message(body: MessageCreate):
|
||||
"""Public endpoint — anyone can send a message about a listing."""
|
||||
svc = MessageService()
|
||||
return svc.send_message(body.model_dump())
|
||||
|
||||
|
||||
@router.get("/", response_model=MessageListResponse)
|
||||
def list_messages(
|
||||
read: Optional[bool] = None,
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""List messages for the current user's agency."""
|
||||
agency_svc = AgencyService()
|
||||
agency = agency_svc.get_agency_by_user(user["id"])
|
||||
|
||||
svc = MessageService()
|
||||
return svc.list_messages(
|
||||
agency_id=agency["id"],
|
||||
user_id=user["id"],
|
||||
user_role=user["role"],
|
||||
read_filter=read,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/unread-count")
|
||||
def unread_count(user: dict = Depends(get_current_user)):
|
||||
agency_svc = AgencyService()
|
||||
agency = agency_svc.get_agency_by_user(user["id"])
|
||||
|
||||
svc = MessageService()
|
||||
count = svc.get_unread_count(agency["id"])
|
||||
return {"unread_count": count}
|
||||
|
||||
|
||||
@router.patch("/{message_id}/read", response_model=MessageResponse)
|
||||
def mark_message_read(
|
||||
message_id: str,
|
||||
body: MessageMarkRead,
|
||||
user: dict = Depends(get_current_user),
|
||||
):
|
||||
svc = MessageService()
|
||||
return svc.mark_read(message_id, user["id"], user["role"], body.read)
|
||||
|
||||
|
||||
@router.delete("/{message_id}")
|
||||
def delete_message(message_id: str, user: dict = Depends(get_current_user)):
|
||||
svc = MessageService()
|
||||
return svc.delete_message(message_id, user["id"], user["role"])
|
||||
80
app/api/v1/endpoints/payments.py
Normal file
80
app/api/v1/endpoints/payments.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""Payment endpoints — subscription and purchase flows via CinetPay."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends, Request, Response
|
||||
|
||||
from app.middleware.auth import get_current_user, require_agency
|
||||
from app.schemas.payment import (
|
||||
PaymentInitiate,
|
||||
PaymentInitiateResponse,
|
||||
PaymentReceiptResponse,
|
||||
PaymentResponse,
|
||||
SubscriptionResponse,
|
||||
)
|
||||
from app.services.payment_service import PaymentService
|
||||
|
||||
router = APIRouter(tags=["Payments"])
|
||||
|
||||
|
||||
# ── Payments ─────────────────────────────────────────────
|
||||
|
||||
|
||||
@router.post("/payments/initiate", response_model=PaymentInitiateResponse, status_code=201)
|
||||
def initiate_payment(
|
||||
body: PaymentInitiate,
|
||||
user: dict = Depends(get_current_user),
|
||||
):
|
||||
svc = PaymentService()
|
||||
return svc.initiate(
|
||||
user_id=user["id"],
|
||||
payment_type=body.type,
|
||||
plan=body.plan,
|
||||
listing_id=body.listing_id,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/payments/webhook")
|
||||
async def cinetpay_webhook(request: Request):
|
||||
"""Receive CinetPay webhook — no auth required. Returns 200 immediately."""
|
||||
try:
|
||||
form = await request.form()
|
||||
form_data = dict(form)
|
||||
except Exception:
|
||||
form_data = {}
|
||||
|
||||
# Also accept query params (CinetPay may use GET in some flows)
|
||||
if not form_data:
|
||||
form_data = dict(request.query_params)
|
||||
|
||||
svc = PaymentService()
|
||||
try:
|
||||
svc.handle_webhook(form_data)
|
||||
except Exception:
|
||||
pass # Always return 200 to CinetPay
|
||||
|
||||
return Response(status_code=200)
|
||||
|
||||
|
||||
@router.get("/payments/", response_model=list[PaymentResponse])
|
||||
def list_my_payments(user: dict = Depends(get_current_user)):
|
||||
svc = PaymentService()
|
||||
return svc.get_my_payments(user["id"])
|
||||
|
||||
|
||||
@router.get("/payments/{transaction_id}", response_model=PaymentReceiptResponse)
|
||||
def get_payment_receipt(
|
||||
transaction_id: str,
|
||||
user: dict = Depends(get_current_user),
|
||||
):
|
||||
svc = PaymentService()
|
||||
return svc.get_receipt(transaction_id, user["id"])
|
||||
|
||||
|
||||
# ── Subscriptions ─────────────────────────────────────────
|
||||
|
||||
|
||||
@router.get("/subscriptions/me")
|
||||
def my_subscription(user: dict = Depends(require_agency)):
|
||||
svc = PaymentService()
|
||||
return svc.get_subscription_status(user["id"])
|
||||
53
app/api/v1/endpoints/uploads.py
Normal file
53
app/api/v1/endpoints/uploads.py
Normal file
@@ -0,0 +1,53 @@
|
||||
"""File upload endpoints."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends, File, UploadFile
|
||||
|
||||
from app.middleware.auth import get_current_user
|
||||
from app.services.upload_service import UploadService
|
||||
|
||||
router = APIRouter(prefix="/uploads", tags=["Uploads"])
|
||||
|
||||
|
||||
@router.post("/image")
|
||||
async def upload_image(
|
||||
file: UploadFile = File(...),
|
||||
user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Upload an image and return its public URL."""
|
||||
contents = await file.read()
|
||||
svc = UploadService()
|
||||
url = svc.upload_image(
|
||||
file_bytes=contents,
|
||||
content_type=file.content_type or "image/jpeg",
|
||||
folder=f"users/{user['id']}",
|
||||
)
|
||||
return {"url": url}
|
||||
|
||||
|
||||
@router.post("/images")
|
||||
async def upload_multiple_images(
|
||||
files: list[UploadFile] = File(...),
|
||||
user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Upload multiple images and return their public URLs."""
|
||||
svc = UploadService()
|
||||
urls = []
|
||||
for f in files:
|
||||
contents = await f.read()
|
||||
url = svc.upload_image(
|
||||
file_bytes=contents,
|
||||
content_type=f.content_type or "image/jpeg",
|
||||
folder=f"users/{user['id']}",
|
||||
)
|
||||
urls.append(url)
|
||||
return {"urls": urls}
|
||||
|
||||
|
||||
@router.delete("/")
|
||||
async def delete_image(url: str, user: dict = Depends(get_current_user)):
|
||||
"""Delete an image by URL."""
|
||||
svc = UploadService()
|
||||
success = svc.delete_image(url)
|
||||
return {"deleted": success}
|
||||
55
app/api/v1/endpoints/users.py
Normal file
55
app/api/v1/endpoints/users.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""User management endpoints."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
|
||||
from app.middleware.auth import get_current_user, require_admin
|
||||
from app.schemas.user import UserListResponse, UserResponse, UserUpdate
|
||||
from app.services.user_service import UserService
|
||||
|
||||
router = APIRouter(prefix="/users", tags=["Users"])
|
||||
|
||||
|
||||
@router.get("/me", response_model=UserResponse)
|
||||
def get_my_profile(user: dict = Depends(get_current_user)):
|
||||
svc = UserService()
|
||||
return svc.get_user(user["id"])
|
||||
|
||||
|
||||
@router.patch("/me", response_model=UserResponse)
|
||||
def update_my_profile(body: UserUpdate, user: dict = Depends(get_current_user)):
|
||||
svc = UserService()
|
||||
return svc.update_user(user["id"], body.model_dump(exclude_unset=True))
|
||||
|
||||
|
||||
@router.get("/{user_id}", response_model=UserResponse)
|
||||
def get_user(user_id: str, _admin: dict = Depends(require_admin)):
|
||||
svc = UserService()
|
||||
return svc.get_user(user_id)
|
||||
|
||||
|
||||
@router.get("/", response_model=UserListResponse)
|
||||
def list_users(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
role: Optional[str] = None,
|
||||
search: Optional[str] = None,
|
||||
_admin: dict = Depends(require_admin),
|
||||
):
|
||||
svc = UserService()
|
||||
return svc.list_users(page=page, page_size=page_size, role=role, search=search)
|
||||
|
||||
|
||||
@router.post("/{user_id}/verify", response_model=UserResponse)
|
||||
def verify_user(user_id: str, _admin: dict = Depends(require_admin)):
|
||||
svc = UserService()
|
||||
return svc.verify_user(user_id)
|
||||
|
||||
|
||||
@router.delete("/{user_id}")
|
||||
def delete_user(user_id: str, user: dict = Depends(get_current_user)):
|
||||
svc = UserService()
|
||||
return svc.delete_user(user_id, user["id"], user["role"])
|
||||
27
app/api/v1/router.py
Normal file
27
app/api/v1/router.py
Normal file
@@ -0,0 +1,27 @@
|
||||
"""Aggregate all v1 routers."""
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.api.v1.endpoints import (
|
||||
agencies,
|
||||
auth,
|
||||
categories,
|
||||
favorites,
|
||||
listings,
|
||||
messages,
|
||||
payments,
|
||||
uploads,
|
||||
users,
|
||||
)
|
||||
|
||||
api_router = APIRouter(prefix="/api/v1")
|
||||
|
||||
api_router.include_router(auth.router)
|
||||
api_router.include_router(users.router)
|
||||
api_router.include_router(agencies.router)
|
||||
api_router.include_router(categories.router)
|
||||
api_router.include_router(listings.router)
|
||||
api_router.include_router(messages.router)
|
||||
api_router.include_router(favorites.router)
|
||||
api_router.include_router(uploads.router)
|
||||
api_router.include_router(payments.router)
|
||||
Reference in New Issue
Block a user