from datetime import datetime from sqlalchemy import BigInteger, Boolean, DateTime, Integer, String, func from sqlalchemy.orm import Mapped, mapped_column from .database import Base class Admin(Base): """An admin-portal login. `is_admin` grants user/API-key/log management.""" __tablename__ = "radadmin_admins" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) username: Mapped[str] = mapped_column(String(64), nullable=False, unique=True) password_hash: Mapped[str] = mapped_column(String(255), nullable=False) is_admin: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) must_change_password: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) created_at: Mapped[datetime | None] = mapped_column( DateTime, nullable=True, server_default=func.current_timestamp() ) class Session(Base): """An opaque login token handed to the browser (Authorization: Bearer).""" __tablename__ = "radadmin_sessions" token: Mapped[str] = mapped_column(String(64), primary_key=True) admin_id: Mapped[int] = mapped_column(Integer, nullable=False) created_at: Mapped[datetime | None] = mapped_column( DateTime, nullable=True, server_default=func.current_timestamp() ) expires_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) class ApiKey(Base): """A programmatic key (X-API-Key). Only the hash is stored.""" __tablename__ = "radadmin_api_keys" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(64), nullable=False) key_prefix: Mapped[str] = mapped_column(String(16), nullable=False) key_hash: Mapped[str] = mapped_column(String(255), nullable=False) created_by: Mapped[str | None] = mapped_column(String(64), nullable=True) created_at: Mapped[datetime | None] = mapped_column( DateTime, nullable=True, server_default=func.current_timestamp() ) last_used_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) revoked: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) class LogEntry(Base): """One activity-log row per state-changing action or auth event.""" __tablename__ = "radadmin_logs" id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) username: Mapped[str | None] = mapped_column(String(64), nullable=True) action: Mapped[str] = mapped_column(String(64), nullable=False) detail: Mapped[str | None] = mapped_column(String(512), nullable=True) ip_address: Mapped[str | None] = mapped_column(String(45), nullable=True) created_at: Mapped[datetime | None] = mapped_column( DateTime, nullable=True, server_default=func.current_timestamp() ) class Customer(Base): __tablename__ = "customers" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) username: Mapped[str] = mapped_column(String(64), nullable=False) mac_address: Mapped[str] = mapped_column(String(17), nullable=False) status: Mapped[str] = mapped_column(String(10), nullable=False, default="new") created_at: Mapped[datetime | None] = mapped_column( DateTime, nullable=True, server_default=func.current_timestamp() ) class RadadminClient(Base): """Human-only client metadata (name/phone/alias), keyed by MAC. RADIUS ignores this.""" __tablename__ = "radadmin_clients" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) # Not DB-unique on its own — uniqueness is enforced only among live rows by the # generated `active_mac` column (see radadmin_schema.sql). Soft-deleted rows # (deleted_at set) may share a mac_address with the live row and each other. mac_address: Mapped[str] = mapped_column(String(17), nullable=False) name: Mapped[str | None] = mapped_column(String(128), nullable=True) phone: Mapped[str | None] = mapped_column(String(32), nullable=True) alias: Mapped[str | None] = mapped_column(String(64), nullable=True) # Mirror of the client's group (radusergroup) and billing status (customers): # set on add, synced on edit, and kept frozen on the row after soft-delete so # the deleted client's group/status survive their hard-deleted source rows. groupname: Mapped[str | None] = mapped_column(String(64), nullable=True, default=None) status: Mapped[str | None] = mapped_column(String(10), nullable=True, default=None) created_at: Mapped[datetime | None] = mapped_column( DateTime, nullable=True, server_default=func.current_timestamp() ) # Soft delete: NULL = live, a timestamp = deleted. Deleted rows are never # returned by the API and are kept only for restore/reference. deleted_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None) class RadadminDevice(Base): """Human alias for a NAS/AP box, keyed by its AP MAC. RADIUS never reads this.""" __tablename__ = "radadmin_devices" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) mac_address: Mapped[str] = mapped_column(String(17), nullable=False, unique=True) alias: Mapped[str | None] = mapped_column(String(64), nullable=True) created_at: Mapped[datetime | None] = mapped_column( DateTime, nullable=True, server_default=func.current_timestamp() ) class Nas(Base): __tablename__ = "nas" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) nasname: Mapped[str] = mapped_column(String(128), nullable=False) shortname: Mapped[str | None] = mapped_column(String(32), nullable=True) type: Mapped[str | None] = mapped_column(String(30), nullable=True, default="other") ports: Mapped[int | None] = mapped_column(Integer, nullable=True) secret: Mapped[str] = mapped_column(String(60), nullable=False, default="secret") server: Mapped[str | None] = mapped_column(String(64), nullable=True) community: Mapped[str | None] = mapped_column(String(50), nullable=True) description: Mapped[str | None] = mapped_column(String(200), nullable=True, default="RADIUS Client") class NasReload(Base): __tablename__ = "nasreload" nasipaddress: Mapped[str] = mapped_column(String(15), primary_key=True) reloadtime: Mapped[datetime] = mapped_column(DateTime, nullable=False) class RadCheck(Base): __tablename__ = "radcheck" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) username: Mapped[str] = mapped_column(String(64), nullable=False, default="") attribute: Mapped[str] = mapped_column(String(64), nullable=False, default="") op: Mapped[str] = mapped_column(String(2), nullable=False, default="==") value: Mapped[str] = mapped_column(String(253), nullable=False, default="") class RadReply(Base): __tablename__ = "radreply" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) username: Mapped[str] = mapped_column(String(64), nullable=False, default="") attribute: Mapped[str] = mapped_column(String(64), nullable=False, default="") op: Mapped[str] = mapped_column(String(2), nullable=False, default="=") value: Mapped[str] = mapped_column(String(253), nullable=False, default="") class RadGroupCheck(Base): __tablename__ = "radgroupcheck" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) groupname: Mapped[str] = mapped_column(String(64), nullable=False, default="") attribute: Mapped[str] = mapped_column(String(64), nullable=False, default="") op: Mapped[str] = mapped_column(String(2), nullable=False, default="==") value: Mapped[str] = mapped_column(String(253), nullable=False, default="") class RadGroupReply(Base): __tablename__ = "radgroupreply" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) groupname: Mapped[str] = mapped_column(String(64), nullable=False, default="") attribute: Mapped[str] = mapped_column(String(64), nullable=False, default="") op: Mapped[str] = mapped_column(String(2), nullable=False, default="=") value: Mapped[str] = mapped_column(String(253), nullable=False, default="") class RadUserGroup(Base): __tablename__ = "radusergroup" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) username: Mapped[str] = mapped_column(String(64), nullable=False, default="") groupname: Mapped[str] = mapped_column(String(64), nullable=False, default="") priority: Mapped[int] = mapped_column(Integer, nullable=False, default=1) class RadAcct(Base): __tablename__ = "radacct" radacctid: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) acctsessionid: Mapped[str] = mapped_column(String(64), nullable=False, default="") acctuniqueid: Mapped[str] = mapped_column(String(32), nullable=False, default="") username: Mapped[str] = mapped_column(String(64), nullable=False, default="") realm: Mapped[str | None] = mapped_column(String(64), nullable=True, default="") nasipaddress: Mapped[str] = mapped_column(String(15), nullable=False, default="") nasportid: Mapped[str | None] = mapped_column(String(32), nullable=True) nasporttype: Mapped[str | None] = mapped_column(String(32), nullable=True) acctstarttime: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) acctupdatetime: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) acctstoptime: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) acctinterval: Mapped[int | None] = mapped_column(Integer, nullable=True) acctsessiontime: Mapped[int | None] = mapped_column(Integer, nullable=True) acctauthentic: Mapped[str | None] = mapped_column(String(32), nullable=True) connectinfo_start: Mapped[str | None] = mapped_column(String(128), nullable=True) connectinfo_stop: Mapped[str | None] = mapped_column(String(128), nullable=True) acctinputoctets: Mapped[int | None] = mapped_column(BigInteger, nullable=True) acctoutputoctets: Mapped[int | None] = mapped_column(BigInteger, nullable=True) calledstationid: Mapped[str] = mapped_column(String(50), nullable=False, default="") callingstationid: Mapped[str] = mapped_column(String(50), nullable=False, default="") acctterminatecause: Mapped[str] = mapped_column(String(32), nullable=False, default="") servicetype: Mapped[str | None] = mapped_column(String(32), nullable=True) framedprotocol: Mapped[str | None] = mapped_column(String(32), nullable=True) framedipaddress: Mapped[str] = mapped_column(String(15), nullable=False, default="") framedipv6address: Mapped[str] = mapped_column(String(45), nullable=False, default="") framedipv6prefix: Mapped[str] = mapped_column(String(45), nullable=False, default="") framedinterfaceid: Mapped[str] = mapped_column(String(44), nullable=False, default="") delegatedipv6prefix: Mapped[str] = mapped_column(String(45), nullable=False, default="") class_: Mapped[str | None] = mapped_column("class", String(64), nullable=True) class RadPostAuth(Base): __tablename__ = "radpostauth" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) username: Mapped[str] = mapped_column(String(64), nullable=False, default="") pass_: Mapped[str] = mapped_column("pass", String(64), nullable=False, default="") reply: Mapped[str] = mapped_column(String(32), nullable=False, default="") authdate: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) class_: Mapped[str | None] = mapped_column("class", String(64), nullable=True)