Spaces:
Running
Running
| 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"), | |
| ) | |