refactor: enforce strict architecture rules and add Pydantic response models

- Update architecture rules to be stricter (API-003 now blocks ALL exception
  raising in endpoints, not just HTTPException)
- Update get_current_vendor_api dependency to guarantee token_vendor_id presence
- Remove redundant _get_vendor_from_token helpers from all vendor API files
- Move vendor access validation to service layer methods
- Add Pydantic response models for media, notification, and payment endpoints
- Add get_active_vendor_by_code service method for public vendor lookup
- Add get_import_job_for_vendor service method with vendor validation
- Update validation script to detect exception raising patterns in endpoints

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-04 23:26:03 +01:00
parent cbfbbb4654
commit 81bfc49f77
25 changed files with 1225 additions and 530 deletions

View File

@@ -275,7 +275,9 @@ def get_current_vendor_api(
Get current vendor user from Authorization header ONLY.
Used for vendor API endpoints that should not accept cookies.
Validates that user still has access to the vendor specified in the token.
Validates that:
1. Token contains vendor context (token_vendor_id)
2. User still has access to the vendor specified in the token
Args:
credentials: Bearer token from Authorization header
@@ -285,7 +287,7 @@ def get_current_vendor_api(
User: Authenticated vendor user (with token_vendor_id, token_vendor_code, token_vendor_role)
Raises:
InvalidTokenException: If no token or invalid token
InvalidTokenException: If no token, invalid token, or missing vendor context
InsufficientPermissionsException: If user is not vendor or lost access to vendor
"""
if not credentials:
@@ -302,23 +304,25 @@ def get_current_vendor_api(
logger.warning(f"Non-vendor user {user.username} attempted vendor API")
raise InsufficientPermissionsException("Vendor privileges required")
# Validate vendor access if token is vendor-scoped
if hasattr(user, "token_vendor_id"):
vendor_id = user.token_vendor_id
# Require vendor context in token
if not hasattr(user, "token_vendor_id"):
raise InvalidTokenException("Token missing vendor information. Please login again.")
# Verify user still has access to this vendor
if not user.is_member_of(vendor_id):
logger.warning(
f"User {user.username} lost access to vendor_id={vendor_id}"
)
raise InsufficientPermissionsException(
"Access to vendor has been revoked. Please login again."
)
vendor_id = user.token_vendor_id
logger.debug(
f"Vendor API access: user={user.username}, vendor_id={vendor_id}, "
f"vendor_code={getattr(user, 'token_vendor_code', 'N/A')}"
# Verify user still has access to this vendor
if not user.is_member_of(vendor_id):
logger.warning(
f"User {user.username} lost access to vendor_id={vendor_id}"
)
raise InsufficientPermissionsException(
"Access to vendor has been revoked. Please login again."
)
logger.debug(
f"Vendor API access: user={user.username}, vendor_id={vendor_id}, "
f"vendor_code={getattr(user, 'token_vendor_code', 'N/A')}"
)
return user

View File

@@ -21,7 +21,11 @@ from models.schema.admin import (
PlatformAlertCreate,
PlatformAlertListResponse,
PlatformAlertResolve,
PlatformAlertResponse,
)
from models.schema.notification import (
AlertStatisticsResponse,
MessageResponse,
UnreadCountResponse,
)
router = APIRouter(prefix="/notifications")
@@ -49,17 +53,17 @@ def get_notifications(
)
@router.get("/unread-count")
@router.get("/unread-count", response_model=UnreadCountResponse)
def get_unread_count(
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_api),
):
"""Get count of unread notifications."""
# TODO: Implement
return {"unread_count": 0}
return UnreadCountResponse(unread_count=0)
@router.put("/{notification_id}/read")
@router.put("/{notification_id}/read", response_model=MessageResponse)
def mark_as_read(
notification_id: int,
db: Session = Depends(get_db),
@@ -67,17 +71,17 @@ def mark_as_read(
):
"""Mark notification as read."""
# TODO: Implement
return {"message": "Notification marked as read"}
return MessageResponse(message="Notification marked as read")
@router.put("/mark-all-read")
@router.put("/mark-all-read", response_model=MessageResponse)
def mark_all_as_read(
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_api),
):
"""Mark all notifications as read."""
# TODO: Implement
return {"message": "All notifications marked as read"}
return MessageResponse(message="All notifications marked as read")
# ============================================================================
@@ -101,19 +105,19 @@ def get_platform_alerts(
)
@router.post("/alerts", response_model=PlatformAlertResponse)
@router.post("/alerts", response_model=MessageResponse)
def create_platform_alert(
alert_data: PlatformAlertCreate,
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_api),
):
"""Create new platform alert (manual)."""
# TODO: Implement
# TODO: Implement - return PlatformAlertResponse when service is ready
logger.info(f"Admin {current_admin.username} created alert: {alert_data.title}")
return {}
return MessageResponse(message="Platform alert creation coming soon")
@router.put("/alerts/{alert_id}/resolve")
@router.put("/alerts/{alert_id}/resolve", response_model=MessageResponse)
def resolve_platform_alert(
alert_id: int,
resolve_data: PlatformAlertResolve,
@@ -123,19 +127,19 @@ def resolve_platform_alert(
"""Resolve platform alert."""
# TODO: Implement
logger.info(f"Admin {current_admin.username} resolved alert {alert_id}")
return {"message": "Alert resolved successfully"}
return MessageResponse(message="Alert resolved successfully")
@router.get("/alerts/stats")
@router.get("/alerts/stats", response_model=AlertStatisticsResponse)
def get_alert_statistics(
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_api),
):
"""Get alert statistics for dashboard."""
# TODO: Implement
return {
"total_alerts": 0,
"active_alerts": 0,
"critical_alerts": 0,
"resolved_today": 0,
}
return AlertStatisticsResponse(
total_alerts=0,
active_alerts=0,
critical_alerts=0,
resolved_today=0,
)

View File

@@ -2,7 +2,8 @@
"""
Vendor analytics and reporting endpoints.
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern)
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern).
The get_current_vendor_api dependency guarantees token_vendor_id is present.
"""
import logging
@@ -12,7 +13,6 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api
from app.core.database import get_db
from app.exceptions import InvalidTokenException
from app.services.stats_service import stats_service
from models.database.user import User
@@ -20,13 +20,6 @@ router = APIRouter(prefix="/analytics")
logger = logging.getLogger(__name__)
def _get_vendor_id_from_token(current_user: User) -> int:
"""Helper to get vendor_id from JWT token."""
if not hasattr(current_user, "token_vendor_id"):
raise InvalidTokenException("Token missing vendor information. Please login again.")
return current_user.token_vendor_id
@router.get("")
def get_vendor_analytics(
period: str = Query("30d", description="Time period: 7d, 30d, 90d, 1y"),
@@ -34,5 +27,4 @@ def get_vendor_analytics(
db: Session = Depends(get_db),
):
"""Get vendor analytics data for specified time period."""
vendor_id = _get_vendor_id_from_token(current_user)
return stats_service.get_vendor_analytics(db, vendor_id, period)
return stats_service.get_vendor_analytics(db, current_user.token_vendor_id, period)

View File

@@ -1,9 +1,9 @@
# Vendor customer management
# app/api/v1/vendor/customers.py
"""
Vendor customer management endpoints.
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern)
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern).
The get_current_vendor_api dependency guarantees token_vendor_id is present.
"""
import logging
@@ -13,7 +13,6 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api
from app.core.database import get_db
from app.exceptions import InvalidTokenException
from app.services.vendor_service import vendor_service
from models.database.user import User
@@ -21,13 +20,6 @@ router = APIRouter(prefix="/customers")
logger = logging.getLogger(__name__)
def _get_vendor_from_token(current_user: User, db: Session):
"""Helper to get vendor from JWT token."""
if not hasattr(current_user, "token_vendor_id"):
raise InvalidTokenException("Token missing vendor information. Please login again.")
return vendor_service.get_vendor_by_id(db, current_user.token_vendor_id)
@router.get("")
def get_vendor_customers(
skip: int = Query(0, ge=0),
@@ -46,7 +38,7 @@ def get_vendor_customers(
- Support filtering by active status
- Return paginated results
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return {
"customers": [],
"total": 0,
@@ -71,7 +63,7 @@ def get_customer_details(
- Include order history
- Include total spent, etc.
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return {"message": "Customer details coming in Slice 4"}
@@ -89,7 +81,7 @@ def get_customer_orders(
- Filter by vendor_id
- Return order details
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return {"orders": [], "message": "Customer orders coming in Slice 5"}
@@ -108,7 +100,7 @@ def update_customer(
- Verify customer belongs to vendor
- Update customer preferences
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return {"message": "Customer update coming in Slice 4"}
@@ -126,7 +118,7 @@ def toggle_customer_status(
- Verify customer belongs to vendor
- Log the change
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return {"message": "Customer status toggle coming in Slice 4"}
@@ -145,7 +137,7 @@ def get_customer_statistics(
- Average order value
- Last order date
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return {
"total_orders": 0,
"total_spent": 0.0,

View File

@@ -1,6 +1,9 @@
# app/api/v1/vendor/dashboard.py
"""
Vendor dashboard and statistics endpoints.
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern).
The get_current_vendor_api dependency guarantees token_vendor_id is present.
"""
import logging
@@ -10,7 +13,7 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api
from app.core.database import get_db
from app.exceptions import InvalidTokenException, VendorNotActiveException
from app.exceptions import VendorNotActiveException
from app.services.stats_service import stats_service
from app.services.vendor_service import vendor_service
from models.database.user import User
@@ -37,10 +40,6 @@ def get_vendor_dashboard_stats(
Vendor is determined from the JWT token (vendor_id claim).
Requires Authorization header (API endpoint).
"""
# Get vendor ID from token (set by get_current_vendor_api)
if not hasattr(current_user, "token_vendor_id"):
raise InvalidTokenException("Token missing vendor information. Please login again.")
vendor_id = current_user.token_vendor_id
# Get vendor object (raises VendorNotFoundException if not found)

