Unify database schema: Resolve all User model conflicts and auth table incompatibilities

Major changes:
- Consolidate 3 different User models into single unified model (models/user.py)
- Use UUID primary keys throughout (matches existing database schema)
- Add comprehensive authentication fields while preserving existing data
- Remove duplicate User model from auth.py, keep APIKey/RefreshToken/TokenBlacklist
- Update all imports to use unified User model consistently
- Create database migration (002_add_auth_fields.sql) for safe schema upgrade
- Fix frontend User interface to handle UUID string IDs
- Add backward compatibility fields (name property, role field)
- Maintain relationships for authentication features (api_keys, refresh_tokens)

Schema conflicts resolved:
 Migration schema (UUID, 7 fields) + Basic model (Integer, 6 fields) + Auth model (Integer, 10 fields)
   → Unified model (UUID, 12 fields with full backward compatibility)
 Field inconsistencies (name vs full_name) resolved with compatibility property
 Database foreign key constraints updated for UUID relationships
 JWT token handling fixed for UUID user IDs

This completes the holistic database schema unification requested after quick
patching caused conflicts. All existing data preserved, full auth system functional.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
anthonyrawlins
2025-07-10 22:56:14 +10:00
parent 2547a5c2b3
commit eda5b2d6d3
8 changed files with 203 additions and 85 deletions

View File

@@ -1,14 +1,85 @@
from sqlalchemy import Column, Integer, String, DateTime, Boolean
"""
Unified User model for Hive platform.
Combines authentication and basic user functionality with UUID support.
"""
from datetime import datetime
from typing import Optional, List
import uuid
from sqlalchemy import Column, String, DateTime, Boolean, Text, ForeignKey
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from passlib.context import CryptContext
from ..core.database import Base
class User(Base):
__tablename__ = "users"
# Password hashing context
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True)
email = Column(String, unique=True, index=True)
hashed_password = Column(String)
class User(Base):
"""Unified user model with authentication and authorization support."""
__tablename__ = "users"
# Use UUID to match existing database schema
id = Column(UUID(as_uuid=True), primary_key=True, index=True, default=uuid.uuid4)
username = Column(String(50), unique=True, index=True, nullable=True) # Made nullable for compatibility
email = Column(String(255), unique=True, index=True, nullable=False)
hashed_password = Column(String(255), nullable=False)
# Extended user information (for backward compatibility)
full_name = Column(String(255), nullable=True)
role = Column(String(50), nullable=True) # For backward compatibility with existing code
# User status and permissions
is_active = Column(Boolean, default=True)
is_superuser = Column(Boolean, default=False)
is_verified = Column(Boolean, default=False)
# Timestamps (match existing database schema)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
last_login = Column(DateTime(timezone=True), nullable=True)
# Relationships for authentication features
api_keys = relationship("APIKey", back_populates="user", cascade="all, delete-orphan")
refresh_tokens = relationship("RefreshToken", back_populates="user", cascade="all, delete-orphan")
def verify_password(self, password: str) -> bool:
"""Verify a password against the hashed password."""
return pwd_context.verify(password, self.hashed_password)
@classmethod
def hash_password(cls, password: str) -> str:
"""Hash a password for storage."""
return pwd_context.hash(password)
def set_password(self, password: str) -> None:
"""Set a new password for the user."""
self.hashed_password = self.hash_password(password)
def update_last_login(self) -> None:
"""Update the last login timestamp."""
self.last_login = datetime.utcnow()
@property
def name(self) -> str:
"""Backward compatibility property for 'name' field."""
return self.full_name or self.username or self.email.split('@')[0]
def to_dict(self) -> dict:
"""Convert user to dictionary (excluding sensitive data)."""
return {
"id": str(self.id), # Convert UUID to string for JSON serialization
"username": self.username,
"email": self.email,
"full_name": self.full_name,
"name": self.name, # Backward compatibility
"role": self.role,
"is_active": self.is_active,
"is_superuser": self.is_superuser,
"is_verified": self.is_verified,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
"last_login": self.last_login.isoformat() if self.last_login else None,
}