from sqlalchemy import ( Column, String, Boolean, DateTime, Integer, Text, Index, TypeDecorator ) from sqlalchemy.sql import func from .base import Base import uuid import sys import os # Ensure core is in path for encryption sys.path.append(os.path.join(os.path.dirname(__file__), "../../../..")) from core.security.encryption import encryptor class EncryptedString(TypeDecorator): impl = String cache_ok = True def process_bind_param(self, value, dialect): if value is None: return None return encryptor.encrypt(str(value)) def process_result_value(self, value, dialect): if value is None: return None return encryptor.decrypt(value) class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, autoincrement=True) user_hash = Column(String(64), unique=True, nullable=False, index=True) # PII (Encrypted) phone_number = Column(EncryptedString(500), nullable=True, index=True) kra_pin = Column(EncryptedString(500), nullable=True) national_id = Column(EncryptedString(500), nullable=True) tenant_id = Column(String(64), nullable=True, index=True) # Profile display_name = Column(String(100)) user_type = Column(String(20), default="personal") # personal | business | institutional language_preference = Column(String(10), default="en") # en | sw | mixed country_code = Column(String(5), default="KE") currency = Column(String(5), default="KES") # Subscription tier = Column(String(20), default="free") # free | personal_pro | business | institutional tier_expires_at = Column(DateTime, nullable=True) # Auth pin_hash = Column(String(128), nullable=False) is_active = Column(Boolean, default=True) # Consent (ODPC compliance) consent_data_storage = Column(Boolean, default=False) consent_training_data = Column(Boolean, default=False) consent_marketing = Column(Boolean, default=False) consent_given_at = Column(DateTime, nullable=True) # Timestamps created_at = Column(DateTime, server_default=func.now()) updated_at = Column(DateTime, onupdate=func.now()) last_active_at = Column(DateTime, server_default=func.now()) __table_args__ = ( Index("ix_users_hash", "user_hash"), Index("ix_users_tier", "tier"), Index("ix_users_created", "created_at"), ) class ApiKey(Base): __tablename__ = "api_keys" id = Column(Integer, primary_key=True) key_hash = Column(String(128), unique=True, nullable=False, index=True) user_hash = Column(String(64), nullable=False, index=True) name = Column(String(100)) # e.g. "Hermes Production", "My App" scopes = Column(Text, default="[]") # JSON: ["process", "files", "hermes"] tenant_id = Column(String(64), nullable=True, index=True) # For institutional: links key to tenant is_active = Column(Boolean, default=True) last_used_at = Column(DateTime, nullable=True) expires_at = Column(DateTime, nullable=True) requests_today = Column(Integer, default=0) created_at = Column(DateTime, server_default=func.now()) __table_args__ = ( Index("ix_apikeys_user", "user_hash"), Index("ix_apikeys_tenant", "tenant_id"), )