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

@@ -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.