style: apply black and isort formatting across entire codebase
- Standardize quote style (single to double quotes) - Reorder and group imports alphabetically - Fix line breaks and indentation for consistency - Apply PEP 8 formatting standards Also updated Makefile to exclude both venv and .venv from code quality checks. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -24,21 +24,9 @@ IMPORTANT:
|
||||
from fastapi import APIRouter
|
||||
|
||||
# Import all admin routers
|
||||
from . import (
|
||||
auth,
|
||||
vendors,
|
||||
vendor_domains,
|
||||
vendor_themes,
|
||||
users,
|
||||
dashboard,
|
||||
marketplace,
|
||||
monitoring,
|
||||
audit,
|
||||
settings,
|
||||
notifications,
|
||||
content_pages,
|
||||
code_quality
|
||||
)
|
||||
from . import (audit, auth, code_quality, content_pages, dashboard,
|
||||
marketplace, monitoring, notifications, settings, users,
|
||||
vendor_domains, vendor_themes, vendors)
|
||||
|
||||
# Create admin router
|
||||
router = APIRouter()
|
||||
@@ -66,7 +54,9 @@ router.include_router(vendor_domains.router, tags=["admin-vendor-domains"])
|
||||
router.include_router(vendor_themes.router, tags=["admin-vendor-themes"])
|
||||
|
||||
# Include content pages management endpoints
|
||||
router.include_router(content_pages.router, prefix="/content-pages", tags=["admin-content-pages"])
|
||||
router.include_router(
|
||||
content_pages.router, prefix="/content-pages", tags=["admin-content-pages"]
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -115,7 +105,9 @@ router.include_router(notifications.router, tags=["admin-notifications"])
|
||||
# ============================================================================
|
||||
|
||||
# Include code quality and architecture validation endpoints
|
||||
router.include_router(code_quality.router, prefix="/code-quality", tags=["admin-code-quality"])
|
||||
router.include_router(
|
||||
code_quality.router, prefix="/code-quality", tags=["admin-code-quality"]
|
||||
)
|
||||
|
||||
# Export the router
|
||||
__all__ = ["router"]
|
||||
|
||||
@@ -9,8 +9,8 @@ Provides endpoints for:
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -18,12 +18,10 @@ from sqlalchemy.orm import Session
|
||||
from app.api.deps import get_current_admin_api
|
||||
from app.core.database import get_db
|
||||
from app.services.admin_audit_service import admin_audit_service
|
||||
from models.schema.admin import (
|
||||
AdminAuditLogResponse,
|
||||
AdminAuditLogFilters,
|
||||
AdminAuditLogListResponse
|
||||
)
|
||||
from models.database.user import User
|
||||
from models.schema.admin import (AdminAuditLogFilters,
|
||||
AdminAuditLogListResponse,
|
||||
AdminAuditLogResponse)
|
||||
|
||||
router = APIRouter(prefix="/audit")
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -31,15 +29,15 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
@router.get("/logs", response_model=AdminAuditLogListResponse)
|
||||
def get_audit_logs(
|
||||
admin_user_id: Optional[int] = Query(None, description="Filter by admin user"),
|
||||
action: Optional[str] = Query(None, description="Filter by action type"),
|
||||
target_type: Optional[str] = Query(None, description="Filter by target type"),
|
||||
date_from: Optional[datetime] = Query(None, description="Filter from date"),
|
||||
date_to: Optional[datetime] = Query(None, description="Filter to date"),
|
||||
skip: int = Query(0, ge=0, description="Number of records to skip"),
|
||||
limit: int = Query(100, ge=1, le=1000, description="Maximum records to return"),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
admin_user_id: Optional[int] = Query(None, description="Filter by admin user"),
|
||||
action: Optional[str] = Query(None, description="Filter by action type"),
|
||||
target_type: Optional[str] = Query(None, description="Filter by target type"),
|
||||
date_from: Optional[datetime] = Query(None, description="Filter from date"),
|
||||
date_to: Optional[datetime] = Query(None, description="Filter to date"),
|
||||
skip: int = Query(0, ge=0, description="Number of records to skip"),
|
||||
limit: int = Query(100, ge=1, le=1000, description="Maximum records to return"),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Get filtered admin audit logs.
|
||||
@@ -54,7 +52,7 @@ def get_audit_logs(
|
||||
date_from=date_from,
|
||||
date_to=date_to,
|
||||
skip=skip,
|
||||
limit=limit
|
||||
limit=limit,
|
||||
)
|
||||
|
||||
logs = admin_audit_service.get_audit_logs(db, filters)
|
||||
@@ -62,19 +60,14 @@ def get_audit_logs(
|
||||
|
||||
logger.info(f"Admin {current_admin.username} retrieved {len(logs)} audit logs")
|
||||
|
||||
return AdminAuditLogListResponse(
|
||||
logs=logs,
|
||||
total=total,
|
||||
skip=skip,
|
||||
limit=limit
|
||||
)
|
||||
return AdminAuditLogListResponse(logs=logs, total=total, skip=skip, limit=limit)
|
||||
|
||||
|
||||
@router.get("/logs/recent", response_model=list[AdminAuditLogResponse])
|
||||
def get_recent_audit_logs(
|
||||
limit: int = Query(20, ge=1, le=100),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
limit: int = Query(20, ge=1, le=100),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""Get recent audit logs (last 20 by default)."""
|
||||
filters = AdminAuditLogFilters(limit=limit)
|
||||
@@ -83,25 +76,23 @@ def get_recent_audit_logs(
|
||||
|
||||
@router.get("/logs/my-actions", response_model=list[AdminAuditLogResponse])
|
||||
def get_my_actions(
|
||||
limit: int = Query(50, ge=1, le=100),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
limit: int = Query(50, ge=1, le=100),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""Get audit logs for current admin's actions."""
|
||||
return admin_audit_service.get_recent_actions_by_admin(
|
||||
db=db,
|
||||
admin_user_id=current_admin.id,
|
||||
limit=limit
|
||||
db=db, admin_user_id=current_admin.id, limit=limit
|
||||
)
|
||||
|
||||
|
||||
@router.get("/logs/target/{target_type}/{target_id}")
|
||||
def get_actions_by_target(
|
||||
target_type: str,
|
||||
target_id: str,
|
||||
limit: int = Query(50, ge=1, le=100),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
target_type: str,
|
||||
target_id: str,
|
||||
limit: int = Query(50, ge=1, le=100),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Get all actions performed on a specific target.
|
||||
@@ -109,8 +100,5 @@ def get_actions_by_target(
|
||||
Useful for tracking the history of a specific vendor, user, or entity.
|
||||
"""
|
||||
return admin_audit_service.get_actions_by_target(
|
||||
db=db,
|
||||
target_type=target_type,
|
||||
target_id=target_id,
|
||||
limit=limit
|
||||
db=db, target_type=target_type, target_id=target_id, limit=limit
|
||||
)
|
||||
|
||||
@@ -10,16 +10,17 @@ This prevents admin cookies from being sent to vendor routes.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, Depends, Response
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_current_admin_api
|
||||
from app.core.database import get_db
|
||||
from app.core.environment import should_use_secure_cookies
|
||||
from app.services.auth_service import auth_service
|
||||
from app.exceptions import InvalidCredentialsException
|
||||
from models.schema.auth import LoginResponse, UserLogin, UserResponse
|
||||
from app.services.auth_service import auth_service
|
||||
from models.database.user import User
|
||||
from app.api.deps import get_current_admin_api
|
||||
from models.schema.auth import LoginResponse, UserLogin, UserResponse
|
||||
|
||||
router = APIRouter(prefix="/auth")
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -27,9 +28,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
@router.post("/login", response_model=LoginResponse)
|
||||
def admin_login(
|
||||
user_credentials: UserLogin,
|
||||
response: Response,
|
||||
db: Session = Depends(get_db)
|
||||
user_credentials: UserLogin, response: Response, db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Admin login endpoint.
|
||||
@@ -49,7 +48,9 @@ def admin_login(
|
||||
|
||||
# Verify user is admin
|
||||
if login_result["user"].role != "admin":
|
||||
logger.warning(f"Non-admin user attempted admin login: {user_credentials.email_or_username}")
|
||||
logger.warning(
|
||||
f"Non-admin user attempted admin login: {user_credentials.email_or_username}"
|
||||
)
|
||||
raise InvalidCredentialsException("Admin access required")
|
||||
|
||||
logger.info(f"Admin login successful: {login_result['user'].username}")
|
||||
|
||||
@@ -3,25 +3,27 @@ Code Quality API Endpoints
|
||||
RESTful API for architecture validation and violation management
|
||||
"""
|
||||
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_current_admin_api
|
||||
from app.core.database import get_db
|
||||
from app.services.code_quality_service import code_quality_service
|
||||
from app.api.deps import get_current_admin_api
|
||||
from models.database.user import User
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
# Pydantic Models for API
|
||||
|
||||
|
||||
class ScanResponse(BaseModel):
|
||||
"""Response model for a scan"""
|
||||
|
||||
id: int
|
||||
timestamp: str
|
||||
total_files: int
|
||||
@@ -38,6 +40,7 @@ class ScanResponse(BaseModel):
|
||||
|
||||
class ViolationResponse(BaseModel):
|
||||
"""Response model for a violation"""
|
||||
|
||||
id: int
|
||||
scan_id: int
|
||||
rule_id: str
|
||||
@@ -61,6 +64,7 @@ class ViolationResponse(BaseModel):
|
||||
|
||||
class ViolationListResponse(BaseModel):
|
||||
"""Response model for paginated violations list"""
|
||||
|
||||
violations: list[ViolationResponse]
|
||||
total: int
|
||||
page: int
|
||||
@@ -70,34 +74,42 @@ class ViolationListResponse(BaseModel):
|
||||
|
||||
class ViolationDetailResponse(ViolationResponse):
|
||||
"""Response model for single violation with relationships"""
|
||||
|
||||
assignments: list = []
|
||||
comments: list = []
|
||||
|
||||
|
||||
class AssignViolationRequest(BaseModel):
|
||||
"""Request model for assigning a violation"""
|
||||
|
||||
user_id: int = Field(..., description="User ID to assign to")
|
||||
due_date: Optional[datetime] = Field(None, description="Due date for resolution")
|
||||
priority: str = Field("medium", description="Priority level (low, medium, high, critical)")
|
||||
priority: str = Field(
|
||||
"medium", description="Priority level (low, medium, high, critical)"
|
||||
)
|
||||
|
||||
|
||||
class ResolveViolationRequest(BaseModel):
|
||||
"""Request model for resolving a violation"""
|
||||
|
||||
resolution_note: str = Field(..., description="Note about the resolution")
|
||||
|
||||
|
||||
class IgnoreViolationRequest(BaseModel):
|
||||
"""Request model for ignoring a violation"""
|
||||
|
||||
reason: str = Field(..., description="Reason for ignoring")
|
||||
|
||||
|
||||
class AddCommentRequest(BaseModel):
|
||||
"""Request model for adding a comment"""
|
||||
|
||||
comment: str = Field(..., min_length=1, description="Comment text")
|
||||
|
||||
|
||||
class DashboardStatsResponse(BaseModel):
|
||||
"""Response model for dashboard statistics"""
|
||||
|
||||
total_violations: int
|
||||
errors: int
|
||||
warnings: int
|
||||
@@ -116,10 +128,10 @@ class DashboardStatsResponse(BaseModel):
|
||||
|
||||
# API Endpoints
|
||||
|
||||
|
||||
@router.post("/scan", response_model=ScanResponse)
|
||||
async def trigger_scan(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_api)
|
||||
db: Session = Depends(get_db), current_user: User = Depends(get_current_admin_api)
|
||||
):
|
||||
"""
|
||||
Trigger a new architecture scan
|
||||
@@ -127,7 +139,9 @@ async def trigger_scan(
|
||||
Requires authentication. Runs the validator script and stores results.
|
||||
"""
|
||||
try:
|
||||
scan = code_quality_service.run_scan(db, triggered_by=f"manual:{current_user.username}")
|
||||
scan = code_quality_service.run_scan(
|
||||
db, triggered_by=f"manual:{current_user.username}"
|
||||
)
|
||||
|
||||
return ScanResponse(
|
||||
id=scan.id,
|
||||
@@ -138,7 +152,7 @@ async def trigger_scan(
|
||||
warnings=scan.warnings,
|
||||
duration_seconds=scan.duration_seconds,
|
||||
triggered_by=scan.triggered_by,
|
||||
git_commit_hash=scan.git_commit_hash
|
||||
git_commit_hash=scan.git_commit_hash,
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Scan failed: {str(e)}")
|
||||
@@ -148,7 +162,7 @@ async def trigger_scan(
|
||||
async def list_scans(
|
||||
limit: int = Query(30, ge=1, le=100, description="Number of scans to return"),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_api)
|
||||
current_user: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Get scan history
|
||||
@@ -167,7 +181,7 @@ async def list_scans(
|
||||
warnings=scan.warnings,
|
||||
duration_seconds=scan.duration_seconds,
|
||||
triggered_by=scan.triggered_by,
|
||||
git_commit_hash=scan.git_commit_hash
|
||||
git_commit_hash=scan.git_commit_hash,
|
||||
)
|
||||
for scan in scans
|
||||
]
|
||||
@@ -175,15 +189,23 @@ async def list_scans(
|
||||
|
||||
@router.get("/violations", response_model=ViolationListResponse)
|
||||
async def list_violations(
|
||||
scan_id: Optional[int] = Query(None, description="Filter by scan ID (defaults to latest)"),
|
||||
severity: Optional[str] = Query(None, description="Filter by severity (error, warning)"),
|
||||
status: Optional[str] = Query(None, description="Filter by status (open, assigned, resolved, ignored)"),
|
||||
scan_id: Optional[int] = Query(
|
||||
None, description="Filter by scan ID (defaults to latest)"
|
||||
),
|
||||
severity: Optional[str] = Query(
|
||||
None, description="Filter by severity (error, warning)"
|
||||
),
|
||||
status: Optional[str] = Query(
|
||||
None, description="Filter by status (open, assigned, resolved, ignored)"
|
||||
),
|
||||
rule_id: Optional[str] = Query(None, description="Filter by rule ID"),
|
||||
file_path: Optional[str] = Query(None, description="Filter by file path (partial match)"),
|
||||
file_path: Optional[str] = Query(
|
||||
None, description="Filter by file path (partial match)"
|
||||
),
|
||||
page: int = Query(1, ge=1, description="Page number"),
|
||||
page_size: int = Query(50, ge=1, le=200, description="Items per page"),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_api)
|
||||
current_user: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Get violations with filtering and pagination
|
||||
@@ -200,7 +222,7 @@ async def list_violations(
|
||||
rule_id=rule_id,
|
||||
file_path=file_path,
|
||||
limit=page_size,
|
||||
offset=offset
|
||||
offset=offset,
|
||||
)
|
||||
|
||||
total_pages = (total + page_size - 1) // page_size
|
||||
@@ -223,14 +245,14 @@ async def list_violations(
|
||||
resolved_at=v.resolved_at.isoformat() if v.resolved_at else None,
|
||||
resolved_by=v.resolved_by,
|
||||
resolution_note=v.resolution_note,
|
||||
created_at=v.created_at.isoformat()
|
||||
created_at=v.created_at.isoformat(),
|
||||
)
|
||||
for v in violations
|
||||
],
|
||||
total=total,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
total_pages=total_pages
|
||||
total_pages=total_pages,
|
||||
)
|
||||
|
||||
|
||||
@@ -238,7 +260,7 @@ async def list_violations(
|
||||
async def get_violation(
|
||||
violation_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_api)
|
||||
current_user: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Get single violation with details
|
||||
@@ -253,12 +275,12 @@ async def get_violation(
|
||||
# Format assignments
|
||||
assignments = [
|
||||
{
|
||||
'id': a.id,
|
||||
'user_id': a.user_id,
|
||||
'assigned_at': a.assigned_at.isoformat(),
|
||||
'assigned_by': a.assigned_by,
|
||||
'due_date': a.due_date.isoformat() if a.due_date else None,
|
||||
'priority': a.priority
|
||||
"id": a.id,
|
||||
"user_id": a.user_id,
|
||||
"assigned_at": a.assigned_at.isoformat(),
|
||||
"assigned_by": a.assigned_by,
|
||||
"due_date": a.due_date.isoformat() if a.due_date else None,
|
||||
"priority": a.priority,
|
||||
}
|
||||
for a in violation.assignments
|
||||
]
|
||||
@@ -266,10 +288,10 @@ async def get_violation(
|
||||
# Format comments
|
||||
comments = [
|
||||
{
|
||||
'id': c.id,
|
||||
'user_id': c.user_id,
|
||||
'comment': c.comment,
|
||||
'created_at': c.created_at.isoformat()
|
||||
"id": c.id,
|
||||
"user_id": c.user_id,
|
||||
"comment": c.comment,
|
||||
"created_at": c.created_at.isoformat(),
|
||||
}
|
||||
for c in violation.comments
|
||||
]
|
||||
@@ -287,12 +309,14 @@ async def get_violation(
|
||||
suggestion=violation.suggestion,
|
||||
status=violation.status,
|
||||
assigned_to=violation.assigned_to,
|
||||
resolved_at=violation.resolved_at.isoformat() if violation.resolved_at else None,
|
||||
resolved_at=(
|
||||
violation.resolved_at.isoformat() if violation.resolved_at else None
|
||||
),
|
||||
resolved_by=violation.resolved_by,
|
||||
resolution_note=violation.resolution_note,
|
||||
created_at=violation.created_at.isoformat(),
|
||||
assignments=assignments,
|
||||
comments=comments
|
||||
comments=comments,
|
||||
)
|
||||
|
||||
|
||||
@@ -301,7 +325,7 @@ async def assign_violation(
|
||||
violation_id: int,
|
||||
request: AssignViolationRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_api)
|
||||
current_user: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Assign violation to a developer
|
||||
@@ -315,17 +339,19 @@ async def assign_violation(
|
||||
user_id=request.user_id,
|
||||
assigned_by=current_user.id,
|
||||
due_date=request.due_date,
|
||||
priority=request.priority
|
||||
priority=request.priority,
|
||||
)
|
||||
|
||||
return {
|
||||
'id': assignment.id,
|
||||
'violation_id': assignment.violation_id,
|
||||
'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,
|
||||
'priority': assignment.priority
|
||||
"id": assignment.id,
|
||||
"violation_id": assignment.violation_id,
|
||||
"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
|
||||
),
|
||||
"priority": assignment.priority,
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
@@ -336,7 +362,7 @@ async def resolve_violation(
|
||||
violation_id: int,
|
||||
request: ResolveViolationRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_api)
|
||||
current_user: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Mark violation as resolved
|
||||
@@ -348,15 +374,17 @@ async def resolve_violation(
|
||||
db,
|
||||
violation_id=violation_id,
|
||||
resolved_by=current_user.id,
|
||||
resolution_note=request.resolution_note
|
||||
resolution_note=request.resolution_note,
|
||||
)
|
||||
|
||||
return {
|
||||
'id': violation.id,
|
||||
'status': violation.status,
|
||||
'resolved_at': violation.resolved_at.isoformat() if violation.resolved_at else None,
|
||||
'resolved_by': violation.resolved_by,
|
||||
'resolution_note': violation.resolution_note
|
||||
"id": violation.id,
|
||||
"status": violation.status,
|
||||
"resolved_at": (
|
||||
violation.resolved_at.isoformat() if violation.resolved_at else None
|
||||
),
|
||||
"resolved_by": violation.resolved_by,
|
||||
"resolution_note": violation.resolution_note,
|
||||
}
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
@@ -369,7 +397,7 @@ async def ignore_violation(
|
||||
violation_id: int,
|
||||
request: IgnoreViolationRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_api)
|
||||
current_user: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Mark violation as ignored (won't fix)
|
||||
@@ -381,15 +409,17 @@ async def ignore_violation(
|
||||
db,
|
||||
violation_id=violation_id,
|
||||
ignored_by=current_user.id,
|
||||
reason=request.reason
|
||||
reason=request.reason,
|
||||
)
|
||||
|
||||
return {
|
||||
'id': violation.id,
|
||||
'status': violation.status,
|
||||
'resolved_at': violation.resolved_at.isoformat() if violation.resolved_at else None,
|
||||
'resolved_by': violation.resolved_by,
|
||||
'resolution_note': violation.resolution_note
|
||||
"id": violation.id,
|
||||
"status": violation.status,
|
||||
"resolved_at": (
|
||||
violation.resolved_at.isoformat() if violation.resolved_at else None
|
||||
),
|
||||
"resolved_by": violation.resolved_by,
|
||||
"resolution_note": violation.resolution_note,
|
||||
}
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=404, detail=str(e))
|
||||
@@ -402,7 +432,7 @@ async def add_comment(
|
||||
violation_id: int,
|
||||
request: AddCommentRequest,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_api)
|
||||
current_user: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Add comment to violation
|
||||
@@ -414,15 +444,15 @@ async def add_comment(
|
||||
db,
|
||||
violation_id=violation_id,
|
||||
user_id=current_user.id,
|
||||
comment=request.comment
|
||||
comment=request.comment,
|
||||
)
|
||||
|
||||
return {
|
||||
'id': comment.id,
|
||||
'violation_id': comment.violation_id,
|
||||
'user_id': comment.user_id,
|
||||
'comment': comment.comment,
|
||||
'created_at': comment.created_at.isoformat()
|
||||
"id": comment.id,
|
||||
"violation_id": comment.violation_id,
|
||||
"user_id": comment.user_id,
|
||||
"comment": comment.comment,
|
||||
"created_at": comment.created_at.isoformat(),
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
@@ -430,8 +460,7 @@ async def add_comment(
|
||||
|
||||
@router.get("/stats", response_model=DashboardStatsResponse)
|
||||
async def get_dashboard_stats(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_admin_api)
|
||||
db: Session = Depends(get_db), current_user: User = Depends(get_current_admin_api)
|
||||
):
|
||||
"""
|
||||
Get dashboard statistics
|
||||
|
||||
@@ -10,6 +10,7 @@ Platform administrators can:
|
||||
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -26,24 +27,43 @@ logger = logging.getLogger(__name__)
|
||||
# REQUEST/RESPONSE SCHEMAS
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class ContentPageCreate(BaseModel):
|
||||
"""Schema for creating a content page."""
|
||||
slug: str = Field(..., max_length=100, description="URL-safe identifier (about, faq, contact, etc.)")
|
||||
|
||||
slug: str = Field(
|
||||
...,
|
||||
max_length=100,
|
||||
description="URL-safe identifier (about, faq, contact, etc.)",
|
||||
)
|
||||
title: str = Field(..., max_length=200, description="Page title")
|
||||
content: str = Field(..., description="HTML or Markdown content")
|
||||
content_format: str = Field(default="html", description="Content format: html or markdown")
|
||||
template: str = Field(default="default", max_length=50, description="Template name (default, minimal, modern)")
|
||||
meta_description: Optional[str] = Field(None, max_length=300, description="SEO meta description")
|
||||
meta_keywords: Optional[str] = Field(None, max_length=300, description="SEO keywords")
|
||||
content_format: str = Field(
|
||||
default="html", description="Content format: html or markdown"
|
||||
)
|
||||
template: str = Field(
|
||||
default="default",
|
||||
max_length=50,
|
||||
description="Template name (default, minimal, modern)",
|
||||
)
|
||||
meta_description: Optional[str] = Field(
|
||||
None, max_length=300, description="SEO meta description"
|
||||
)
|
||||
meta_keywords: Optional[str] = Field(
|
||||
None, max_length=300, description="SEO keywords"
|
||||
)
|
||||
is_published: bool = Field(default=False, description="Publish immediately")
|
||||
show_in_footer: bool = Field(default=True, description="Show in footer navigation")
|
||||
show_in_header: bool = Field(default=False, description="Show in header navigation")
|
||||
display_order: int = Field(default=0, description="Display order (lower = first)")
|
||||
vendor_id: Optional[int] = Field(None, description="Vendor ID (None for platform default)")
|
||||
vendor_id: Optional[int] = Field(
|
||||
None, description="Vendor ID (None for platform default)"
|
||||
)
|
||||
|
||||
|
||||
class ContentPageUpdate(BaseModel):
|
||||
"""Schema for updating a content page."""
|
||||
|
||||
title: Optional[str] = Field(None, max_length=200)
|
||||
content: Optional[str] = None
|
||||
content_format: Optional[str] = None
|
||||
@@ -58,6 +78,7 @@ class ContentPageUpdate(BaseModel):
|
||||
|
||||
class ContentPageResponse(BaseModel):
|
||||
"""Schema for content page response."""
|
||||
|
||||
id: int
|
||||
vendor_id: Optional[int]
|
||||
vendor_name: Optional[str]
|
||||
@@ -84,11 +105,12 @@ class ContentPageResponse(BaseModel):
|
||||
# PLATFORM DEFAULT PAGES (vendor_id=NULL)
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@router.get("/platform", response_model=List[ContentPageResponse])
|
||||
def list_platform_pages(
|
||||
include_unpublished: bool = Query(False, description="Include draft pages"),
|
||||
current_user: User = Depends(get_current_admin_api),
|
||||
db: Session = Depends(get_db)
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
List all platform default content pages.
|
||||
@@ -96,8 +118,7 @@ def list_platform_pages(
|
||||
These are used as fallbacks when vendors haven't created custom pages.
|
||||
"""
|
||||
pages = content_page_service.list_all_platform_pages(
|
||||
db,
|
||||
include_unpublished=include_unpublished
|
||||
db, include_unpublished=include_unpublished
|
||||
)
|
||||
|
||||
return [page.to_dict() for page in pages]
|
||||
@@ -107,7 +128,7 @@ def list_platform_pages(
|
||||
def create_platform_page(
|
||||
page_data: ContentPageCreate,
|
||||
current_user: User = Depends(get_current_admin_api),
|
||||
db: Session = Depends(get_db)
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
Create a new platform default content page.
|
||||
@@ -129,7 +150,7 @@ def create_platform_page(
|
||||
show_in_footer=page_data.show_in_footer,
|
||||
show_in_header=page_data.show_in_header,
|
||||
display_order=page_data.display_order,
|
||||
created_by=current_user.id
|
||||
created_by=current_user.id,
|
||||
)
|
||||
|
||||
return page.to_dict()
|
||||
@@ -139,12 +160,13 @@ def create_platform_page(
|
||||
# ALL CONTENT PAGES (Platform + Vendors)
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@router.get("/", response_model=List[ContentPageResponse])
|
||||
def list_all_pages(
|
||||
vendor_id: Optional[int] = Query(None, description="Filter by vendor ID"),
|
||||
include_unpublished: bool = Query(False, description="Include draft pages"),
|
||||
current_user: User = Depends(get_current_admin_api),
|
||||
db: Session = Depends(get_db)
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""
|
||||
List all content pages (platform defaults and vendor overrides).
|
||||
@@ -153,15 +175,14 @@ def list_all_pages(
|
||||
"""
|
||||
if vendor_id:
|
||||
pages = content_page_service.list_all_vendor_pages(
|
||||
db,
|
||||
vendor_id=vendor_id,
|
||||
include_unpublished=include_unpublished
|
||||
db, vendor_id=vendor_id, include_unpublished=include_unpublished
|
||||
)
|
||||
else:
|
||||
# Get all pages (both platform and vendor)
|
||||
from models.database.content_page import ContentPage
|
||||
from sqlalchemy import and_
|
||||
|
||||
from models.database.content_page import ContentPage
|
||||
|
||||
filters = []
|
||||
if not include_unpublished:
|
||||
filters.append(ContentPage.is_published == True)
|
||||
@@ -169,7 +190,9 @@ def list_all_pages(
|
||||
pages = (
|
||||
db.query(ContentPage)
|
||||
.filter(and_(*filters) if filters else True)
|
||||
.order_by(ContentPage.vendor_id, ContentPage.display_order, ContentPage.title)
|
||||
.order_by(
|
||||
ContentPage.vendor_id, ContentPage.display_order, ContentPage.title
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
||||
@@ -180,7 +203,7 @@ def list_all_pages(
|
||||
def get_page(
|
||||
page_id: int,
|
||||
current_user: User = Depends(get_current_admin_api),
|
||||
db: Session = Depends(get_db)
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Get a specific content page by ID."""
|
||||
page = content_page_service.get_page_by_id(db, page_id)
|
||||
@@ -196,7 +219,7 @@ def update_page(
|
||||
page_id: int,
|
||||
page_data: ContentPageUpdate,
|
||||
current_user: User = Depends(get_current_admin_api),
|
||||
db: Session = Depends(get_db)
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Update a content page (platform or vendor)."""
|
||||
page = content_page_service.update_page(
|
||||
@@ -212,7 +235,7 @@ def update_page(
|
||||
show_in_footer=page_data.show_in_footer,
|
||||
show_in_header=page_data.show_in_header,
|
||||
display_order=page_data.display_order,
|
||||
updated_by=current_user.id
|
||||
updated_by=current_user.id,
|
||||
)
|
||||
|
||||
if not page:
|
||||
@@ -225,7 +248,7 @@ def update_page(
|
||||
def delete_page(
|
||||
page_id: int,
|
||||
current_user: User = Depends(get_current_admin_api),
|
||||
db: Session = Depends(get_db)
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Delete a content page."""
|
||||
success = content_page_service.delete_page(db, page_id)
|
||||
|
||||
@@ -5,6 +5,7 @@ Admin dashboard and statistics endpoints.
|
||||
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -87,4 +88,4 @@ def get_platform_statistics(
|
||||
"products": stats_service.get_product_statistics(db),
|
||||
"orders": stats_service.get_order_statistics(db),
|
||||
"imports": stats_service.get_import_statistics(db),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ from app.api.deps import get_current_admin_api
|
||||
from app.core.database import get_db
|
||||
from app.services.admin_service import admin_service
|
||||
from app.services.stats_service import stats_service
|
||||
from models.schema.marketplace_import_job import MarketplaceImportJobResponse
|
||||
from models.database.user import User
|
||||
from models.schema.marketplace_import_job import MarketplaceImportJobResponse
|
||||
|
||||
router = APIRouter(prefix="/marketplace-import-jobs")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -1 +1 @@
|
||||
# Platform monitoring and alerts
|
||||
# Platform monitoring and alerts
|
||||
|
||||
@@ -16,16 +16,13 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_current_admin_api
|
||||
from app.core.database import get_db
|
||||
from models.schema.admin import (
|
||||
AdminNotificationCreate,
|
||||
AdminNotificationResponse,
|
||||
AdminNotificationListResponse,
|
||||
PlatformAlertCreate,
|
||||
PlatformAlertResponse,
|
||||
PlatformAlertListResponse,
|
||||
PlatformAlertResolve
|
||||
)
|
||||
from models.database.user import User
|
||||
from models.schema.admin import (AdminNotificationCreate,
|
||||
AdminNotificationListResponse,
|
||||
AdminNotificationResponse,
|
||||
PlatformAlertCreate,
|
||||
PlatformAlertListResponse,
|
||||
PlatformAlertResolve, PlatformAlertResponse)
|
||||
|
||||
router = APIRouter(prefix="/notifications")
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -35,6 +32,7 @@ logger = logging.getLogger(__name__)
|
||||
# ADMIN NOTIFICATIONS
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@router.get("", response_model=AdminNotificationListResponse)
|
||||
def get_notifications(
|
||||
priority: Optional[str] = Query(None, description="Filter by priority"),
|
||||
@@ -47,11 +45,7 @@ def get_notifications(
|
||||
"""Get admin notifications with filtering."""
|
||||
# TODO: Implement notification service
|
||||
return AdminNotificationListResponse(
|
||||
notifications=[],
|
||||
total=0,
|
||||
unread_count=0,
|
||||
skip=skip,
|
||||
limit=limit
|
||||
notifications=[], total=0, unread_count=0, skip=skip, limit=limit
|
||||
)
|
||||
|
||||
|
||||
@@ -90,10 +84,13 @@ def mark_all_as_read(
|
||||
# PLATFORM ALERTS
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@router.get("/alerts", response_model=PlatformAlertListResponse)
|
||||
def get_platform_alerts(
|
||||
severity: Optional[str] = Query(None, description="Filter by severity"),
|
||||
is_resolved: Optional[bool] = Query(None, description="Filter by resolution status"),
|
||||
is_resolved: Optional[bool] = Query(
|
||||
None, description="Filter by resolution status"
|
||||
),
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(50, ge=1, le=100),
|
||||
db: Session = Depends(get_db),
|
||||
@@ -102,12 +99,7 @@ def get_platform_alerts(
|
||||
"""Get platform alerts with filtering."""
|
||||
# TODO: Implement alert service
|
||||
return PlatformAlertListResponse(
|
||||
alerts=[],
|
||||
total=0,
|
||||
active_count=0,
|
||||
critical_count=0,
|
||||
skip=skip,
|
||||
limit=limit
|
||||
alerts=[], total=0, active_count=0, critical_count=0, skip=skip, limit=limit
|
||||
)
|
||||
|
||||
|
||||
@@ -147,5 +139,5 @@ def get_alert_statistics(
|
||||
"total_alerts": 0,
|
||||
"active_alerts": 0,
|
||||
"critical_alerts": 0,
|
||||
"resolved_today": 0
|
||||
"resolved_today": 0,
|
||||
}
|
||||
|
||||
@@ -16,15 +16,11 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_current_admin_api
|
||||
from app.core.database import get_db
|
||||
from app.services.admin_settings_service import admin_settings_service
|
||||
from app.services.admin_audit_service import admin_audit_service
|
||||
from models.schema.admin import (
|
||||
AdminSettingCreate,
|
||||
AdminSettingResponse,
|
||||
AdminSettingUpdate,
|
||||
AdminSettingListResponse
|
||||
)
|
||||
from app.services.admin_settings_service import admin_settings_service
|
||||
from models.database.user import User
|
||||
from models.schema.admin import (AdminSettingCreate, AdminSettingListResponse,
|
||||
AdminSettingResponse, AdminSettingUpdate)
|
||||
|
||||
router = APIRouter(prefix="/settings")
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -32,10 +28,10 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
@router.get("", response_model=AdminSettingListResponse)
|
||||
def get_all_settings(
|
||||
category: Optional[str] = Query(None, description="Filter by category"),
|
||||
is_public: Optional[bool] = Query(None, description="Filter by public flag"),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
category: Optional[str] = Query(None, description="Filter by category"),
|
||||
is_public: Optional[bool] = Query(None, description="Filter by public flag"),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Get all platform settings.
|
||||
@@ -46,16 +42,14 @@ def get_all_settings(
|
||||
settings = admin_settings_service.get_all_settings(db, category, is_public)
|
||||
|
||||
return AdminSettingListResponse(
|
||||
settings=settings,
|
||||
total=len(settings),
|
||||
category=category
|
||||
settings=settings, total=len(settings), category=category
|
||||
)
|
||||
|
||||
|
||||
@router.get("/categories")
|
||||
def get_setting_categories(
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""Get list of all setting categories."""
|
||||
# This could be enhanced to return counts per category
|
||||
@@ -66,22 +60,23 @@ def get_setting_categories(
|
||||
"marketplace",
|
||||
"notifications",
|
||||
"integrations",
|
||||
"payments"
|
||||
"payments",
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{key}", response_model=AdminSettingResponse)
|
||||
def get_setting(
|
||||
key: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
key: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""Get specific setting by key."""
|
||||
setting = admin_settings_service.get_setting_by_key(db, key)
|
||||
|
||||
if not setting:
|
||||
from fastapi import HTTPException
|
||||
|
||||
raise HTTPException(status_code=404, detail=f"Setting '{key}' not found")
|
||||
|
||||
return AdminSettingResponse.model_validate(setting)
|
||||
@@ -89,9 +84,9 @@ def get_setting(
|
||||
|
||||
@router.post("", response_model=AdminSettingResponse)
|
||||
def create_setting(
|
||||
setting_data: AdminSettingCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
setting_data: AdminSettingCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Create new platform setting.
|
||||
@@ -99,9 +94,7 @@ def create_setting(
|
||||
Setting keys should be lowercase with underscores (e.g., max_vendors_allowed).
|
||||
"""
|
||||
result = admin_settings_service.create_setting(
|
||||
db=db,
|
||||
setting_data=setting_data,
|
||||
admin_user_id=current_admin.id
|
||||
db=db, setting_data=setting_data, admin_user_id=current_admin.id
|
||||
)
|
||||
|
||||
# Log action
|
||||
@@ -111,7 +104,10 @@ def create_setting(
|
||||
action="create_setting",
|
||||
target_type="setting",
|
||||
target_id=setting_data.key,
|
||||
details={"category": setting_data.category, "value_type": setting_data.value_type}
|
||||
details={
|
||||
"category": setting_data.category,
|
||||
"value_type": setting_data.value_type,
|
||||
},
|
||||
)
|
||||
|
||||
return result
|
||||
@@ -119,19 +115,16 @@ def create_setting(
|
||||
|
||||
@router.put("/{key}", response_model=AdminSettingResponse)
|
||||
def update_setting(
|
||||
key: str,
|
||||
update_data: AdminSettingUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
key: str,
|
||||
update_data: AdminSettingUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""Update existing setting value."""
|
||||
old_value = admin_settings_service.get_setting_value(db, key)
|
||||
|
||||
result = admin_settings_service.update_setting(
|
||||
db=db,
|
||||
key=key,
|
||||
update_data=update_data,
|
||||
admin_user_id=current_admin.id
|
||||
db=db, key=key, update_data=update_data, admin_user_id=current_admin.id
|
||||
)
|
||||
|
||||
# Log action
|
||||
@@ -141,7 +134,7 @@ def update_setting(
|
||||
action="update_setting",
|
||||
target_type="setting",
|
||||
target_id=key,
|
||||
details={"old_value": str(old_value), "new_value": update_data.value}
|
||||
details={"old_value": str(old_value), "new_value": update_data.value},
|
||||
)
|
||||
|
||||
return result
|
||||
@@ -149,9 +142,9 @@ def update_setting(
|
||||
|
||||
@router.post("/upsert", response_model=AdminSettingResponse)
|
||||
def upsert_setting(
|
||||
setting_data: AdminSettingCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
setting_data: AdminSettingCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Create or update setting (upsert).
|
||||
@@ -159,9 +152,7 @@ def upsert_setting(
|
||||
If setting exists, updates its value. If not, creates new setting.
|
||||
"""
|
||||
result = admin_settings_service.upsert_setting(
|
||||
db=db,
|
||||
setting_data=setting_data,
|
||||
admin_user_id=current_admin.id
|
||||
db=db, setting_data=setting_data, admin_user_id=current_admin.id
|
||||
)
|
||||
|
||||
# Log action
|
||||
@@ -171,7 +162,7 @@ def upsert_setting(
|
||||
action="upsert_setting",
|
||||
target_type="setting",
|
||||
target_id=setting_data.key,
|
||||
details={"category": setting_data.category}
|
||||
details={"category": setting_data.category},
|
||||
)
|
||||
|
||||
return result
|
||||
@@ -179,10 +170,10 @@ def upsert_setting(
|
||||
|
||||
@router.delete("/{key}")
|
||||
def delete_setting(
|
||||
key: str,
|
||||
confirm: bool = Query(False, description="Must be true to confirm deletion"),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
key: str,
|
||||
confirm: bool = Query(False, description="Must be true to confirm deletion"),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Delete platform setting.
|
||||
@@ -195,13 +186,11 @@ def delete_setting(
|
||||
if not confirm:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Deletion requires confirmation parameter: confirm=true"
|
||||
detail="Deletion requires confirmation parameter: confirm=true",
|
||||
)
|
||||
|
||||
message = admin_settings_service.delete_setting(
|
||||
db=db,
|
||||
key=key,
|
||||
admin_user_id=current_admin.id
|
||||
db=db, key=key, admin_user_id=current_admin.id
|
||||
)
|
||||
|
||||
# Log action
|
||||
@@ -211,7 +200,7 @@ def delete_setting(
|
||||
action="delete_setting",
|
||||
target_type="setting",
|
||||
target_id=key,
|
||||
details={}
|
||||
details={},
|
||||
)
|
||||
|
||||
return {"message": message}
|
||||
|
||||
@@ -13,8 +13,8 @@ from app.api.deps import get_current_admin_api
|
||||
from app.core.database import get_db
|
||||
from app.services.admin_service import admin_service
|
||||
from app.services.stats_service import stats_service
|
||||
from models.schema.auth import UserResponse
|
||||
from models.database.user import User
|
||||
from models.schema.auth import UserResponse
|
||||
|
||||
router = APIRouter(prefix="/users")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -12,24 +12,22 @@ Follows the architecture pattern:
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from fastapi import APIRouter, Depends, Path, Body, Query
|
||||
from fastapi import APIRouter, Body, Depends, Path, Query
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_current_admin_api
|
||||
from app.core.database import get_db
|
||||
from app.services.vendor_domain_service import vendor_domain_service
|
||||
from app.exceptions import VendorNotFoundException
|
||||
from models.schema.vendor_domain import (
|
||||
VendorDomainCreate,
|
||||
VendorDomainUpdate,
|
||||
VendorDomainResponse,
|
||||
VendorDomainListResponse,
|
||||
DomainVerificationInstructions,
|
||||
DomainVerificationResponse,
|
||||
DomainDeletionResponse,
|
||||
)
|
||||
from app.services.vendor_domain_service import vendor_domain_service
|
||||
from models.database.user import User
|
||||
from models.database.vendor import Vendor
|
||||
from models.schema.vendor_domain import (DomainDeletionResponse,
|
||||
DomainVerificationInstructions,
|
||||
DomainVerificationResponse,
|
||||
VendorDomainCreate,
|
||||
VendorDomainListResponse,
|
||||
VendorDomainResponse,
|
||||
VendorDomainUpdate)
|
||||
|
||||
router = APIRouter(prefix="/vendors")
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -57,10 +55,10 @@ def _get_vendor_by_id(db: Session, vendor_id: int) -> Vendor:
|
||||
|
||||
@router.post("/{vendor_id}/domains", response_model=VendorDomainResponse)
|
||||
def add_vendor_domain(
|
||||
vendor_id: int = Path(..., description="Vendor ID", gt=0),
|
||||
domain_data: VendorDomainCreate = Body(...),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
vendor_id: int = Path(..., description="Vendor ID", gt=0),
|
||||
domain_data: VendorDomainCreate = Body(...),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Add a custom domain to vendor (Admin only).
|
||||
@@ -88,9 +86,7 @@ def add_vendor_domain(
|
||||
- 422: Invalid domain format or reserved subdomain
|
||||
"""
|
||||
domain = vendor_domain_service.add_domain(
|
||||
db=db,
|
||||
vendor_id=vendor_id,
|
||||
domain_data=domain_data
|
||||
db=db, vendor_id=vendor_id, domain_data=domain_data
|
||||
)
|
||||
|
||||
return VendorDomainResponse(
|
||||
@@ -111,9 +107,9 @@ def add_vendor_domain(
|
||||
|
||||
@router.get("/{vendor_id}/domains", response_model=VendorDomainListResponse)
|
||||
def list_vendor_domains(
|
||||
vendor_id: int = Path(..., description="Vendor ID", gt=0),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
vendor_id: int = Path(..., description="Vendor ID", gt=0),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
List all domains for a vendor (Admin only).
|
||||
@@ -148,15 +144,15 @@ def list_vendor_domains(
|
||||
)
|
||||
for d in domains
|
||||
],
|
||||
total=len(domains)
|
||||
total=len(domains),
|
||||
)
|
||||
|
||||
|
||||
@router.get("/domains/{domain_id}", response_model=VendorDomainResponse)
|
||||
def get_domain_details(
|
||||
domain_id: int = Path(..., description="Domain ID", gt=0),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
domain_id: int = Path(..., description="Domain ID", gt=0),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Get detailed information about a specific domain (Admin only).
|
||||
@@ -174,7 +170,9 @@ def get_domain_details(
|
||||
is_active=domain.is_active,
|
||||
is_verified=domain.is_verified,
|
||||
ssl_status=domain.ssl_status,
|
||||
verification_token=domain.verification_token if not domain.is_verified else None,
|
||||
verification_token=(
|
||||
domain.verification_token if not domain.is_verified else None
|
||||
),
|
||||
verified_at=domain.verified_at,
|
||||
ssl_verified_at=domain.ssl_verified_at,
|
||||
created_at=domain.created_at,
|
||||
@@ -184,10 +182,10 @@ def get_domain_details(
|
||||
|
||||
@router.put("/domains/{domain_id}", response_model=VendorDomainResponse)
|
||||
def update_vendor_domain(
|
||||
domain_id: int = Path(..., description="Domain ID", gt=0),
|
||||
domain_update: VendorDomainUpdate = Body(...),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
domain_id: int = Path(..., description="Domain ID", gt=0),
|
||||
domain_update: VendorDomainUpdate = Body(...),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Update domain settings (Admin only).
|
||||
@@ -206,9 +204,7 @@ def update_vendor_domain(
|
||||
- 400: Cannot activate unverified domain
|
||||
"""
|
||||
domain = vendor_domain_service.update_domain(
|
||||
db=db,
|
||||
domain_id=domain_id,
|
||||
domain_update=domain_update
|
||||
db=db, domain_id=domain_id, domain_update=domain_update
|
||||
)
|
||||
|
||||
return VendorDomainResponse(
|
||||
@@ -229,9 +225,9 @@ def update_vendor_domain(
|
||||
|
||||
@router.delete("/domains/{domain_id}", response_model=DomainDeletionResponse)
|
||||
def delete_vendor_domain(
|
||||
domain_id: int = Path(..., description="Domain ID", gt=0),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
domain_id: int = Path(..., description="Domain ID", gt=0),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Delete a custom domain (Admin only).
|
||||
@@ -250,17 +246,15 @@ def delete_vendor_domain(
|
||||
message = vendor_domain_service.delete_domain(db, domain_id)
|
||||
|
||||
return DomainDeletionResponse(
|
||||
message=message,
|
||||
domain=domain_name,
|
||||
vendor_id=vendor_id
|
||||
message=message, domain=domain_name, vendor_id=vendor_id
|
||||
)
|
||||
|
||||
|
||||
@router.post("/domains/{domain_id}/verify", response_model=DomainVerificationResponse)
|
||||
def verify_domain_ownership(
|
||||
domain_id: int = Path(..., description="Domain ID", gt=0),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
domain_id: int = Path(..., description="Domain ID", gt=0),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Verify domain ownership via DNS TXT record (Admin only).
|
||||
@@ -290,15 +284,18 @@ def verify_domain_ownership(
|
||||
message=message,
|
||||
domain=domain.domain,
|
||||
verified_at=domain.verified_at,
|
||||
is_verified=domain.is_verified
|
||||
is_verified=domain.is_verified,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/domains/{domain_id}/verification-instructions", response_model=DomainVerificationInstructions)
|
||||
@router.get(
|
||||
"/domains/{domain_id}/verification-instructions",
|
||||
response_model=DomainVerificationInstructions,
|
||||
)
|
||||
def get_domain_verification_instructions(
|
||||
domain_id: int = Path(..., description="Domain ID", gt=0),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
domain_id: int = Path(..., description="Domain ID", gt=0),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Get DNS verification instructions for domain (Admin only).
|
||||
@@ -324,5 +321,5 @@ def get_domain_verification_instructions(
|
||||
verification_token=instructions["verification_token"],
|
||||
instructions=instructions["instructions"],
|
||||
txt_record=instructions["txt_record"],
|
||||
common_registrars=instructions["common_registrars"]
|
||||
common_registrars=instructions["common_registrars"],
|
||||
)
|
||||
|
||||
@@ -20,11 +20,8 @@ from sqlalchemy.orm import Session
|
||||
from app.api.deps import get_current_admin_api, get_db
|
||||
from app.services.vendor_theme_service import vendor_theme_service
|
||||
from models.database.user import User
|
||||
from models.schema.vendor_theme import (
|
||||
VendorThemeResponse,
|
||||
VendorThemeUpdate,
|
||||
ThemePresetListResponse
|
||||
)
|
||||
from models.schema.vendor_theme import (ThemePresetListResponse,
|
||||
VendorThemeResponse, VendorThemeUpdate)
|
||||
|
||||
router = APIRouter(prefix="/vendor-themes")
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -34,10 +31,9 @@ logger = logging.getLogger(__name__)
|
||||
# PRESET ENDPOINTS
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@router.get("/presets", response_model=ThemePresetListResponse)
|
||||
async def get_theme_presets(
|
||||
current_admin: User = Depends(get_current_admin_api)
|
||||
):
|
||||
async def get_theme_presets(current_admin: User = Depends(get_current_admin_api)):
|
||||
"""
|
||||
Get all available theme presets with preview information.
|
||||
|
||||
@@ -59,11 +55,12 @@ async def get_theme_presets(
|
||||
# THEME RETRIEVAL
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@router.get("/{vendor_code}", response_model=VendorThemeResponse)
|
||||
async def get_vendor_theme(
|
||||
vendor_code: str = Path(..., description="Vendor code"),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api)
|
||||
vendor_code: str = Path(..., description="Vendor code"),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Get theme configuration for a vendor.
|
||||
@@ -93,12 +90,13 @@ async def get_vendor_theme(
|
||||
# THEME UPDATE
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@router.put("/{vendor_code}", response_model=VendorThemeResponse)
|
||||
async def update_vendor_theme(
|
||||
vendor_code: str = Path(..., description="Vendor code"),
|
||||
theme_data: VendorThemeUpdate = None,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api)
|
||||
vendor_code: str = Path(..., description="Vendor code"),
|
||||
theme_data: VendorThemeUpdate = None,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Update or create theme for a vendor.
|
||||
@@ -140,12 +138,13 @@ async def update_vendor_theme(
|
||||
# PRESET APPLICATION
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@router.post("/{vendor_code}/preset/{preset_name}")
|
||||
async def apply_theme_preset(
|
||||
vendor_code: str = Path(..., description="Vendor code"),
|
||||
preset_name: str = Path(..., description="Preset name"),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api)
|
||||
vendor_code: str = Path(..., description="Vendor code"),
|
||||
preset_name: str = Path(..., description="Preset name"),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Apply a theme preset to a vendor.
|
||||
@@ -184,7 +183,7 @@ async def apply_theme_preset(
|
||||
|
||||
return {
|
||||
"message": f"Applied {preset_name} preset successfully",
|
||||
"theme": theme.to_dict()
|
||||
"theme": theme.to_dict(),
|
||||
}
|
||||
|
||||
|
||||
@@ -192,11 +191,12 @@ async def apply_theme_preset(
|
||||
# THEME DELETION
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@router.delete("/{vendor_code}")
|
||||
async def delete_vendor_theme(
|
||||
vendor_code: str = Path(..., description="Vendor code"),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api)
|
||||
vendor_code: str = Path(..., description="Vendor code"),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Delete custom theme for a vendor.
|
||||
|
||||
@@ -6,28 +6,24 @@ Vendor management endpoints for admin.
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query, Path, Body
|
||||
from fastapi import APIRouter, Body, Depends, Path, Query
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_current_admin_api
|
||||
from app.core.database import get_db
|
||||
from app.exceptions import (ConfirmationRequiredException,
|
||||
VendorNotFoundException)
|
||||
from app.services.admin_service import admin_service
|
||||
from app.services.stats_service import stats_service
|
||||
from app.exceptions import VendorNotFoundException, ConfirmationRequiredException
|
||||
from models.schema.stats import VendorStatsResponse
|
||||
from models.schema.vendor import (
|
||||
VendorListResponse,
|
||||
VendorResponse,
|
||||
VendorDetailResponse,
|
||||
VendorCreate,
|
||||
VendorCreateResponse,
|
||||
VendorUpdate,
|
||||
VendorTransferOwnership,
|
||||
VendorTransferOwnershipResponse,
|
||||
)
|
||||
from models.database.user import User
|
||||
from models.database.vendor import Vendor
|
||||
from sqlalchemy import func
|
||||
from models.schema.stats import VendorStatsResponse
|
||||
from models.schema.vendor import (VendorCreate, VendorCreateResponse,
|
||||
VendorDetailResponse, VendorListResponse,
|
||||
VendorResponse, VendorTransferOwnership,
|
||||
VendorTransferOwnershipResponse,
|
||||
VendorUpdate)
|
||||
|
||||
router = APIRouter(prefix="/vendors")
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -60,9 +56,11 @@ def _get_vendor_by_identifier(db: Session, identifier: str) -> Vendor:
|
||||
pass
|
||||
|
||||
# Try as vendor_code (case-insensitive)
|
||||
vendor = db.query(Vendor).filter(
|
||||
func.upper(Vendor.vendor_code) == identifier.upper()
|
||||
).first()
|
||||
vendor = (
|
||||
db.query(Vendor)
|
||||
.filter(func.upper(Vendor.vendor_code) == identifier.upper())
|
||||
.first()
|
||||
)
|
||||
|
||||
if not vendor:
|
||||
raise VendorNotFoundException(identifier, identifier_type="code")
|
||||
@@ -72,9 +70,9 @@ def _get_vendor_by_identifier(db: Session, identifier: str) -> Vendor:
|
||||
|
||||
@router.post("", response_model=VendorCreateResponse)
|
||||
def create_vendor_with_owner(
|
||||
vendor_data: VendorCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
vendor_data: VendorCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Create a new vendor with owner user account (Admin only).
|
||||
@@ -93,8 +91,7 @@ def create_vendor_with_owner(
|
||||
Returns vendor details with owner credentials.
|
||||
"""
|
||||
vendor, owner_user, temp_password = admin_service.create_vendor_with_owner(
|
||||
db=db,
|
||||
vendor_data=vendor_data
|
||||
db=db, vendor_data=vendor_data
|
||||
)
|
||||
|
||||
return VendorCreateResponse(
|
||||
@@ -121,19 +118,19 @@ def create_vendor_with_owner(
|
||||
owner_email=owner_user.email,
|
||||
owner_username=owner_user.username,
|
||||
temporary_password=temp_password,
|
||||
login_url=f"http://localhost:8000/vendor/{vendor.subdomain}/login"
|
||||
login_url=f"http://localhost:8000/vendor/{vendor.subdomain}/login",
|
||||
)
|
||||
|
||||
|
||||
@router.get("", response_model=VendorListResponse)
|
||||
def get_all_vendors_admin(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
search: Optional[str] = Query(None, description="Search by name or vendor code"),
|
||||
is_active: Optional[bool] = Query(None),
|
||||
is_verified: Optional[bool] = Query(None),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
search: Optional[str] = Query(None, description="Search by name or vendor code"),
|
||||
is_active: Optional[bool] = Query(None),
|
||||
is_verified: Optional[bool] = Query(None),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""Get all vendors with filtering (Admin only)."""
|
||||
vendors, total = admin_service.get_all_vendors(
|
||||
@@ -142,15 +139,15 @@ def get_all_vendors_admin(
|
||||
limit=limit,
|
||||
search=search,
|
||||
is_active=is_active,
|
||||
is_verified=is_verified
|
||||
is_verified=is_verified,
|
||||
)
|
||||
return VendorListResponse(vendors=vendors, total=total, skip=skip, limit=limit)
|
||||
|
||||
|
||||
@router.get("/stats", response_model=VendorStatsResponse)
|
||||
def get_vendor_statistics_endpoint(
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""Get vendor statistics for admin dashboard (Admin only)."""
|
||||
stats = stats_service.get_vendor_statistics(db)
|
||||
@@ -165,9 +162,9 @@ def get_vendor_statistics_endpoint(
|
||||
|
||||
@router.get("/{vendor_identifier}", response_model=VendorDetailResponse)
|
||||
def get_vendor_details(
|
||||
vendor_identifier: str = Path(..., description="Vendor ID or vendor_code"),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
vendor_identifier: str = Path(..., description="Vendor ID or vendor_code"),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Get detailed vendor information including owner details (Admin only).
|
||||
@@ -208,10 +205,10 @@ def get_vendor_details(
|
||||
|
||||
@router.put("/{vendor_identifier}", response_model=VendorDetailResponse)
|
||||
def update_vendor(
|
||||
vendor_identifier: str = Path(..., description="Vendor ID or vendor_code"),
|
||||
vendor_update: VendorUpdate = Body(...),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
vendor_identifier: str = Path(..., description="Vendor ID or vendor_code"),
|
||||
vendor_update: VendorUpdate = Body(...),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Update vendor information (Admin only).
|
||||
@@ -257,12 +254,15 @@ def update_vendor(
|
||||
)
|
||||
|
||||
|
||||
@router.post("/{vendor_identifier}/transfer-ownership", response_model=VendorTransferOwnershipResponse)
|
||||
@router.post(
|
||||
"/{vendor_identifier}/transfer-ownership",
|
||||
response_model=VendorTransferOwnershipResponse,
|
||||
)
|
||||
def transfer_vendor_ownership(
|
||||
vendor_identifier: str = Path(..., description="Vendor ID or vendor_code"),
|
||||
transfer_data: VendorTransferOwnership = Body(...),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
vendor_identifier: str = Path(..., description="Vendor ID or vendor_code"),
|
||||
transfer_data: VendorTransferOwnership = Body(...),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Transfer vendor ownership to another user (Admin only).
|
||||
@@ -311,10 +311,10 @@ def transfer_vendor_ownership(
|
||||
|
||||
@router.put("/{vendor_identifier}/verification", response_model=VendorDetailResponse)
|
||||
def toggle_vendor_verification(
|
||||
vendor_identifier: str = Path(..., description="Vendor ID or vendor_code"),
|
||||
verification_data: dict = Body(..., example={"is_verified": True}),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
vendor_identifier: str = Path(..., description="Vendor ID or vendor_code"),
|
||||
verification_data: dict = Body(..., example={"is_verified": True}),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Toggle vendor verification status (Admin only).
|
||||
@@ -362,10 +362,10 @@ def toggle_vendor_verification(
|
||||
|
||||
@router.put("/{vendor_identifier}/status", response_model=VendorDetailResponse)
|
||||
def toggle_vendor_status(
|
||||
vendor_identifier: str = Path(..., description="Vendor ID or vendor_code"),
|
||||
status_data: dict = Body(..., example={"is_active": True}),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
vendor_identifier: str = Path(..., description="Vendor ID or vendor_code"),
|
||||
status_data: dict = Body(..., example={"is_active": True}),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Toggle vendor active status (Admin only).
|
||||
@@ -413,10 +413,10 @@ def toggle_vendor_status(
|
||||
|
||||
@router.delete("/{vendor_identifier}")
|
||||
def delete_vendor(
|
||||
vendor_identifier: str = Path(..., description="Vendor ID or vendor_code"),
|
||||
confirm: bool = Query(False, description="Must be true to confirm deletion"),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
vendor_identifier: str = Path(..., description="Vendor ID or vendor_code"),
|
||||
confirm: bool = Query(False, description="Must be true to confirm deletion"),
|
||||
db: Session = Depends(get_db),
|
||||
current_admin: User = Depends(get_current_admin_api),
|
||||
):
|
||||
"""
|
||||
Delete vendor and all associated data (Admin only).
|
||||
@@ -436,7 +436,7 @@ def delete_vendor(
|
||||
if not confirm:
|
||||
raise ConfirmationRequiredException(
|
||||
operation="delete_vendor",
|
||||
message="Deletion requires confirmation parameter: confirm=true"
|
||||
message="Deletion requires confirmation parameter: confirm=true",
|
||||
)
|
||||
|
||||
vendor = _get_vendor_by_identifier(db, vendor_identifier)
|
||||
|
||||
Reference in New Issue
Block a user