""" LandveX Admin Backend — Database Base Model SQLAlchemy declarative base and mixins """ from datetime import datetime, timezone from typing import Any, Dict from uuid import uuid4 from sqlalchemy import Column, DateTime, String, event from sqlalchemy.dialects.postgresql import UUID, JSONB from sqlalchemy.orm import DeclarativeBase, declared_attr class Base(DeclarativeBase): """Base class for all database models.""" # Generate tablename automatically from class name @declared_attr.directive def __tablename__(cls) -> str: return cls.__name__.lower() + "s" # Common columns for all tables id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4) created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc)) def to_dict(self) -> Dict[str, Any]: """Convert model instance to dictionary.""" result = {} for column in self.__table__.columns: value = getattr(self, column.name) if value is not None: if isinstance(value, datetime): result[column.name] = value.isoformat() else: result[column.name] = value return result def __repr__(self) -> str: return f"<{self.__class__.__name__}(id={self.id})>" class AuditMixin: """Mixin to add audit fields to models.""" created_by = Column(String(255), nullable=True) updated_by = Column(String(255), nullable=True) deleted_at = Column(DateTime(timezone=True), nullable=True) deleted_by = Column(String(255), nullable=True) is_deleted = Column(String(1), default="N") # Soft delete flag @property def is_active(self) -> bool: """Check if record is active (not soft-deleted).""" return self.is_deleted == "N" and self.deleted_at is None class MetadataMixin: """Mixin to add JSON metadata field to models.""" metadata_json = Column("metadata", JSONB, default=dict) def get_metadata(self) -> Dict[str, Any]: """Get metadata as dictionary.""" return self.metadata_json or {} def set_metadata(self, key: str, value: Any) -> None: """Set a metadata value.""" if self.metadata_json is None: self.metadata_json = {} self.metadata_json[key] = value