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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user