major refactoring adding vendor and customer features

This commit is contained in:
2025-10-11 09:09:25 +02:00
parent f569995883
commit dd16198276
126 changed files with 15109 additions and 3747 deletions

View File

@@ -0,0 +1,17 @@
"""
Admin API endpoints.
"""
from fastapi import APIRouter
from . import auth, vendors, dashboard, users
# Create admin router
router = APIRouter()
# Include all admin sub-routers
router.include_router(auth.router, tags=["admin-auth"])
router.include_router(vendors.router, tags=["admin-vendors"])
router.include_router(dashboard.router, tags=["admin-dashboard"])
router.include_router(users.router, tags=["admin-users"])
__all__ = ["router"]

58
app/api/v1/admin/auth.py Normal file
View File

@@ -0,0 +1,58 @@
# app/api/v1/admin/auth.py
"""
Admin authentication endpoints.
This module provides:
- Admin user login
- Admin token validation
- Admin-specific authentication logic
"""
import logging
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.services.auth_service import auth_service
from app.exceptions import InvalidCredentialsException
from models.schemas.auth import LoginResponse, UserLogin
router = APIRouter()
logger = logging.getLogger(__name__)
@router.post("/login", response_model=LoginResponse)
def admin_login(user_credentials: UserLogin, db: Session = Depends(get_db)):
"""
Admin login endpoint.
Only allows users with 'admin' role to login.
Returns JWT token for authenticated admin users.
"""
# Authenticate user
login_result = auth_service.login_user(db=db, user_credentials=user_credentials)
# Verify user is admin
if login_result["user"].role != "admin":
logger.warning(f"Non-admin user attempted admin login: {user_credentials.username}")
raise InvalidCredentialsException("Admin access required")
logger.info(f"Admin login successful: {login_result['user'].username}")
return LoginResponse(
access_token=login_result["token_data"]["access_token"],
token_type=login_result["token_data"]["token_type"],
expires_in=login_result["token_data"]["expires_in"],
user=login_result["user"],
)
@router.post("/logout")
def admin_logout():
"""
Admin logout endpoint.
Client should remove token from storage.
Server-side token invalidation can be implemented here if needed.
"""
return {"message": "Logged out successfully"}

View File

