blackboxai-api / app /models.py
Copilot
fix: resolve all UPGRADED_DEEP_REPO_AUDIT critical issues
cbcf0fb
Raw
History Blame Contribute Delete
6.26 kB
"""
SQLAlchemy ORM models matching the PRD database design.
Fixes applied from UPGRADED_DEEP_REPO_AUDIT.md:
- Added UniqueConstraint on user_sessions(user_id, session_id) [DB Audit]
- Added ondelete="CASCADE" to all FK columns referencing users/sessions [Issue 5.9]
- Added cascade="all, delete-orphan" to User relationships [Issue 5.9]
- Added missing DB indexes: users.created_at, sessions.status,
notifications.created_at, audit_logs.created_at [DB Audit]
"""
from datetime import datetime
from sqlalchemy import (
Column,
Integer,
String,
Text,
DateTime,
Float,
Boolean,
Enum,
ForeignKey,
JSON,
Index,
UniqueConstraint,
)
from sqlalchemy.orm import relationship
from app.database import Base
class User(Base):
__tablename__ = "users"
__table_args__ = (
Index("idx_users_created_at", "created_at"), # Issue: ORDER BY created_at
)
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
name = Column(String(100), nullable=False)
email = Column(String(255), unique=True, index=True, nullable=False)
password_hash = Column(String(255), nullable=False)
role = Column(Enum("admin", "user", name="user_role"), default="user", nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
# Cascade deletes: removing a User removes all their related rows (Issue 5.9)
user_sessions = relationship(
"UserSession", back_populates="user", cascade="all, delete-orphan"
)
messages = relationship(
"Message", back_populates="user", cascade="all, delete-orphan"
)
audit_logs = relationship(
"AuditLog", back_populates="actor", cascade="all, delete-orphan"
)
notifications_sent = relationship("Notification", back_populates="creator")
class Session(Base):
__tablename__ = "sessions"
__table_args__ = (
Index("idx_sessions_status", "status"), # Issue: frequent filter by status
)
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
session_name = Column(String(200), nullable=False)
start_time = Column(DateTime, nullable=True)
end_time = Column(DateTime, nullable=True)
status = Column(
Enum("pending", "active", "paused", "completed", name="session_status"),
default="pending",
nullable=False,
)
user_sessions = relationship(
"UserSession", back_populates="session", cascade="all, delete-orphan"
)
messages = relationship(
"Message", back_populates="session", cascade="all, delete-orphan"
)
class UserSession(Base):
__tablename__ = "user_sessions"
__table_args__ = (
Index("idx_us_user_session", "user_id", "session_id"),
# Unique constraint prevents duplicate rows from TOCTOU race (DB Audit)
UniqueConstraint("user_id", "session_id", name="uq_user_session"),
)
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
# ondelete="CASCADE" ensures FK rows are removed when parent is deleted (Issue 5.9)
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
session_id = Column(Integer, ForeignKey("sessions.id", ondelete="CASCADE"), nullable=False)
joined_at = Column(DateTime, default=datetime.utcnow)
completed_at = Column(DateTime, nullable=True)
score = Column(Float, default=0.0)
achieved_target = Column(Boolean, default=False)
prompt_count = Column(Integer, default=0)
user = relationship("User", back_populates="user_sessions")
session = relationship("Session", back_populates="user_sessions")
class Message(Base):
__tablename__ = "messages"
__table_args__ = (
Index("idx_msg_user_session", "user_id", "session_id"),
Index("idx_msg_prompt_timestamp", "prompt_timestamp"),
)
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
session_id = Column(Integer, ForeignKey("sessions.id", ondelete="CASCADE"), nullable=False)
prompt_text = Column(Text, nullable=False)
response_text = Column(Text, nullable=True)
prompt_timestamp = Column(DateTime, default=datetime.utcnow)
response_timestamp = Column(DateTime, nullable=True)
latency_ms = Column(Integer, nullable=True)
success = Column(Boolean, default=True)
user = relationship("User", back_populates="messages")
session = relationship("Session", back_populates="messages")
class AdminSettings(Base):
__tablename__ = "admin_settings"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
max_messages_per_user = Column(Integer, default=50)
max_messages_per_minute = Column(Integer, default=5)
challenge_duration = Column(Integer, default=3600) # seconds
llm_provider = Column(String(50), default="ollama")
llm_model = Column(String(100), default="llama3")
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class Notification(Base):
__tablename__ = "notifications"
__table_args__ = (
Index("idx_notif_created_at", "created_at"), # Issue: ORDER BY created_at
)
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
message = Column(Text, nullable=False)
priority = Column(
Enum("low", "normal", "high", "urgent", name="notification_priority"),
default="normal",
)
created_by = Column(Integer, ForeignKey("users.id"), nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
creator = relationship("User", back_populates="notifications_sent")
class AuditLog(Base):
__tablename__ = "audit_logs"
__table_args__ = (
Index("idx_audit_created_at", "created_at"), # Issue: ORDER BY created_at
)
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
actor_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
action_type = Column(String(100), nullable=False)
action_data = Column(JSON, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
actor = relationship("User", back_populates="audit_logs")