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

@@ -1 +1,190 @@
# Media/file management models
# models/schema/media.py
"""
Media/file management Pydantic schemas for API validation and responses.
This module provides schemas for:
- Media library listing
- File upload responses
- Media metadata operations
- Media usage tracking
"""
from datetime import datetime
from typing import Any
from pydantic import BaseModel, Field
# ============================================================================
# SHARED RESPONSE SCHEMAS
# ============================================================================
class MessageResponse(BaseModel):
"""Generic message response for simple operations."""
message: str
# ============================================================================
# MEDIA ITEM SCHEMAS
# ============================================================================
class MediaItemResponse(BaseModel):
"""Single media item response."""
id: int
filename: str
original_filename: str | None = None
file_url: str
thumbnail_url: str | None = None
media_type: str # image, video, document
mime_type: str | None = None
file_size: int | None = None # bytes
width: int | None = None # for images/videos
height: int | None = None # for images/videos
alt_text: str | None = None
description: str | None = None
folder: str | None = None
metadata: dict[str, Any] | None = None
created_at: datetime
updated_at: datetime | None = None
model_config = {"from_attributes": True}
class MediaListResponse(BaseModel):
"""Paginated list of media items."""
media: list[MediaItemResponse] = []
total: int = 0
skip: int = 0
limit: int = 100
message: str | None = None
# ============================================================================
# UPLOAD RESPONSE SCHEMAS
# ============================================================================
class MediaUploadResponse(BaseModel):
"""Response for single file upload."""
id: int | None = None
file_url: str | None = None
thumbnail_url: str | None = None
filename: str | None = None
file_size: int | None = None
media_type: str | None = None
message: str | None = None
class UploadedFileInfo(BaseModel):
"""Information about a successfully uploaded file."""
id: int
filename: str
file_url: str
thumbnail_url: str | None = None
class FailedFileInfo(BaseModel):
"""Information about a failed file upload."""
filename: str
error: str
class MultipleUploadResponse(BaseModel):
"""Response for multiple file upload."""
uploaded_files: list[UploadedFileInfo] = []
failed_files: list[FailedFileInfo] = []
total_uploaded: int = 0
total_failed: int = 0
message: str | None = None
# ============================================================================
# MEDIA DETAIL SCHEMAS
# ============================================================================
class MediaDetailResponse(BaseModel):
"""Detailed media item response with usage info."""
id: int | None = None
filename: str | None = None
original_filename: str | None = None
file_url: str | None = None
thumbnail_url: str | None = None
media_type: str | None = None
mime_type: str | None = None
file_size: int | None = None
width: int | None = None
height: int | None = None
alt_text: str | None = None
description: str | None = None
folder: str | None = None
metadata: dict[str, Any] | None = None
created_at: datetime | None = None
updated_at: datetime | None = None
message: str | None = None
model_config = {"from_attributes": True}
# ============================================================================
# MEDIA UPDATE SCHEMAS
# ============================================================================
class MediaMetadataUpdate(BaseModel):
"""Request model for updating media metadata."""
filename: str | None = Field(None, max_length=255)
alt_text: str | None = Field(None, max_length=500)
description: str | None = None
folder: str | None = Field(None, max_length=100)
metadata: dict[str, Any] | None = None
# ============================================================================
# MEDIA USAGE SCHEMAS
# ============================================================================
class ProductUsageInfo(BaseModel):
"""Information about product using this media."""
product_id: int
product_name: str
usage_type: str # main_image, gallery, variant, etc.
class MediaUsageResponse(BaseModel):
"""Response showing where media is being used."""
media_id: int | None = None
products: list[ProductUsageInfo] = []
other_usage: list[dict[str, Any]] = []
total_usage_count: int = 0
message: str | None = None
# ============================================================================
# MEDIA OPTIMIZATION SCHEMAS
# ============================================================================
class OptimizationResultResponse(BaseModel):
"""Response for media optimization operation."""
media_id: int | None = None
original_size: int | None = None
optimized_size: int | None = None
savings_percent: float | None = None
optimized_url: str | None = None
message: str | None = None

View File

