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:
2025-12-13 22:59:51 +01:00
parent 94d268f330
commit 9920430b9e
123 changed files with 1408 additions and 840 deletions

View File

@@ -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"]
),

View File

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

View File

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

View File

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

View File

@@ -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),

View File

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