View File

@@ -10,48 +10,16 @@ This module provides:
import logging
from fastapi import APIRouter, Depends, Path
from sqlalchemy import func
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.exceptions import VendorNotFoundException
from models.database.vendor import Vendor
from app.services.vendor_service import vendor_service
from models.schema.vendor import VendorDetailResponse
router = APIRouter()
logger = logging.getLogger(__name__)
def _get_vendor_by_code(db: Session, vendor_code: str) -> Vendor:
"""
Helper to get active vendor by vendor_code.
Args:
db: Database session
vendor_code: Vendor code (case-insensitive)
Returns:
Vendor object
Raises:
VendorNotFoundException: If vendor not found or inactive
"""
vendor = (
db.query(Vendor)
.filter(
func.upper(Vendor.vendor_code) == vendor_code.upper(),
Vendor.is_active == True,
)
.first()
)
if not vendor:
logger.warning(f"Vendor not found or inactive: {vendor_code}")
raise VendorNotFoundException(vendor_code, identifier_type="code")
return vendor
@router.get("/{vendor_code}", response_model=VendorDetailResponse)
def get_vendor_info(
vendor_code: str = Path(..., description="Vendor code"),
@@ -81,7 +49,7 @@ def get_vendor_info(
"""
logger.info(f"Public vendor info request: {vendor_code}")
vendor = _get_vendor_by_code(db, vendor_code)
vendor = vendor_service.get_active_vendor_by_code(db, vendor_code)
logger.info(f"Vendor info retrieved: {vendor.name} ({vendor.vendor_code})")

View File

@@ -2,7 +2,8 @@
"""
Vendor inventory management endpoints.
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern)
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern).
The get_current_vendor_api dependency guarantees token_vendor_id is present.
"""
import logging
@@ -11,7 +12,6 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api
from app.core.database import get_db
from app.exceptions import InvalidTokenException
from app.services.inventory_service import inventory_service
from models.database.user import User
from models.schema.inventory import (
@@ -28,13 +28,6 @@ router = APIRouter()
logger = logging.getLogger(__name__)
def _get_vendor_id_from_token(current_user: User) -> int:
"""Helper to get vendor_id from JWT token."""
if not hasattr(current_user, "token_vendor_id"):
raise InvalidTokenException("Token missing vendor information. Please login again.")
return current_user.token_vendor_id
@router.post("/inventory/set", response_model=InventoryResponse)
def set_inventory(
inventory: InventoryCreate,
@@ -42,8 +35,7 @@ def set_inventory(
db: Session = Depends(get_db),
):
"""Set exact inventory quantity (replaces existing)."""
vendor_id = _get_vendor_id_from_token(current_user)
return inventory_service.set_inventory(db, vendor_id, inventory)
return inventory_service.set_inventory(db, current_user.token_vendor_id, inventory)
@router.post("/inventory/adjust", response_model=InventoryResponse)
@@ -53,8 +45,7 @@ def adjust_inventory(
db: Session = Depends(get_db),
):
"""Adjust inventory (positive to add, negative to remove)."""
vendor_id = _get_vendor_id_from_token(current_user)
return inventory_service.adjust_inventory(db, vendor_id, adjustment)
return inventory_service.adjust_inventory(db, current_user.token_vendor_id, adjustment)
@router.post("/inventory/reserve", response_model=InventoryResponse)
@@ -64,8 +55,7 @@ def reserve_inventory(
db: Session = Depends(get_db),
):
"""Reserve inventory for an order."""
vendor_id = _get_vendor_id_from_token(current_user)
return inventory_service.reserve_inventory(db, vendor_id, reservation)
return inventory_service.reserve_inventory(db, current_user.token_vendor_id, reservation)
@router.post("/inventory/release", response_model=InventoryResponse)
@@ -75,8 +65,7 @@ def release_reservation(
db: Session = Depends(get_db),
):
"""Release reserved inventory (cancel order)."""
vendor_id = _get_vendor_id_from_token(current_user)
return inventory_service.release_reservation(db, vendor_id, reservation)
return inventory_service.release_reservation(db, current_user.token_vendor_id, reservation)
@router.post("/inventory/fulfill", response_model=InventoryResponse)
@@ -86,8 +75,7 @@ def fulfill_reservation(
db: Session = Depends(get_db),
):
"""Fulfill reservation (complete order, remove from stock)."""
vendor_id = _get_vendor_id_from_token(current_user)
return inventory_service.fulfill_reservation(db, vendor_id, reservation)
return inventory_service.fulfill_reservation(db, current_user.token_vendor_id, reservation)
@router.get("/inventory/product/{product_id}", response_model=ProductInventorySummary)
@@ -97,8 +85,7 @@ def get_product_inventory(
db: Session = Depends(get_db),
):
"""Get inventory summary for a product."""
vendor_id = _get_vendor_id_from_token(current_user)
return inventory_service.get_product_inventory(db, vendor_id, product_id)
return inventory_service.get_product_inventory(db, current_user.token_vendor_id, product_id)
@router.get("/inventory", response_model=InventoryListResponse)
@@ -111,9 +98,8 @@ def get_vendor_inventory(
db: Session = Depends(get_db),
):
"""Get all inventory for vendor."""
vendor_id = _get_vendor_id_from_token(current_user)
inventories = inventory_service.get_vendor_inventory(
db, vendor_id, skip, limit, location, low_stock
db, current_user.token_vendor_id, skip, limit, location, low_stock
)
# Get total count
@@ -132,9 +118,8 @@ def update_inventory(
db: Session = Depends(get_db),
):
"""Update inventory entry."""
vendor_id = _get_vendor_id_from_token(current_user)
return inventory_service.update_inventory(
db, vendor_id, inventory_id, inventory_update
db, current_user.token_vendor_id, inventory_id, inventory_update
)
@@ -145,6 +130,5 @@ def delete_inventory(
db: Session = Depends(get_db),
):
"""Delete inventory entry."""
vendor_id = _get_vendor_id_from_token(current_user)
inventory_service.delete_inventory(db, vendor_id, inventory_id)
inventory_service.delete_inventory(db, current_user.token_vendor_id, inventory_id)
return {"message": "Inventory deleted successfully"}

View File

@@ -2,7 +2,8 @@
"""
Marketplace import endpoints for vendors.
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern)
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern).
The get_current_vendor_api dependency guarantees token_vendor_id is present.
"""
import logging
@@ -12,7 +13,6 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api
from app.core.database import get_db
from app.exceptions import InvalidTokenException, UnauthorizedVendorAccessException
from app.services.marketplace_import_job_service import marketplace_import_job_service
from app.services.vendor_service import vendor_service
from app.tasks.background_tasks import process_marketplace_import
@@ -27,13 +27,6 @@ router = APIRouter(prefix="/marketplace")
logger = logging.getLogger(__name__)
def _get_vendor_from_token(current_user: User, db: Session):
"""Helper to get vendor from JWT token."""
if not hasattr(current_user, "token_vendor_id"):
raise InvalidTokenException("Token missing vendor information. Please login again.")
return vendor_service.get_vendor_by_id(db, current_user.token_vendor_id)
@router.post("/import", response_model=MarketplaceImportJobResponse)
@rate_limit(max_requests=10, window_seconds=3600)
async def import_products_from_marketplace(
@@ -43,7 +36,7 @@ async def import_products_from_marketplace(
db: Session = Depends(get_db),
):
"""Import products from marketplace CSV with background processing (Protected)."""
vendor = _get_vendor_from_token(current_user, db)
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id)
logger.info(
f"Starting marketplace import: {request.marketplace} for vendor {vendor.vendor_code} "
@@ -90,13 +83,10 @@ def get_marketplace_import_status(
db: Session = Depends(get_db),
):
"""Get status of marketplace import job (Protected)."""
vendor = _get_vendor_from_token(current_user, db)
job = marketplace_import_job_service.get_import_job_by_id(db, job_id, current_user)
# Verify job belongs to current vendor
if job.vendor_id != vendor.id:
raise UnauthorizedVendorAccessException(vendor.vendor_code, current_user.id)
# Service validates that job belongs to vendor and raises UnauthorizedVendorAccessException if not
job = marketplace_import_job_service.get_import_job_for_vendor(
db, job_id, current_user.token_vendor_id
)
return marketplace_import_job_service.convert_to_response_model(job)
@@ -110,7 +100,7 @@ def get_marketplace_import_jobs(
db: Session = Depends(get_db),
):
"""Get marketplace import jobs for current vendor (Protected)."""
vendor = _get_vendor_from_token(current_user, db)
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id)
jobs = marketplace_import_job_service.get_import_jobs(
db=db,

View File

@@ -1,9 +1,9 @@
# File and media management
# app/api/v1/vendor/media.py
"""
Vendor media and file management endpoints.
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern)
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern).
The get_current_vendor_api dependency guarantees token_vendor_id is present.
"""
import logging
@@ -13,22 +13,23 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api
from app.core.database import get_db
from app.exceptions import InvalidTokenException
from app.services.vendor_service import vendor_service
from models.database.user import User
from models.schema.media import (
MediaDetailResponse,
MediaListResponse,
MediaMetadataUpdate,
MediaUploadResponse,
MediaUsageResponse,
MultipleUploadResponse,
OptimizationResultResponse,
)
router = APIRouter(prefix="/media")
logger = logging.getLogger(__name__)
def _get_vendor_from_token(current_user: User, db: Session):
"""Helper to get vendor from JWT token."""
if not hasattr(current_user, "token_vendor_id"):
raise InvalidTokenException("Token missing vendor information. Please login again.")
return vendor_service.get_vendor_by_id(db, current_user.token_vendor_id)
@router.get("")
@router.get("", response_model=MediaListResponse)
def get_media_library(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
@@ -47,17 +48,17 @@ def get_media_library(
- Support pagination
- Return file URLs, sizes, metadata
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {
"media": [],
"total": 0,
"skip": skip,
"limit": limit,
"message": "Media library coming in Slice 3",
}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return MediaListResponse(
media=[],
total=0,
skip=skip,
limit=limit,
message="Media library coming in Slice 3",
)
@router.post("/upload")
@router.post("/upload", response_model=MediaUploadResponse)
async def upload_media(
file: UploadFile = File(...),
folder: str | None = Query(None, description="products, general, etc."),
@@ -75,15 +76,15 @@ async def upload_media(
- Save metadata to database
- Return file URL
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {
"file_url": None,
"thumbnail_url": None,
"message": "Media upload coming in Slice 3",
}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return MediaUploadResponse(
file_url=None,
thumbnail_url=None,
message="Media upload coming in Slice 3",
)
@router.post("/upload/multiple")
@router.post("/upload/multiple", response_model=MultipleUploadResponse)
async def upload_multiple_media(
files: list[UploadFile] = File(...),
folder: str | None = Query(None),
@@ -99,15 +100,15 @@ async def upload_multiple_media(
- Return list of uploaded file URLs
- Handle errors gracefully
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {
"uploaded_files": [],
"failed_files": [],
"message": "Multiple upload coming in Slice 3",
}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return MultipleUploadResponse(
uploaded_files=[],
failed_files=[],
message="Multiple upload coming in Slice 3",
)
@router.get("/{media_id}")
@router.get("/{media_id}", response_model=MediaDetailResponse)
def get_media_details(
media_id: int,
current_user: User = Depends(get_current_vendor_api),
@@ -121,14 +122,14 @@ def get_media_details(
- Return file URL
- Return usage information (which products use this file)
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {"message": "Media details coming in Slice 3"}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return MediaDetailResponse(message="Media details coming in Slice 3")
@router.put("/{media_id}")
@router.put("/{media_id}", response_model=MediaDetailResponse)
def update_media_metadata(
media_id: int,
metadata: dict,
metadata: MediaMetadataUpdate,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
@@ -141,11 +142,11 @@ def update_media_metadata(
- Update tags/categories
- Update description
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {"message": "Media update coming in Slice 3"}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return MediaDetailResponse(message="Media update coming in Slice 3")
@router.delete("/{media_id}")
@router.delete("/{media_id}", response_model=MediaDetailResponse)
def delete_media(
media_id: int,
current_user: User = Depends(get_current_vendor_api),
@@ -161,11 +162,11 @@ def delete_media(
- Delete database record
- Return success/error
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {"message": "Media deletion coming in Slice 3"}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return MediaDetailResponse(message="Media deletion coming in Slice 3")
@router.get("/{media_id}/usage")
@router.get("/{media_id}/usage", response_model=MediaUsageResponse)
def get_media_usage(
media_id: int,
current_user: User = Depends(get_current_vendor_api),
@@ -179,15 +180,15 @@ def get_media_usage(
- Check other entities using this media
- Return list of usage
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {
"products": [],
"other_usage": [],
"message": "Media usage tracking coming in Slice 3",
}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return MediaUsageResponse(
products=[],
other_usage=[],
message="Media usage tracking coming in Slice 3",
)
@router.post("/optimize/{media_id}")
@router.post("/optimize/{media_id}", response_model=OptimizationResultResponse)
def optimize_media(
media_id: int,
current_user: User = Depends(get_current_vendor_api),
@@ -202,5 +203,5 @@ def optimize_media(
- Keep original
- Update database with new versions
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {"message": "Media optimization coming in Slice 3"}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return OptimizationResultResponse(message="Media optimization coming in Slice 3")

View File

@@ -1,9 +1,9 @@
# Notification management
# app/api/v1/vendor/notifications.py
"""
Vendor notification management endpoints.
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern)
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern).
The get_current_vendor_api dependency guarantees token_vendor_id is present.
"""
import logging
@@ -13,22 +13,24 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api
from app.core.database import get_db
from app.exceptions import InvalidTokenException
from app.services.vendor_service import vendor_service
from models.database.user import User
from models.schema.notification import (
MessageResponse,
NotificationListResponse,
NotificationSettingsResponse,
NotificationSettingsUpdate,
NotificationTemplateListResponse,
NotificationTemplateUpdate,
TestNotificationRequest,
UnreadCountResponse,
)
router = APIRouter(prefix="/notifications")
logger = logging.getLogger(__name__)
def _get_vendor_from_token(current_user: User, db: Session):
"""Helper to get vendor from JWT token."""
if not hasattr(current_user, "token_vendor_id"):
raise InvalidTokenException("Token missing vendor information. Please login again.")
return vendor_service.get_vendor_by_id(db, current_user.token_vendor_id)
@router.get("")
@router.get("", response_model=NotificationListResponse)
def get_notifications(
skip: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=100),
@@ -45,16 +47,16 @@ def get_notifications(
- Support pagination
- Return notification details
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {
"notifications": [],
"total": 0,
"unread_count": 0,
"message": "Notifications coming in Slice 5",
}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return NotificationListResponse(
notifications=[],
total=0,
unread_count=0,
message="Notifications coming in Slice 5",
)
@router.get("/unread-count")
@router.get("/unread-count", response_model=UnreadCountResponse)
def get_unread_count(
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
@@ -66,11 +68,11 @@ def get_unread_count(
- Count unread notifications for vendor
- Used for notification badge
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {"unread_count": 0, "message": "Unread count coming in Slice 5"}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return UnreadCountResponse(unread_count=0, message="Unread count coming in Slice 5")
@router.put("/{notification_id}/read")
@router.put("/{notification_id}/read", response_model=MessageResponse)
def mark_as_read(
notification_id: int,
current_user: User = Depends(get_current_vendor_api),
@@ -83,11 +85,11 @@ def mark_as_read(
- Mark single notification as read
- Update read timestamp
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {"message": "Mark as read coming in Slice 5"}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return MessageResponse(message="Mark as read coming in Slice 5")
@router.put("/mark-all-read")
@router.put("/mark-all-read", response_model=MessageResponse)
def mark_all_as_read(
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
@@ -99,11 +101,11 @@ def mark_all_as_read(
- Mark all vendor notifications as read
- Update timestamps
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {"message": "Mark all as read coming in Slice 5"}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return MessageResponse(message="Mark all as read coming in Slice 5")
@router.delete("/{notification_id}")
@router.delete("/{notification_id}", response_model=MessageResponse)
def delete_notification(
notification_id: int,
current_user: User = Depends(get_current_vendor_api),
@@ -116,11 +118,11 @@ def delete_notification(
- Delete single notification
- Verify notification belongs to vendor
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {"message": "Notification deletion coming in Slice 5"}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return MessageResponse(message="Notification deletion coming in Slice 5")
@router.get("/settings")
@router.get("/settings", response_model=NotificationSettingsResponse)
def get_notification_settings(
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
@@ -133,18 +135,18 @@ def get_notification_settings(
- Get in-app notification settings
- Get notification types enabled/disabled
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {
"email_notifications": True,
"in_app_notifications": True,
"notification_types": {},
"message": "Notification settings coming in Slice 5",
}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return NotificationSettingsResponse(
email_notifications=True,
in_app_notifications=True,
notification_types={},
message="Notification settings coming in Slice 5",
)
@router.put("/settings")
@router.put("/settings", response_model=MessageResponse)
def update_notification_settings(
settings: dict,
settings: NotificationSettingsUpdate,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
@@ -156,11 +158,11 @@ def update_notification_settings(
- Update in-app notification settings
- Enable/disable specific notification types
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {"message": "Notification settings update coming in Slice 5"}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return MessageResponse(message="Notification settings update coming in Slice 5")
@router.get("/templates")
@router.get("/templates", response_model=NotificationTemplateListResponse)
def get_notification_templates(
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
@@ -173,14 +175,16 @@ def get_notification_templates(
- Include: order confirmation, shipping notification, etc.
- Return template details
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {"templates": [], "message": "Notification templates coming in Slice 5"}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return NotificationTemplateListResponse(
templates=[], message="Notification templates coming in Slice 5"
)
@router.put("/templates/{template_id}")
@router.put("/templates/{template_id}", response_model=MessageResponse)
def update_notification_template(
template_id: int,
template_data: dict,
template_data: NotificationTemplateUpdate,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
@@ -193,13 +197,13 @@ def update_notification_template(
- Validate template variables
- Preview template
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {"message": "Template update coming in Slice 5"}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return MessageResponse(message="Template update coming in Slice 5")
@router.post("/test")
@router.post("/test", response_model=MessageResponse)
def send_test_notification(
notification_data: dict,
notification_data: TestNotificationRequest,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
@@ -211,5 +215,5 @@ def send_test_notification(
- Use specified template
- Send to current user's email
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {"message": "Test notification coming in Slice 5"}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return MessageResponse(message="Test notification coming in Slice 5")

View File

@@ -1,6 +1,9 @@
# app/api/v1/vendor/orders.py
"""
Vendor order management endpoints.
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern).
The get_current_vendor_api dependency guarantees token_vendor_id is present.
"""
import logging
@@ -42,20 +45,9 @@ def get_vendor_orders(
Vendor is determined from JWT token (vendor_id claim).
Requires Authorization header (API endpoint).
"""
from fastapi import HTTPException
# Get vendor ID from token
if not hasattr(current_user, "token_vendor_id"):
raise HTTPException(
status_code=400,
detail="Token missing vendor information. Please login again.",
)
vendor_id = current_user.token_vendor_id
orders, total = order_service.get_vendor_orders(
db=db,
vendor_id=vendor_id,
vendor_id=current_user.token_vendor_id,
skip=skip,
limit=limit,
status=status,
@@ -81,18 +73,9 @@ def get_order_details(
Requires Authorization header (API endpoint).
"""
from fastapi import HTTPException
# Get vendor ID from token
if not hasattr(current_user, "token_vendor_id"):
raise HTTPException(
status_code=400,
detail="Token missing vendor information. Please login again.",
)
vendor_id = current_user.token_vendor_id
order = order_service.get_order(db=db, vendor_id=vendor_id, order_id=order_id)
order = order_service.get_order(
db=db, vendor_id=current_user.token_vendor_id, order_id=order_id
)
return OrderDetailResponse.model_validate(order)
@@ -117,19 +100,11 @@ def update_order_status(
Requires Authorization header (API endpoint).
"""
from fastapi import HTTPException
# Get vendor ID from token
if not hasattr(current_user, "token_vendor_id"):
raise HTTPException(
status_code=400,
detail="Token missing vendor information. Please login again.",
)
vendor_id = current_user.token_vendor_id
order = order_service.update_order_status(
db=db, vendor_id=vendor_id, order_id=order_id, order_update=order_update
db=db,
vendor_id=current_user.token_vendor_id,
order_id=order_id,
order_update=order_update,
)
logger.info(

View File

@@ -1,9 +1,9 @@
# Payment configuration and processing
# app/api/v1/vendor/payments.py
"""
Vendor payment configuration and processing endpoints.
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern)
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern).
The get_current_vendor_api dependency guarantees token_vendor_id is present.
"""
import logging
@@ -13,22 +13,27 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api
from app.core.database import get_db
from app.exceptions import InvalidTokenException
from app.services.vendor_service import vendor_service
from models.database.user import User
from models.schema.payment import (
PaymentBalanceResponse,
PaymentConfigResponse,
PaymentConfigUpdate,
PaymentConfigUpdateResponse,
PaymentMethodsResponse,
RefundRequest,
RefundResponse,
StripeConnectRequest,
StripeConnectResponse,
StripeDisconnectResponse,
TransactionsResponse,
)
router = APIRouter(prefix="/payments")
logger = logging.getLogger(__name__)
def _get_vendor_from_token(current_user: User, db: Session):
"""Helper to get vendor from JWT token."""
if not hasattr(current_user, "token_vendor_id"):
raise InvalidTokenException("Token missing vendor information. Please login again.")
return vendor_service.get_vendor_by_id(db, current_user.token_vendor_id)
@router.get("/config")
@router.get("/config", response_model=PaymentConfigResponse)
def get_payment_configuration(
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
@@ -42,19 +47,19 @@ def get_payment_configuration(
- Get currency settings
- Return masked/secure information only
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {
"payment_gateway": None,
"accepted_methods": [],
"currency": "EUR",
"stripe_connected": False,
"message": "Payment configuration coming in Slice 5",
}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return PaymentConfigResponse(
payment_gateway=None,
accepted_methods=[],
currency="EUR",
stripe_connected=False,
message="Payment configuration coming in Slice 5",
)
@router.put("/config")
@router.put("/config", response_model=PaymentConfigUpdateResponse)
def update_payment_configuration(
payment_config: dict,
payment_config: PaymentConfigUpdate,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
@@ -67,13 +72,15 @@ def update_payment_configuration(
- Update accepted payment methods
- Validate configuration before saving
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {"message": "Payment configuration update coming in Slice 5"}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return PaymentConfigUpdateResponse(
message="Payment configuration update coming in Slice 5"
)
@router.post("/stripe/connect")
@router.post("/stripe/connect", response_model=StripeConnectResponse)
def connect_stripe_account(
stripe_data: dict,
stripe_data: StripeConnectRequest,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
@@ -86,11 +93,11 @@ def connect_stripe_account(
- Verify Stripe account is active
- Enable payment processing
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {"message": "Stripe connection coming in Slice 5"}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return StripeConnectResponse(message="Stripe connection coming in Slice 5")
@router.delete("/stripe/disconnect")
@router.delete("/stripe/disconnect", response_model=StripeDisconnectResponse)
def disconnect_stripe_account(
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
@@ -103,11 +110,11 @@ def disconnect_stripe_account(
- Disable payment processing
- Warn about pending payments
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {"message": "Stripe disconnection coming in Slice 5"}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return StripeDisconnectResponse(message="Stripe disconnection coming in Slice 5")
@router.get("/methods")
@router.get("/methods", response_model=PaymentMethodsResponse)
def get_payment_methods(
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
@@ -119,11 +126,14 @@ def get_payment_methods(
- Return list of enabled payment methods
- Include: credit card, PayPal, bank transfer, etc.
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {"methods": [], "message": "Payment methods coming in Slice 5"}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return PaymentMethodsResponse(
methods=[],
message="Payment methods coming in Slice 5",
)
@router.get("/transactions")
@router.get("/transactions", response_model=TransactionsResponse)
def get_payment_transactions(
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
@@ -137,15 +147,15 @@ def get_payment_transactions(
- Include payment details
- Support pagination
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {
"transactions": [],
"total": 0,
"message": "Payment transactions coming in Slice 5",
}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return TransactionsResponse(
transactions=[],
total=0,
message="Payment transactions coming in Slice 5",
)
@router.get("/balance")
@router.get("/balance", response_model=PaymentBalanceResponse)
def get_payment_balance(
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
@@ -159,20 +169,20 @@ def get_payment_balance(
- Get next payout date
- Get payout history
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {
"available_balance": 0.0,
"pending_balance": 0.0,
"currency": "EUR",
"next_payout_date": None,
"message": "Payment balance coming in Slice 5",
}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return PaymentBalanceResponse(
available_balance=0.0,
pending_balance=0.0,
currency="EUR",
next_payout_date=None,
message="Payment balance coming in Slice 5",
)
@router.post("/refund/{payment_id}")
@router.post("/refund/{payment_id}", response_model=RefundResponse)
def refund_payment(
payment_id: int,
refund_data: dict,
refund_data: RefundRequest,
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
@@ -185,5 +195,5 @@ def refund_payment(
- Update order status
- Send refund notification to customer
"""
vendor = _get_vendor_from_token(current_user, db) # noqa: F841 - vendor will be used when implemented
return {"message": "Payment refund coming in Slice 5"}
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
return RefundResponse(message="Payment refund coming in Slice 5")

View File

@@ -1,6 +1,9 @@
# app/api/v1/vendor/products.py
"""
Vendor product catalog management endpoints.
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern).
The get_current_vendor_api dependency guarantees token_vendor_id is present.
"""
import logging
@@ -10,7 +13,6 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api
from app.core.database import get_db
from app.exceptions import InvalidTokenException
from app.services.product_service import product_service
from models.database.user import User
from models.schema.product import (
@@ -45,15 +47,9 @@ def get_vendor_products(
Vendor is determined from JWT token (vendor_id claim).
"""
# Get vendor ID from token
if not hasattr(current_user, "token_vendor_id"):
raise InvalidTokenException("Token missing vendor information. Please login again.")
vendor_id = current_user.token_vendor_id
products, total = product_service.get_vendor_products(
db=db,
vendor_id=vendor_id,
vendor_id=current_user.token_vendor_id,
skip=skip,
limit=limit,
is_active=is_active,
@@ -75,14 +71,8 @@ def get_product_details(
db: Session = Depends(get_db),
):
"""Get detailed product information including inventory."""
# Get vendor ID from token
if not hasattr(current_user, "token_vendor_id"):
raise InvalidTokenException("Token missing vendor information. Please login again.")
vendor_id = current_user.token_vendor_id
product = product_service.get_product(
db=db, vendor_id=vendor_id, product_id=product_id
db=db, vendor_id=current_user.token_vendor_id, product_id=product_id
)
return ProductDetailResponse.model_validate(product)
@@ -99,14 +89,8 @@ def add_product_to_catalog(
This publishes a MarketplaceProduct to the vendor's public catalog.
"""
# Get vendor ID from token
if not hasattr(current_user, "token_vendor_id"):
raise InvalidTokenException("Token missing vendor information. Please login again.")
vendor_id = current_user.token_vendor_id
product = product_service.create_product(
db=db, vendor_id=vendor_id, product_data=product_data
db=db, vendor_id=current_user.token_vendor_id, product_data=product_data
)
logger.info(
@@ -125,14 +109,11 @@ def update_product(
db: Session = Depends(get_db),
):
"""Update product in vendor catalog."""
# Get vendor ID from token
if not hasattr(current_user, "token_vendor_id"):
raise InvalidTokenException("Token missing vendor information. Please login again.")
vendor_id = current_user.token_vendor_id
product = product_service.update_product(
db=db, vendor_id=vendor_id, product_id=product_id, product_update=product_data
db=db,
vendor_id=current_user.token_vendor_id,
product_id=product_id,
product_update=product_data,
)
logger.info(
@@ -150,13 +131,9 @@ def remove_product_from_catalog(
db: Session = Depends(get_db),
):
"""Remove product from vendor catalog."""
# Get vendor ID from token
if not hasattr(current_user, "token_vendor_id"):
raise InvalidTokenException("Token missing vendor information. Please login again.")
vendor_id = current_user.token_vendor_id
product_service.delete_product(db=db, vendor_id=vendor_id, product_id=product_id)
product_service.delete_product(
db=db, vendor_id=current_user.token_vendor_id, product_id=product_id
)
logger.info(
f"Product {product_id} removed from catalog by user {current_user.username} "
@@ -177,18 +154,12 @@ def publish_from_marketplace(
Shortcut endpoint for publishing directly from marketplace import.
"""
# Get vendor ID from token
if not hasattr(current_user, "token_vendor_id"):
raise InvalidTokenException("Token missing vendor information. Please login again.")
vendor_id = current_user.token_vendor_id
product_data = ProductCreate(
marketplace_product_id=marketplace_product_id, is_active=True
)
product = product_service.create_product(
db=db, vendor_id=vendor_id, product_data=product_data
db=db, vendor_id=current_user.token_vendor_id, product_data=product_data
)
logger.info(
@@ -206,13 +177,9 @@ def toggle_product_active(
db: Session = Depends(get_db),
):
"""Toggle product active status."""
# Get vendor ID from token
if not hasattr(current_user, "token_vendor_id"):
raise InvalidTokenException("Token missing vendor information. Please login again.")
vendor_id = current_user.token_vendor_id
product = product_service.get_product(db, vendor_id, product_id)
product = product_service.get_product(
db, current_user.token_vendor_id, product_id
)
product.is_active = not product.is_active
db.commit()
@@ -231,13 +198,9 @@ def toggle_product_featured(
db: Session = Depends(get_db),
):
"""Toggle product featured status."""
# Get vendor ID from token
if not hasattr(current_user, "token_vendor_id"):
raise InvalidTokenException("Token missing vendor information. Please login again.")
vendor_id = current_user.token_vendor_id
product = product_service.get_product(db, vendor_id, product_id)
product = product_service.get_product(
db, current_user.token_vendor_id, product_id
)
product.is_featured = not product.is_featured
db.commit()

View File

@@ -2,7 +2,8 @@
"""
Vendor profile management endpoints.
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern)
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern).
The get_current_vendor_api dependency guarantees token_vendor_id is present.
"""
import logging
@@ -12,7 +13,6 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api
from app.core.database import get_db
from app.exceptions import InsufficientPermissionsException, InvalidTokenException
from app.services.vendor_service import vendor_service
from models.database.user import User
from models.schema.vendor import VendorResponse, VendorUpdate
@@ -21,20 +21,13 @@ router = APIRouter(prefix="/profile")
logger = logging.getLogger(__name__)
def _get_vendor_from_token(current_user: User, db: Session):
"""Helper to get vendor from JWT token."""
if not hasattr(current_user, "token_vendor_id"):
raise InvalidTokenException("Token missing vendor information. Please login again.")
return vendor_service.get_vendor_by_id(db, current_user.token_vendor_id)
@router.get("", response_model=VendorResponse)
def get_vendor_profile(
current_user: User = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Get current vendor profile information."""
vendor = _get_vendor_from_token(current_user, db)
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id)
return vendor
@@ -45,10 +38,7 @@ def update_vendor_profile(
db: Session = Depends(get_db),
):
"""Update vendor profile information."""
vendor = _get_vendor_from_token(current_user, db)
# Verify user has permission to update vendor
if not vendor_service.can_update_vendor(vendor, current_user):
raise InsufficientPermissionsException(required_permission="vendor:profile:update")
return vendor_service.update_vendor(db, vendor.id, vendor_update)
# Service handles permission checking and raises InsufficientPermissionsException if needed
return vendor_service.update_vendor(
db, current_user.token_vendor_id, vendor_update, current_user
)

View File

@@ -2,7 +2,8 @@
"""
Vendor settings and configuration endpoints.
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern)
Vendor Context: Uses token_vendor_id from JWT token (authenticated vendor API pattern).
The get_current_vendor_api dependency guarantees token_vendor_id is present.
"""
import logging
@@ -12,7 +13,6 @@ from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api
from app.core.database import get_db
from app.exceptions import InsufficientPermissionsException, InvalidTokenException
from app.services.vendor_service import vendor_service
from models.database.user import User
@@ -26,10 +26,6 @@ def get_vendor_settings(
db: Session = Depends(get_db),
):
"""Get vendor settings and configuration."""
# Get vendor ID from JWT token
if not hasattr(current_user, "token_vendor_id"):
raise InvalidTokenException("Token missing vendor information. Please login again.")
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id)
return {
@@ -56,32 +52,7 @@ def update_marketplace_settings(
db: Session = Depends(get_db),
):
"""Update marketplace integration settings."""
# Get vendor ID from JWT token
if not hasattr(current_user, "token_vendor_id"):
raise InvalidTokenException("Token missing vendor information. Please login again.")
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id)
# Verify permissions
if not vendor_service.can_update_vendor(vendor, current_user):
raise InsufficientPermissionsException(
required_permission="vendor:settings:update"
)
# Update Letzshop URLs
if "letzshop_csv_url_fr" in marketplace_config:
vendor.letzshop_csv_url_fr = marketplace_config["letzshop_csv_url_fr"]
if "letzshop_csv_url_en" in marketplace_config:
vendor.letzshop_csv_url_en = marketplace_config["letzshop_csv_url_en"]
if "letzshop_csv_url_de" in marketplace_config:
vendor.letzshop_csv_url_de = marketplace_config["letzshop_csv_url_de"]
db.commit()
db.refresh(vendor)
return {
"message": "Marketplace settings updated successfully",
"letzshop_csv_url_fr": vendor.letzshop_csv_url_fr,
"letzshop_csv_url_en": vendor.letzshop_csv_url_en,
"letzshop_csv_url_de": vendor.letzshop_csv_url_de,
}
# Service handles permission checking and raises InsufficientPermissionsException if needed
return vendor_service.update_marketplace_settings(
db, current_user.token_vendor_id, marketplace_config, current_user
)

View File

@@ -164,7 +164,7 @@ def invite_team_member(
)
@router.post("/accept-invitation", response_model=InvitationAcceptResponse)
@router.post("/accept-invitation", response_model=InvitationAcceptResponse) # public
def accept_invitation(acceptance: InvitationAccept, db: Session = Depends(get_db)):
"""
Accept a team invitation and activate account.