fix: correct tojson|safe usage in templates and update validator
- Remove |safe from |tojson in HTML attributes (x-data) - quotes must become " for browsers to parse correctly - Update LANG-002 and LANG-003 architecture rules to document correct |tojson usage patterns: - HTML attributes: |tojson (no |safe) - Script blocks: |tojson|safe - Fix validator to warn when |tojson|safe is used in x-data (breaks HTML attribute parsing) - Improve code quality across services, APIs, and tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -45,7 +45,6 @@ from app.exceptions import (
|
||||
InsufficientVendorPermissionsException,
|
||||
InvalidTokenException,
|
||||
UnauthorizedVendorAccessException,
|
||||
VendorAccessDeniedException,
|
||||
VendorNotFoundException,
|
||||
VendorOwnerOnlyException,
|
||||
)
|
||||
@@ -306,15 +305,15 @@ def get_current_vendor_api(
|
||||
|
||||
# Require vendor context in token
|
||||
if not hasattr(user, "token_vendor_id"):
|
||||
raise InvalidTokenException("Token missing vendor information. Please login again.")
|
||||
raise InvalidTokenException(
|
||||
"Token missing vendor information. Please login again."
|
||||
)
|
||||
|
||||
vendor_id = user.token_vendor_id
|
||||
|
||||
# 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}"
|
||||
)
|
||||
logger.warning(f"User {user.username} lost access to vendor_id={vendor_id}")
|
||||
raise InsufficientPermissionsException(
|
||||
"Access to vendor has been revoked. Please login again."
|
||||
)
|
||||
@@ -605,7 +604,9 @@ def require_vendor_permission(permission: str):
|
||||
) -> User:
|
||||
# Get vendor ID from JWT token
|
||||
if not hasattr(current_user, "token_vendor_id"):
|
||||
raise InvalidTokenException("Token missing vendor information. Please login again.")
|
||||
raise InvalidTokenException(
|
||||
"Token missing vendor information. Please login again."
|
||||
)
|
||||
|
||||
vendor_id = current_user.token_vendor_id
|
||||
|
||||
@@ -649,7 +650,9 @@ def require_vendor_owner(
|
||||
"""
|
||||
# Get vendor ID from JWT token
|
||||
if not hasattr(current_user, "token_vendor_id"):
|
||||
raise InvalidTokenException("Token missing vendor information. Please login again.")
|
||||
raise InvalidTokenException(
|
||||
"Token missing vendor information. Please login again."
|
||||
)
|
||||
|
||||
vendor_id = current_user.token_vendor_id
|
||||
|
||||
@@ -695,7 +698,9 @@ def require_any_vendor_permission(*permissions: str):
|
||||
) -> User:
|
||||
# Get vendor ID from JWT token
|
||||
if not hasattr(current_user, "token_vendor_id"):
|
||||
raise InvalidTokenException("Token missing vendor information. Please login again.")
|
||||
raise InvalidTokenException(
|
||||
"Token missing vendor information. Please login again."
|
||||
)
|
||||
|
||||
vendor_id = current_user.token_vendor_id
|
||||
|
||||
@@ -748,7 +753,9 @@ def require_all_vendor_permissions(*permissions: str):
|
||||
) -> User:
|
||||
# Get vendor ID from JWT token
|
||||
if not hasattr(current_user, "token_vendor_id"):
|
||||
raise InvalidTokenException("Token missing vendor information. Please login again.")
|
||||
raise InvalidTokenException(
|
||||
"Token missing vendor information. Please login again."
|
||||
)
|
||||
|
||||
vendor_id = current_user.token_vendor_id
|
||||
|
||||
|
||||
@@ -151,9 +151,7 @@ router.include_router(
|
||||
)
|
||||
|
||||
# Include test runner endpoints
|
||||
router.include_router(
|
||||
tests.router, prefix="/tests", tags=["admin-tests"]
|
||||
)
|
||||
router.include_router(tests.router, prefix="/tests", tags=["admin-tests"])
|
||||
|
||||
# Export the router
|
||||
__all__ = ["router"]
|
||||
|
||||
@@ -63,7 +63,9 @@ def _convert_import_to_response(job) -> BackgroundTaskResponse:
|
||||
started_at=job.started_at.isoformat() if job.started_at else None,
|
||||
completed_at=job.completed_at.isoformat() if job.completed_at else None,
|
||||
duration_seconds=duration,
|
||||
description=f"Import from {job.marketplace}: {job.source_url[:50]}..." if len(job.source_url) > 50 else f"Import from {job.marketplace}: {job.source_url}",
|
||||
description=f"Import from {job.marketplace}: {job.source_url[:50]}..."
|
||||
if len(job.source_url) > 50
|
||||
else f"Import from {job.marketplace}: {job.source_url}",
|
||||
triggered_by=job.user.username if job.user else None,
|
||||
error_message=job.error_message,
|
||||
details={
|
||||
@@ -108,7 +110,9 @@ def _convert_test_run_to_response(run) -> BackgroundTaskResponse:
|
||||
@router.get("/tasks", response_model=list[BackgroundTaskResponse])
|
||||
async def list_background_tasks(
|
||||
status: str | None = Query(None, description="Filter by status"),
|
||||
task_type: str | None = Query(None, description="Filter by type (import, test_run)"),
|
||||
task_type: str | None = Query(
|
||||
None, description="Filter by type (import, test_run)"
|
||||
),
|
||||
limit: int = Query(50, ge=1, le=200),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_api),
|
||||
@@ -122,12 +126,16 @@ async def list_background_tasks(
|
||||
|
||||
# Get import jobs
|
||||
if task_type is None or task_type == "import":
|
||||
import_jobs = background_tasks_service.get_import_jobs(db, status=status, limit=limit)
|
||||
import_jobs = background_tasks_service.get_import_jobs(
|
||||
db, status=status, limit=limit
|
||||
)
|
||||
tasks.extend([_convert_import_to_response(job) for job in import_jobs])
|
||||
|
||||
# Get test runs
|
||||
if task_type is None or task_type == "test_run":
|
||||
test_runs = background_tasks_service.get_test_runs(db, status=status, limit=limit)
|
||||
test_runs = background_tasks_service.get_test_runs(
|
||||
db, status=status, limit=limit
|
||||
)
|
||||
tasks.extend([_convert_test_run_to_response(run) for run in test_runs])
|
||||
|
||||
# Sort by start time (most recent first)
|
||||
|
||||
@@ -329,9 +329,7 @@ async def assign_violation(
|
||||
"user_id": assignment.user_id,
|
||||
"assigned_at": assignment.assigned_at.isoformat(),
|
||||
"assigned_by": assignment.assigned_by,
|
||||
"due_date": (
|
||||
assignment.due_date.isoformat() if assignment.due_date else None
|
||||
),
|
||||
"due_date": (assignment.due_date.isoformat() if assignment.due_date else None),
|
||||
"priority": assignment.priority,
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ def create_company_with_owner(
|
||||
owner_username=owner_user.username,
|
||||
owner_email=owner_user.email,
|
||||
temporary_password=temp_password or "N/A (Existing user)",
|
||||
login_url=f"http://localhost:8000/admin/login",
|
||||
login_url="http://localhost:8000/admin/login",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -46,9 +46,13 @@ def get_admin_dashboard(
|
||||
users=UserStatsResponse(**user_stats),
|
||||
vendors=VendorStatsResponse(
|
||||
total=vendor_stats.get("total", vendor_stats.get("total_vendors", 0)),
|
||||
verified=vendor_stats.get("verified", vendor_stats.get("verified_vendors", 0)),
|
||||
verified=vendor_stats.get(
|
||||
"verified", vendor_stats.get("verified_vendors", 0)
|
||||
),
|
||||
pending=vendor_stats.get("pending", vendor_stats.get("pending_vendors", 0)),
|
||||
inactive=vendor_stats.get("inactive", vendor_stats.get("inactive_vendors", 0)),
|
||||
inactive=vendor_stats.get(
|
||||
"inactive", vendor_stats.get("inactive_vendors", 0)
|
||||
),
|
||||
),
|
||||
recent_vendors=admin_service.get_recent_vendors(db, limit=5),
|
||||
recent_imports=admin_service.get_recent_import_jobs(db, limit=10),
|
||||
@@ -109,9 +113,13 @@ def get_platform_statistics(
|
||||
users=UserStatsResponse(**user_stats),
|
||||
vendors=VendorStatsResponse(
|
||||
total=vendor_stats.get("total", vendor_stats.get("total_vendors", 0)),
|
||||
verified=vendor_stats.get("verified", vendor_stats.get("verified_vendors", 0)),
|
||||
verified=vendor_stats.get(
|
||||
"verified", vendor_stats.get("verified_vendors", 0)
|
||||
),
|
||||
pending=vendor_stats.get("pending", vendor_stats.get("pending_vendors", 0)),
|
||||
inactive=vendor_stats.get("inactive", vendor_stats.get("inactive_vendors", 0)),
|
||||
inactive=vendor_stats.get(
|
||||
"inactive", vendor_stats.get("inactive_vendors", 0)
|
||||
),
|
||||
),
|
||||
products=ProductStatsResponse(**product_stats),
|
||||
orders=OrderStatsBasicResponse(**order_stats),
|
||||
|
||||
@@ -68,7 +68,9 @@ def get_credentials_service(db: Session) -> LetzshopCredentialsService:
|
||||
def list_vendors_letzshop_status(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
configured_only: bool = Query(False, description="Only show vendors with Letzshop configured"),
|
||||
configured_only: bool = Query(
|
||||
False, description="Only show vendors with Letzshop configured"
|
||||
),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
@@ -119,8 +121,9 @@ def get_vendor_credentials(
|
||||
credentials = creds_service.get_credentials_or_raise(vendor_id)
|
||||
except CredentialsNotFoundError:
|
||||
raise ResourceNotFoundException(
|
||||
"LetzshopCredentials", str(vendor_id),
|
||||
message=f"Letzshop credentials not configured for vendor {vendor.name}"
|
||||
"LetzshopCredentials",
|
||||
str(vendor_id),
|
||||
message=f"Letzshop credentials not configured for vendor {vendor.name}",
|
||||
)
|
||||
|
||||
return LetzshopCredentialsResponse(
|
||||
@@ -215,8 +218,9 @@ def update_vendor_credentials(
|
||||
db.commit()
|
||||
except CredentialsNotFoundError:
|
||||
raise ResourceNotFoundException(
|
||||
"LetzshopCredentials", str(vendor_id),
|
||||
message=f"Letzshop credentials not configured for vendor {vendor.name}"
|
||||
"LetzshopCredentials",
|
||||
str(vendor_id),
|
||||
message=f"Letzshop credentials not configured for vendor {vendor.name}",
|
||||
)
|
||||
|
||||
return LetzshopCredentialsResponse(
|
||||
@@ -255,8 +259,9 @@ def delete_vendor_credentials(
|
||||
deleted = creds_service.delete_credentials(vendor_id)
|
||||
if not deleted:
|
||||
raise ResourceNotFoundException(
|
||||
"LetzshopCredentials", str(vendor_id),
|
||||
message=f"Letzshop credentials not configured for vendor {vendor.name}"
|
||||
"LetzshopCredentials",
|
||||
str(vendor_id),
|
||||
message=f"Letzshop credentials not configured for vendor {vendor.name}",
|
||||
)
|
||||
db.commit()
|
||||
|
||||
@@ -445,7 +450,9 @@ def trigger_vendor_sync(
|
||||
orders_imported += 1
|
||||
|
||||
except Exception as e:
|
||||
errors.append(f"Error processing shipment {shipment.get('id')}: {e}")
|
||||
errors.append(
|
||||
f"Error processing shipment {shipment.get('id')}: {e}"
|
||||
)
|
||||
|
||||
db.commit()
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ Provides endpoints for:
|
||||
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, Depends, Query, Response
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_current_admin_api
|
||||
@@ -190,9 +190,10 @@ def download_log_file(
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from app.core.config import settings
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
# Determine log file path
|
||||
log_file_path = settings.log_file
|
||||
if log_file_path:
|
||||
|
||||
@@ -149,7 +149,9 @@ class AdminProductDetail(BaseModel):
|
||||
def get_products(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(50, ge=1, le=500),
|
||||
search: str | None = Query(None, description="Search by title, GTIN, SKU, or brand"),
|
||||
search: str | None = Query(
|
||||
None, description="Search by title, GTIN, SKU, or brand"
|
||||
),
|
||||
marketplace: str | None = Query(None, description="Filter by marketplace"),
|
||||
vendor_name: str | None = Query(None, description="Filter by vendor name"),
|
||||
availability: str | None = Query(None, description="Filter by availability"),
|
||||
|
||||
@@ -79,9 +79,7 @@ def get_setting(
|
||||
setting = admin_settings_service.get_setting_by_key(db, key)
|
||||
|
||||
if not setting:
|
||||
raise ResourceNotFoundException(
|
||||
resource_type="Setting", identifier=key
|
||||
)
|
||||
raise ResourceNotFoundException(resource_type="Setting", identifier=key)
|
||||
|
||||
return AdminSettingResponse.model_validate(setting)
|
||||
|
||||
|
||||
@@ -65,7 +65,9 @@ class RunTestsRequest(BaseModel):
|
||||
"""Request model for running tests"""
|
||||
|
||||
test_path: str = Field("tests", description="Path to tests to run")
|
||||
extra_args: list[str] | None = Field(None, description="Additional pytest arguments")
|
||||
extra_args: list[str] | None = Field(
|
||||
None, description="Additional pytest arguments"
|
||||
)
|
||||
|
||||
|
||||
class TestDashboardStatsResponse(BaseModel):
|
||||
@@ -205,6 +207,7 @@ async def get_run(
|
||||
|
||||
if not run:
|
||||
from app.exceptions.base import ResourceNotFoundException
|
||||
|
||||
raise ResourceNotFoundException("TestRun", str(run_id))
|
||||
|
||||
return TestRunResponse(
|
||||
@@ -231,7 +234,9 @@ async def get_run(
|
||||
@router.get("/runs/{run_id}/results", response_model=list[TestResultResponse])
|
||||
async def get_run_results(
|
||||
run_id: int,
|
||||
outcome: str | None = Query(None, description="Filter by outcome (passed, failed, error, skipped)"),
|
||||
outcome: str | None = Query(
|
||||
None, description="Filter by outcome (passed, failed, error, skipped)"
|
||||
),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_api),
|
||||
):
|
||||
|
||||
@@ -99,7 +99,9 @@ def create_user(
|
||||
full_name=user.full_name,
|
||||
is_email_verified=user.is_email_verified,
|
||||
owned_companies_count=len(user.owned_companies) if user.owned_companies else 0,
|
||||
vendor_memberships_count=len(user.vendor_memberships) if user.vendor_memberships else 0,
|
||||
vendor_memberships_count=len(user.vendor_memberships)
|
||||
if user.vendor_memberships
|
||||
else 0,
|
||||
)
|
||||
|
||||
|
||||
@@ -151,7 +153,9 @@ def get_user_details(
|
||||
full_name=user.full_name,
|
||||
is_email_verified=user.is_email_verified,
|
||||
owned_companies_count=len(user.owned_companies) if user.owned_companies else 0,
|
||||
vendor_memberships_count=len(user.vendor_memberships) if user.vendor_memberships else 0,
|
||||
vendor_memberships_count=len(user.vendor_memberships)
|
||||
if user.vendor_memberships
|
||||
else 0,
|
||||
)
|
||||
|
||||
|
||||
@@ -192,7 +196,9 @@ def update_user(
|
||||
full_name=user.full_name,
|
||||
is_email_verified=user.is_email_verified,
|
||||
owned_companies_count=len(user.owned_companies) if user.owned_companies else 0,
|
||||
vendor_memberships_count=len(user.vendor_memberships) if user.vendor_memberships else 0,
|
||||
vendor_memberships_count=len(user.vendor_memberships)
|
||||
if user.vendor_memberships
|
||||
else 0,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -229,4 +229,6 @@ async def delete_vendor_theme(
|
||||
# Global exception handler converts them to proper HTTP responses
|
||||
result = vendor_theme_service.delete_theme(db, vendor_code)
|
||||
db.commit()
|
||||
return ThemeDeleteResponse(message=result.get("message", "Theme deleted successfully"))
|
||||
return ThemeDeleteResponse(
|
||||
message=result.get("message", "Theme deleted successfully")
|
||||
)
|
||||
|
||||
@@ -318,7 +318,9 @@ def delete_vendor(
|
||||
@router.get("/{vendor_identifier}/export/letzshop")
|
||||
def export_vendor_products_letzshop(
|
||||
vendor_identifier: str = Path(..., description="Vendor ID or vendor_code"),
|
||||
language: str = Query("en", description="Language for title/description (en, fr, de)"),
|
||||
language: str = Query(
|
||||
"en", description="Language for title/description (en, fr, de)"
|
||||
),
|
||||
include_inactive: bool = Query(False, description="Include inactive products"),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
|
||||
@@ -7,6 +7,7 @@ These endpoints handle:
|
||||
- Getting current language info
|
||||
- Listing available languages
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, Request, Response
|
||||
|
||||
4
app/api/v1/vendor/analytics.py
vendored
4
app/api/v1/vendor/analytics.py
vendored
@@ -39,7 +39,9 @@ def get_vendor_analytics(
|
||||
period=data["period"],
|
||||
start_date=data["start_date"],
|
||||
imports=VendorAnalyticsImports(count=data["imports"]["count"]),
|
||||
catalog=VendorAnalyticsCatalog(products_added=data["catalog"]["products_added"]),
|
||||
catalog=VendorAnalyticsCatalog(
|
||||
products_added=data["catalog"]["products_added"]
|
||||
),
|
||||
inventory=VendorAnalyticsInventory(
|
||||
total_locations=data["inventory"]["total_locations"]
|
||||
),
|
||||
|
||||
5
app/api/v1/vendor/auth.py
vendored
5
app/api/v1/vendor/auth.py
vendored
@@ -25,7 +25,6 @@ from app.exceptions import InvalidCredentialsException
|
||||
from app.services.auth_service import auth_service
|
||||
from middleware.vendor_context import get_current_vendor
|
||||
from models.database.user import User
|
||||
from models.database.vendor import Vendor
|
||||
from models.schema.auth import LogoutResponse, UserLogin, VendorUserResponse
|
||||
|
||||
router = APIRouter(prefix="/auth")
|
||||
@@ -95,9 +94,7 @@ def vendor_login(
|
||||
f"User {user.username} attempted login to vendor {vendor.vendor_code} "
|
||||
f"but is not authorized"
|
||||
)
|
||||
raise InvalidCredentialsException(
|
||||
"You do not have access to this vendor"
|
||||
)
|
||||
raise InvalidCredentialsException("You do not have access to this vendor")
|
||||
else:
|
||||
# No vendor context - find which vendor this user belongs to
|
||||
vendor, vendor_role = auth_service.find_user_vendor(user)
|
||||
|
||||
4
app/api/v1/vendor/customers.py
vendored
4
app/api/v1/vendor/customers.py
vendored
@@ -90,7 +90,9 @@ def get_customer_orders(
|
||||
- Return order details
|
||||
"""
|
||||
vendor = vendor_service.get_vendor_by_id(db, current_user.token_vendor_id) # noqa: F841
|
||||
return CustomerOrdersResponse(orders=[], message="Customer orders coming in Slice 5")
|
||||
return CustomerOrdersResponse(
|
||||
orders=[], message="Customer orders coming in Slice 5"
|
||||
)
|
||||
|
||||
|
||||
@router.put("/{customer_id}", response_model=CustomerMessageResponse)
|
||||
|
||||
25
app/api/v1/vendor/inventory.py
vendored
25
app/api/v1/vendor/inventory.py
vendored
@@ -5,6 +5,7 @@ Vendor inventory 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
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
@@ -36,7 +37,9 @@ def set_inventory(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Set exact inventory quantity (replaces existing)."""
|
||||
result = inventory_service.set_inventory(db, current_user.token_vendor_id, inventory)
|
||||
result = inventory_service.set_inventory(
|
||||
db, current_user.token_vendor_id, inventory
|
||||
)
|
||||
db.commit()
|
||||
return result
|
||||
|
||||
@@ -48,7 +51,9 @@ def adjust_inventory(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Adjust inventory (positive to add, negative to remove)."""
|
||||
result = inventory_service.adjust_inventory(db, current_user.token_vendor_id, adjustment)
|
||||
result = inventory_service.adjust_inventory(
|
||||
db, current_user.token_vendor_id, adjustment
|
||||
)
|
||||
db.commit()
|
||||
return result
|
||||
|
||||
@@ -60,7 +65,9 @@ def reserve_inventory(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Reserve inventory for an order."""
|
||||
result = inventory_service.reserve_inventory(db, current_user.token_vendor_id, reservation)
|
||||
result = inventory_service.reserve_inventory(
|
||||
db, current_user.token_vendor_id, reservation
|
||||
)
|
||||
db.commit()
|
||||
return result
|
||||
|
||||
@@ -72,7 +79,9 @@ def release_reservation(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Release reserved inventory (cancel order)."""
|
||||
result = inventory_service.release_reservation(db, current_user.token_vendor_id, reservation)
|
||||
result = inventory_service.release_reservation(
|
||||
db, current_user.token_vendor_id, reservation
|
||||
)
|
||||
db.commit()
|
||||
return result
|
||||
|
||||
@@ -84,7 +93,9 @@ def fulfill_reservation(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Fulfill reservation (complete order, remove from stock)."""
|
||||
result = inventory_service.fulfill_reservation(db, current_user.token_vendor_id, reservation)
|
||||
result = inventory_service.fulfill_reservation(
|
||||
db, current_user.token_vendor_id, reservation
|
||||
)
|
||||
db.commit()
|
||||
return result
|
||||
|
||||
@@ -96,7 +107,9 @@ def get_product_inventory(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Get inventory summary for a product."""
|
||||
return inventory_service.get_product_inventory(db, current_user.token_vendor_id, product_id)
|
||||
return inventory_service.get_product_inventory(
|
||||
db, current_user.token_vendor_id, product_id
|
||||
)
|
||||
|
||||
|
||||
@router.get("/inventory", response_model=InventoryListResponse)
|
||||
|
||||
16
app/api/v1/vendor/letzshop.py
vendored
16
app/api/v1/vendor/letzshop.py
vendored
@@ -323,9 +323,7 @@ def get_order(
|
||||
order_service = get_order_service(db)
|
||||
|
||||
try:
|
||||
order = order_service.get_order_or_raise(
|
||||
current_user.token_vendor_id, order_id
|
||||
)
|
||||
order = order_service.get_order_or_raise(current_user.token_vendor_id, order_id)
|
||||
except OrderNotFoundError:
|
||||
raise ResourceNotFoundException("LetzshopOrder", str(order_id))
|
||||
|
||||
@@ -396,7 +394,9 @@ def import_orders(
|
||||
orders_imported += 1
|
||||
|
||||
except Exception as e:
|
||||
errors.append(f"Error processing shipment {shipment.get('id')}: {e}")
|
||||
errors.append(
|
||||
f"Error processing shipment {shipment.get('id')}: {e}"
|
||||
)
|
||||
|
||||
db.commit()
|
||||
creds_service.update_sync_status(
|
||||
@@ -475,9 +475,7 @@ def confirm_order(
|
||||
return FulfillmentOperationResponse(
|
||||
success=True,
|
||||
message=f"Confirmed {len(inventory_unit_ids)} inventory units",
|
||||
confirmed_units=[
|
||||
u.get("id") for u in result.get("inventoryUnits", [])
|
||||
],
|
||||
confirmed_units=[u.get("id") for u in result.get("inventoryUnits", [])],
|
||||
)
|
||||
|
||||
except LetzshopClientError as e:
|
||||
@@ -699,7 +697,9 @@ def list_fulfillment_queue(
|
||||
|
||||
@router.get("/export")
|
||||
def export_products_letzshop(
|
||||
language: str = Query("en", description="Language for title/description (en, fr, de)"),
|
||||
language: str = Query(
|
||||
"en", description="Language for title/description (en, fr, de)"
|
||||
),
|
||||
include_inactive: bool = Query(False, description="Include inactive products"),
|
||||
current_user: User = Depends(get_current_vendor_api),
|
||||
db: Session = Depends(get_db),
|
||||
|
||||
24
app/api/v1/vendor/products.py
vendored
24
app/api/v1/vendor/products.py
vendored
@@ -181,18 +181,20 @@ def toggle_product_active(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Toggle product active status."""
|
||||
product = product_service.get_product(
|
||||
db, current_user.token_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()
|
||||
db.refresh(product)
|
||||
|
||||
status = "activated" if product.is_active else "deactivated"
|
||||
logger.info(f"Product {product_id} {status} for vendor {current_user.token_vendor_code}")
|
||||
logger.info(
|
||||
f"Product {product_id} {status} for vendor {current_user.token_vendor_code}"
|
||||
)
|
||||
|
||||
return ProductToggleResponse(message=f"Product {status}", is_active=product.is_active)
|
||||
return ProductToggleResponse(
|
||||
message=f"Product {status}", is_active=product.is_active
|
||||
)
|
||||
|
||||
|
||||
@router.put("/{product_id}/toggle-featured", response_model=ProductToggleResponse)
|
||||
@@ -202,15 +204,17 @@ def toggle_product_featured(
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Toggle product featured status."""
|
||||
product = product_service.get_product(
|
||||
db, current_user.token_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()
|
||||
db.refresh(product)
|
||||
|
||||
status = "featured" if product.is_featured else "unfeatured"
|
||||
logger.info(f"Product {product_id} {status} for vendor {current_user.token_vendor_code}")
|
||||
logger.info(
|
||||
f"Product {product_id} {status} for vendor {current_user.token_vendor_code}"
|
||||
)
|
||||
|
||||
return ProductToggleResponse(message=f"Product {status}", is_featured=product.is_featured)
|
||||
return ProductToggleResponse(
|
||||
message=f"Product {status}", is_featured=product.is_featured
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user