Spaces:
Sleeping
Sleeping
| """ | |
| GraphoLab Backend — AuditLog ORM model. | |
| Append-only table: no UPDATE or DELETE is ever issued by application code. | |
| Every sensitive operation is recorded here for forensic chain-of-custody. | |
| """ | |
| from __future__ import annotations | |
| import enum | |
| from datetime import datetime | |
| from sqlalchemy import DateTime, Enum, ForeignKey, Integer, String, Text, func | |
| from sqlalchemy.orm import Mapped, mapped_column | |
| from backend.database import Base | |
| class AuditAction(str, enum.Enum): | |
| login = "login" | |
| project_create = "project_create" | |
| project_delete = "project_delete" | |
| document_upload = "document_upload" | |
| document_delete = "document_delete" | |
| analysis_run = "analysis_run" | |
| analysis_clear = "analysis_clear" | |
| pdf_download = "pdf_download" | |
| password_reset_generate = "password_reset_generate" | |
| password_reset_confirm = "password_reset_confirm" | |
| class AuditLog(Base): | |
| """Immutable audit trail. Never UPDATE or DELETE rows from this table.""" | |
| __tablename__ = "audit_log" | |
| id: Mapped[int] = mapped_column(primary_key=True, index=True) | |
| # UTC timestamp — server-generated, not client-supplied | |
| timestamp: Mapped[datetime] = mapped_column( | |
| DateTime(timezone=True), server_default=func.now(), index=True | |
| ) | |
| # Who performed the action (snapshot: user may be deleted later) | |
| user_id: Mapped[int | None] = mapped_column( | |
| Integer, | |
| ForeignKey("users.id", ondelete="SET NULL"), | |
| nullable=True, | |
| index=True, | |
| ) | |
| user_email: Mapped[str] = mapped_column(String(256), nullable=False) | |
| # What happened | |
| action: Mapped[AuditAction] = mapped_column(Enum(AuditAction), nullable=False, index=True) | |
| # On which resource (e.g. resource_type="project", resource_id=42) | |
| resource_type: Mapped[str | None] = mapped_column(String(64), nullable=True) | |
| resource_id: Mapped[int | None] = mapped_column(Integer, nullable=True) | |
| # Optional extra context (e.g. analysis type, filename) | |
| detail: Mapped[str | None] = mapped_column(Text, nullable=True) | |
| # Network info | |
| ip_address: Mapped[str | None] = mapped_column(String(64), nullable=True) | |