@@ -0,0 +1,90 @@
# app/api/v1/admin/dashboard.py
"""
Admin dashboard and statistics endpoints.
"""
import logging
from typing import List
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.api.deps import get_current_admin_user
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.database.user import User
from models.schemas.stats import MarketplaceStatsResponse, StatsResponse
router = APIRouter(prefix="/dashboard")
logger = logging.getLogger(__name__)
@router.get("")
def get_admin_dashboard(
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""Get admin dashboard with platform statistics (Admin only)."""
return {
"platform": {
"name": "Multi-Tenant Ecommerce Platform",
"version": "1.0.0",
},
"users": admin_service.get_user_statistics(db),
"vendors": admin_service.get_vendor_statistics(db),
"recent_vendors": admin_service.get_recent_vendors(db, limit=5),
"recent_imports": admin_service.get_recent_import_jobs(db, limit=10),
}
@router.get("/stats", response_model=StatsResponse)
def get_comprehensive_stats(
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""Get comprehensive platform statistics (Admin only)."""
stats_data = stats_service.get_comprehensive_stats(db=db)
return StatsResponse(
total_products=stats_data["total_products"],
unique_brands=stats_data["unique_brands"],
unique_categories=stats_data["unique_categories"],
unique_marketplaces=stats_data["unique_marketplaces"],
unique_vendors=stats_data["unique_vendors"],
total_inventory_entries=stats_data["total_inventory_entries"],
total_inventory_quantity=stats_data["total_inventory_quantity"],
)
@router.get("/stats/marketplace", response_model=List[MarketplaceStatsResponse])
def get_marketplace_stats(
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""Get statistics broken down by marketplace (Admin only)."""
marketplace_stats = stats_service.get_marketplace_breakdown_stats(db=db)
return [
MarketplaceStatsResponse(
marketplace=stat["marketplace"],
total_products=stat["total_products"],
unique_vendors=stat["unique_vendors"],
unique_brands=stat["unique_brands"],
)
for stat in marketplace_stats
]
@router.get("/stats/platform")
def get_platform_statistics(
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""Get comprehensive platform statistics (Admin only)."""
return {
"users": admin_service.get_user_statistics(db),
"vendors": admin_service.get_vendor_statistics(db),
"products": admin_service.get_product_statistics(db),
"orders": admin_service.get_order_statistics(db),
"imports": admin_service.get_import_statistics(db),
}

View File

@@ -0,0 +1,49 @@
# app/api/v1/admin/marketplace.py
"""
Marketplace import job monitoring endpoints for admin.
"""
import logging
from typing import List, Optional
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from app.api.deps import get_current_admin_user
from app.core.database import get_db
from app.services.admin_service import admin_service
from models.schemas.marketplace_import_job import MarketplaceImportJobResponse
from models.database.user import User
router = APIRouter(prefix="/marketplace-import-jobs")
logger = logging.getLogger(__name__)
@router.get("", response_model=List[MarketplaceImportJobResponse])
def get_all_marketplace_import_jobs(
marketplace: Optional[str] = Query(None),
vendor_name: Optional[str] = Query(None),
status: Optional[str] = Query(None),
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=100),
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""Get all marketplace import jobs (Admin only)."""
return admin_service.get_marketplace_import_jobs(
db=db,
marketplace=marketplace,
vendor_name=vendor_name,
status=status,
skip=skip,
limit=limit,
)
@router.get("/stats")
def get_import_statistics(
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""Get marketplace import statistics (Admin only)."""
return admin_service.get_import_statistics(db)

51
app/api/v1/admin/users.py Normal file
View File

@@ -0,0 +1,51 @@
# app/api/v1/admin/users.py
"""
User management endpoints for admin.
"""
import logging
from typing import List
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from app.api.deps import get_current_admin_user
from app.core.database import get_db
from app.services.admin_service import admin_service
from models.schemas.auth import UserResponse
from models.database.user import User
router = APIRouter(prefix="/users")
logger = logging.getLogger(__name__)
@router.get("", response_model=List[UserResponse])
def get_all_users(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=1000),
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""Get all users (Admin only)."""
users = admin_service.get_all_users(db=db, skip=skip, limit=limit)
return [UserResponse.model_validate(user) for user in users]
@router.put("/{user_id}/status")
def toggle_user_status(
user_id: int,
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""Toggle user active status (Admin only)."""
user, message = admin_service.toggle_user_status(db, user_id, current_admin.id)
return {"message": message}
@router.get("/stats")
def get_user_statistics(
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""Get user statistics for admin dashboard (Admin only)."""
return admin_service.get_user_statistics(db)

143
app/api/v1/admin/vendors.py Normal file
View File

@@ -0,0 +1,143 @@
# app/api/v1/admin/vendors.py
"""
Vendor management endpoints for admin.
"""
import logging
from typing import Optional
from fastapi import APIRouter, Depends, Query, HTTPException
from sqlalchemy.orm import Session
from app.api.deps import get_current_admin_user
from app.core.database import get_db
from app.services.admin_service import admin_service
from models.schemas.vendor import VendorListResponse, VendorResponse, VendorCreate
from models.database.user import User
router = APIRouter(prefix="/vendors")
logger = logging.getLogger(__name__)
@router.post("", response_model=VendorResponse)
def create_vendor_with_owner(
vendor_data: VendorCreate,
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""
Create a new vendor with owner user account (Admin only).
This endpoint:
1. Creates a new vendor
2. Creates an owner user account for the vendor
3. Sets up default roles (Owner, Manager, Editor, Viewer)
4. Sends welcome email to vendor owner (if email service configured)
Returns the created vendor with owner information.
"""
vendor, owner_user, temp_password = admin_service.create_vendor_with_owner(
db=db,
vendor_data=vendor_data
)
return {
**VendorResponse.model_validate(vendor).model_dump(),
"owner_email": owner_user.email,
"owner_username": owner_user.username,
"temporary_password": temp_password, # Only shown once!
"login_url": f"{vendor.subdomain}.platform.com/vendor/login" if vendor.subdomain else None
}
@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_user),
):
"""Get all vendors with admin view (Admin only)."""
vendors, total = admin_service.get_all_vendors(
db=db,
skip=skip,
limit=limit,
search=search,
is_active=is_active,
is_verified=is_verified
)
return VendorListResponse(vendors=vendors, total=total, skip=skip, limit=limit)
@router.get("/{vendor_id}", response_model=VendorResponse)
def get_vendor_details(
vendor_id: int,
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""Get detailed vendor information (Admin only)."""
vendor = admin_service.get_vendor_by_id(db, vendor_id)
return VendorResponse.model_validate(vendor)
@router.put("/{vendor_id}/verify")
def verify_vendor(
vendor_id: int,
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""Verify/unverify vendor (Admin only)."""
vendor, message = admin_service.verify_vendor(db, vendor_id)
return {"message": message, "vendor": VendorResponse.model_validate(vendor)}
@router.put("/{vendor_id}/status")
def toggle_vendor_status(
vendor_id: int,
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""Toggle vendor active status (Admin only)."""
vendor, message = admin_service.toggle_vendor_status(db, vendor_id)
return {"message": message, "vendor": VendorResponse.model_validate(vendor)}
@router.delete("/{vendor_id}")
def delete_vendor(
vendor_id: int,
confirm: bool = Query(False, description="Must be true to confirm deletion"),
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""
Delete vendor and all associated data (Admin only).
WARNING: This is destructive and will delete:
- Vendor account
- All products
- All orders
- All customers
- All team members
Requires confirmation parameter.
"""
if not confirm:
raise HTTPException(
status_code=400,
detail="Deletion requires confirmation parameter: confirm=true"
)
message = admin_service.delete_vendor(db, vendor_id)
return {"message": message}
@router.get("/stats/vendors")
def get_vendor_statistics(
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_admin_user),
):
"""Get vendor statistics for admin dashboard (Admin only)."""
return admin_service.get_vendor_statistics(db)