refactor: migrate vendor APIs to token-based context and consolidate architecture

## Vendor-in-Token Architecture (Complete Migration)
- Migrate all vendor API endpoints from require_vendor_context() to token_vendor_id
- Update permission dependencies to extract vendor from JWT token
- Add vendor exceptions: VendorAccessDeniedException, VendorOwnerOnlyException,
  InsufficientVendorPermissionsException
- Shop endpoints retain require_vendor_context() for URL-based detection
- Add AUTH-004 architecture rule enforcing vendor context patterns
- Fix marketplace router missing /marketplace prefix

## Exception Pattern Fixes (API-003/API-004)
- Services raise domain exceptions, endpoints let them bubble up
- Add code_quality and content_page exception modules
- Move business logic from endpoints to services (admin, auth, content_page)
- Fix exception handling in admin, shop, and vendor endpoints

## Tailwind CSS Consolidation
- Consolidate CSS to per-area files (admin, vendor, shop, platform)
- Remove shared/cdn-fallback.html and shared/css/tailwind.min.css
- Update all templates to use area-specific Tailwind output files
- Remove Node.js config (package.json, postcss.config.js, tailwind.config.js)

## Documentation & Cleanup
- Update vendor-in-token-architecture.md with completed migration status
- Update architecture-rules.md with new rules
- Move migration docs to docs/development/migration/
- Remove duplicate/obsolete documentation files
- Merge pytest.ini settings into pyproject.toml

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-04 22:24:45 +01:00
parent 76f8a59954
commit 8a367077e1
85 changed files with 21787 additions and 134978 deletions

View File

@@ -25,8 +25,8 @@ from app.exceptions import InvalidCredentialsException
from app.services.auth_service import auth_service
from middleware.vendor_context import get_current_vendor
from models.database.user import User
from models.database.vendor import Role, Vendor, VendorUser
from models.schema.auth import UserLogin
from models.database.vendor import Vendor
from models.schema.auth import LogoutResponse, UserLogin, VendorUserResponse
router = APIRouter(prefix="/auth")
logger = logging.getLogger(__name__)
@@ -68,13 +68,7 @@ def vendor_login(
if not vendor and hasattr(user_credentials, "vendor_code"):
vendor_code = getattr(user_credentials, "vendor_code", None)
if vendor_code:
vendor = (
db.query(Vendor)
.filter(
Vendor.vendor_code == vendor_code.upper(), Vendor.is_active == True
)
.first()
)
vendor = auth_service.get_vendor_by_code(db, vendor_code)
# Authenticate user
login_result = auth_service.login_user(db=db, user_credentials=user_credentials)
@@ -91,51 +85,22 @@ def vendor_login(
vendor_role = "Member"
if vendor:
# Check if user is vendor owner (via company ownership)
is_owner = vendor.company and vendor.company.owner_user_id == user.id
# Check if user has access to this vendor
has_access, role = auth_service.get_user_vendor_role(db, user, vendor)
if is_owner:
vendor_role = "Owner"
if has_access:
vendor_role = role
else:
# Check if user is team member
vendor_user = (
db.query(VendorUser)
.join(Role)
.filter(
VendorUser.user_id == user.id,
VendorUser.vendor_id == vendor.id,
VendorUser.is_active == True,
)
.first()
logger.warning(
f"User {user.username} attempted login to vendor {vendor.vendor_code} "
f"but is not authorized"
)
raise InvalidCredentialsException(
"You do not have access to this vendor"
)
if vendor_user:
vendor_role = vendor_user.role.name
else:
logger.warning(
f"User {user.username} attempted login to vendor {vendor.vendor_code} "
f"but is not authorized"
)
raise InvalidCredentialsException(
"You do not have access to this vendor"
)
else:
# No vendor context - find which vendor this user belongs to
# Check owned vendors first (via company ownership)
for company in user.owned_companies:
if company.vendors:
vendor = company.vendors[0]
vendor_role = "Owner"
break
# Check vendor memberships if no owned vendor found
if not vendor and user.vendor_memberships:
active_membership = next(
(vm for vm in user.vendor_memberships if vm.is_active), None
)
if active_membership:
vendor = active_membership.vendor
vendor_role = active_membership.role.name
vendor, vendor_role = auth_service.find_user_vendor(user)
if not vendor:
raise InvalidCredentialsException("User is not associated with any vendor")
@@ -194,7 +159,7 @@ def vendor_login(
)
@router.post("/logout")
@router.post("/logout", response_model=LogoutResponse)
def vendor_logout(response: Response):
"""
Vendor team member logout.
@@ -212,10 +177,10 @@ def vendor_logout(response: Response):
logger.debug("Deleted vendor_token cookie")
return {"message": "Logged out successfully"}
return LogoutResponse(message="Logged out successfully")
@router.get("/me")
@router.get("/me", response_model=VendorUserResponse)
def get_current_vendor_user(
user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db)
):
@@ -225,10 +190,10 @@ def get_current_vendor_user(
This endpoint can be called to verify authentication and get user info.
Requires Authorization header (header-only authentication for API endpoints).
"""
return {
"id": user.id,
"username": user.username,
"email": user.email,
"role": user.role,
"is_active": user.is_active,
}
return VendorUserResponse(
id=user.id,
username=user.username,
email=user.email,
role=user.role,
is_active=user.is_active,
)