- Add database fields for language preferences: - Vendor: dashboard_language, storefront_language, storefront_languages - User: preferred_language - Customer: preferred_language - Add language middleware for request-level language detection: - Cookie-based persistence - Browser Accept-Language fallback - Vendor storefront language constraints - Add language API endpoints (/api/v1/language/*): - POST /set - Set language preference - GET /current - Get current language info - GET /list - List available languages - DELETE /clear - Clear preference - Add i18n utilities (app/utils/i18n.py): - JSON-based translation loading - Jinja2 template integration - Language resolution helpers - Add reusable language selector macros for templates - Add languageSelector() Alpine.js component - Add translation files (en, fr, de, lb) in static/locales/ - Add architecture rules documentation for language implementation - Update marketplace-product-detail.js to use native language names 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
131 lines
4.5 KiB
Python
131 lines
4.5 KiB
Python
# models/database/user.py - IMPROVED VERSION
|
|
"""
|
|
User model with authentication support.
|
|
|
|
ROLE CLARIFICATION:
|
|
- User.role should ONLY contain platform-level roles:
|
|
* "admin" - Platform administrator (full system access)
|
|
* "vendor" - Any user who owns or is part of a vendor team
|
|
|
|
- Vendor-specific roles (manager, staff, etc.) are stored in VendorUser.role
|
|
- Customers are NOT in the User table - they use the Customer model
|
|
"""
|
|
|
|
import enum
|
|
|
|
from sqlalchemy import Boolean, Column, DateTime, Integer, String
|
|
from sqlalchemy.orm import relationship
|
|
|
|
from app.core.database import Base
|
|
from models.database.base import TimestampMixin
|
|
|
|
|
|
class UserRole(str, enum.Enum):
|
|
"""Platform-level user roles."""
|
|
|
|
ADMIN = "admin" # Platform administrator
|
|
VENDOR = "vendor" # Vendor owner or team member
|
|
|
|
|
|
class User(Base, TimestampMixin):
|
|
"""Represents a platform user (admins and vendors only)."""
|
|
|
|
__tablename__ = "users"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
email = Column(String, unique=True, index=True, nullable=False)
|
|
username = Column(String, unique=True, index=True, nullable=False)
|
|
first_name = Column(String)
|
|
last_name = Column(String)
|
|
hashed_password = Column(String, nullable=False)
|
|
|
|
# Platform-level role only (admin or vendor)
|
|
role = Column(String, nullable=False, default=UserRole.VENDOR.value)
|
|
|
|
is_active = Column(Boolean, default=True, nullable=False)
|
|
is_email_verified = Column(Boolean, default=False, nullable=False)
|
|
last_login = Column(DateTime, nullable=True)
|
|
|
|
# Language preference (NULL = use context default: vendor dashboard_language or system default)
|
|
# Supported: en, fr, de, lb
|
|
preferred_language = Column(String(5), nullable=True)
|
|
|
|
# Relationships
|
|
marketplace_import_jobs = relationship(
|
|
"MarketplaceImportJob", back_populates="user"
|
|
)
|
|
owned_companies = relationship("Company", back_populates="owner")
|
|
vendor_memberships = relationship(
|
|
"VendorUser", foreign_keys="[VendorUser.user_id]", back_populates="user"
|
|
)
|
|
|
|
def __repr__(self):
|
|
"""String representation of the User object."""
|
|
return f"<User(id={self.id}, username='{self.username}', email='{self.email}', role='{self.role}')>"
|
|
|
|
@property
|
|
def full_name(self):
|
|
"""Returns the full name of the user, combining first and last names if available."""
|
|
if self.first_name and self.last_name:
|
|
return f"{self.first_name} {self.last_name}"
|
|
return self.username
|
|
|
|
@property
|
|
def is_admin(self) -> bool:
|
|
"""Check if user is a platform admin."""
|
|
return self.role == UserRole.ADMIN.value
|
|
|
|
@property
|
|
def is_vendor(self) -> bool:
|
|
"""Check if user is a vendor (owner or team member)."""
|
|
return self.role == UserRole.VENDOR.value
|
|
|
|
def is_owner_of(self, vendor_id: int) -> bool:
|
|
"""
|
|
Check if user is the owner of a specific vendor.
|
|
|
|
Ownership is determined via company ownership:
|
|
User owns Company → Company has Vendor → User owns Vendor
|
|
"""
|
|
for company in self.owned_companies:
|
|
if any(v.id == vendor_id for v in company.vendors):
|
|
return True
|
|
return False
|
|
|
|
def is_member_of(self, vendor_id: int) -> bool:
|
|
"""Check if user is a member of a specific vendor (owner or team)."""
|
|
# Check if owner (via company)
|
|
if self.is_owner_of(vendor_id):
|
|
return True
|
|
# Check if team member
|
|
return any(
|
|
vm.vendor_id == vendor_id and vm.is_active for vm in self.vendor_memberships
|
|
)
|
|
|
|
def get_vendor_role(self, vendor_id: int) -> str:
|
|
"""Get user's role within a specific vendor."""
|
|
# Check if owner (via company)
|
|
if self.is_owner_of(vendor_id):
|
|
return "owner"
|
|
|
|
# Check team membership
|
|
for vm in self.vendor_memberships:
|
|
if vm.vendor_id == vendor_id and vm.is_active:
|
|
return vm.role.name if vm.role else "member"
|
|
|
|
return None
|
|
|
|
def has_vendor_permission(self, vendor_id: int, permission: str) -> bool:
|
|
"""Check if user has a specific permission in a vendor."""
|
|
# Owners have all permissions
|
|
if self.is_owner_of(vendor_id):
|
|
return True
|
|
|
|
# Check team member permissions
|
|
for vm in self.vendor_memberships:
|
|
if vm.vendor_id == vendor_id and vm.is_active:
|
|
if vm.role and permission in vm.role.permissions:
|
|
return True
|
|
|
|
return False
|