from __future__ import annotations from datetime import datetime from uuid import uuid4 from sqlalchemy import BigInteger, Boolean, DateTime, Float, ForeignKey, Integer, LargeBinary, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column, relationship from ..db import Base from ..timeutil import utcnow_naive from ._types import JsonType as _JsonType class AppUser(Base): """Local netx application user (login account).""" __tablename__ = "app_user" id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex) username: Mapped[str] = mapped_column(String(128), unique=True, index=True) password_hash: Mapped[str] = mapped_column(String(255), default="") role: Mapped[str] = mapped_column(String(32), default="user", index=True) # admin | user # Optional capability override; empty => role defaults (see auth_scopes). scopes: Mapped[list] = mapped_column(_JsonType, default=list) is_active: Mapped[bool] = mapped_column(Boolean, default=True, index=True) must_change_password: Mapped[bool] = mapped_column(Boolean, default=False) created_by: Mapped[str] = mapped_column(String(64), default="") created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive) updated_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive) class AuditLog(Base): """Application audit trail for authenticated (and auth) actions.""" __tablename__ = "audit_log" id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex) ts: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive, index=True) actor_user_id: Mapped[str] = mapped_column(String(64), default="", index=True) actor_username: Mapped[str] = mapped_column(String(128), default="", index=True) action: Mapped[str] = mapped_column(String(128), default="", index=True) method: Mapped[str] = mapped_column(String(16), default="") path: Mapped[str] = mapped_column(String(512), default="", index=True) status_code: Mapped[int] = mapped_column(Integer, default=0) client_ip: Mapped[str] = mapped_column(String(128), default="") user_agent: Mapped[str] = mapped_column(String(512), default="") detail: Mapped[dict] = mapped_column(_JsonType, default=dict) class ApiToken(Base): """Long-lived API token (MCP/scripts); hashed at rest.""" __tablename__ = "api_token" id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex) name: Mapped[str] = mapped_column(String(128), default="") token_hash: Mapped[str] = mapped_column(String(128), unique=True, index=True) user_id: Mapped[str] = mapped_column(String(64), index=True) # Capability subset; empty inherits owner user scopes (then intersected). scopes: Mapped[list] = mapped_column(_JsonType, default=list) created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive) expires_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, index=True) last_used_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) revoked_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)