@@ -1 +1,151 @@
# Notification models
# models/schema/notification.py
"""
Notification Pydantic schemas for API validation and responses.
This module provides schemas for:
- Vendor notifications (list, read, delete)
- Notification settings management
- Notification email templates
- Unread counts and statistics
"""
from datetime import datetime
from typing import Any
from pydantic import BaseModel, Field
# ============================================================================
# SHARED RESPONSE SCHEMAS
# ============================================================================
class MessageResponse(BaseModel):
"""Generic message response for simple operations."""
message: str
class UnreadCountResponse(BaseModel):
"""Response for unread notification count."""
unread_count: int
message: str | None = None
# ============================================================================
# NOTIFICATION SCHEMAS
# ============================================================================
class NotificationResponse(BaseModel):
"""Single notification response."""
id: int
type: str
title: str
message: str
is_read: bool
read_at: datetime | None = None
priority: str = "normal"
action_url: str | None = None
metadata: dict[str, Any] | None = None
created_at: datetime
model_config = {"from_attributes": True}
class NotificationListResponse(BaseModel):
"""Paginated list of notifications."""
notifications: list[NotificationResponse] = []
total: int = 0
unread_count: int = 0
message: str | None = None
# ============================================================================
# NOTIFICATION SETTINGS SCHEMAS
# ============================================================================
class NotificationSettingsResponse(BaseModel):
"""Notification preferences response."""
email_notifications: bool = True
in_app_notifications: bool = True
notification_types: dict[str, bool] = Field(default_factory=dict)
message: str | None = None
class NotificationSettingsUpdate(BaseModel):
"""Request model for updating notification settings."""
email_notifications: bool | None = None
in_app_notifications: bool | None = None
notification_types: dict[str, bool] | None = None
# ============================================================================
# NOTIFICATION TEMPLATE SCHEMAS
# ============================================================================
class NotificationTemplateResponse(BaseModel):
"""Single notification template response."""
id: int
name: str
type: str
subject: str
body_html: str | None = None
body_text: str | None = None
variables: list[str] = Field(default_factory=list)
is_active: bool = True
created_at: datetime
updated_at: datetime | None = None
model_config = {"from_attributes": True}
class NotificationTemplateListResponse(BaseModel):
"""List of notification templates."""
templates: list[NotificationTemplateResponse] = []
message: str | None = None
class NotificationTemplateUpdate(BaseModel):
"""Request model for updating notification template."""
subject: str | None = Field(None, max_length=200)
body_html: str | None = None
body_text: str | None = None
is_active: bool | None = None
# ============================================================================
# TEST NOTIFICATION SCHEMA
# ============================================================================
class TestNotificationRequest(BaseModel):
"""Request model for sending test notification."""
template_id: int | None = Field(None, description="Template to use")
email: str | None = Field(None, description="Override recipient email")
notification_type: str = Field(default="test", description="Type of notification to send")
# ============================================================================
# ADMIN ALERT STATISTICS SCHEMA
# ============================================================================
class AlertStatisticsResponse(BaseModel):
"""Response for alert statistics."""
total_alerts: int = 0
active_alerts: int = 0
critical_alerts: int = 0
resolved_today: int = 0

View File

@@ -1 +1,166 @@
# Payment models
# models/schema/payment.py
"""
Payment Pydantic schemas for API validation and responses.
This module provides schemas for:
- Payment configuration
- Stripe integration
- Payment methods
- Transactions and balance
- Refunds
"""
from datetime import datetime
from typing import Any
from pydantic import BaseModel, Field
# ============================================================================
# PAYMENT CONFIGURATION SCHEMAS
# ============================================================================
class PaymentConfigResponse(BaseModel):
"""Response for payment configuration."""
payment_gateway: str | None = None
accepted_methods: list[str] = []
currency: str = "EUR"
stripe_connected: bool = False
stripe_account_id: str | None = None
paypal_connected: bool = False
message: str | None = None
class PaymentConfigUpdate(BaseModel):
"""Request model for updating payment configuration."""
payment_gateway: str | None = Field(None, max_length=50)
accepted_methods: list[str] | None = None
currency: str | None = Field(None, max_length=3)
class PaymentConfigUpdateResponse(BaseModel):
"""Response for payment configuration update."""
success: bool = False
message: str | None = None
# ============================================================================
# STRIPE INTEGRATION SCHEMAS
# ============================================================================
class StripeConnectRequest(BaseModel):
"""Request model for connecting Stripe account."""
authorization_code: str | None = None
state: str | None = None
class StripeConnectResponse(BaseModel):
"""Response for Stripe connection."""
connected: bool = False
stripe_account_id: str | None = None
message: str | None = None
class StripeDisconnectResponse(BaseModel):
"""Response for Stripe disconnection."""
disconnected: bool = False
message: str | None = None
# ============================================================================
# PAYMENT METHODS SCHEMAS
# ============================================================================
class PaymentMethodInfo(BaseModel):
"""Information about a payment method."""
id: str
name: str
type: str # credit_card, paypal, bank_transfer, etc.
enabled: bool = True
icon: str | None = None
class PaymentMethodsResponse(BaseModel):
"""Response for payment methods listing."""
methods: list[PaymentMethodInfo] = []
message: str | None = None
# ============================================================================
# TRANSACTION SCHEMAS
# ============================================================================
class TransactionInfo(BaseModel):
"""Information about a payment transaction."""
id: int
order_id: int | None = None
amount: float
currency: str = "EUR"
status: str # pending, completed, failed, refunded
payment_method: str | None = None
customer_email: str | None = None
created_at: datetime
completed_at: datetime | None = None
metadata: dict[str, Any] | None = None
class TransactionsResponse(BaseModel):
"""Response for payment transactions listing."""
transactions: list[TransactionInfo] = []
total: int = 0
skip: int = 0
limit: int = 50
message: str | None = None
# ============================================================================
# BALANCE SCHEMAS
# ============================================================================
class PaymentBalanceResponse(BaseModel):
"""Response for payment balance information."""
available_balance: float = 0.0
pending_balance: float = 0.0
currency: str = "EUR"
next_payout_date: datetime | None = None
last_payout_date: datetime | None = None
last_payout_amount: float | None = None
message: str | None = None
# ============================================================================
# REFUND SCHEMAS
# ============================================================================
class RefundRequest(BaseModel):
"""Request model for processing a refund."""
amount: float | None = Field(None, gt=0, description="Partial refund amount, or None for full refund")
reason: str | None = Field(None, max_length=500)
class RefundResponse(BaseModel):
"""Response for refund operation."""
refund_id: int | None = None
payment_id: int | None = None
amount: float | None = None
status: str | None = None # pending, completed, failed
message: str | None = None