import uuid from datetime import datetime from sqlalchemy import JSON, Index, Boolean, DateTime, ForeignKey, String, func, Text, text from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship class Base(DeclarativeBase): pass class User(Base): __tablename__ = "users" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, ) email: Mapped[str] = mapped_column( String(320), unique=True, nullable=False, index=True, ) password_hash: Mapped[str] = mapped_column( String(512), nullable=False, ) email_verified: Mapped[bool] = mapped_column( Boolean, default=False, nullable=False, ) is_active: Mapped[bool] = mapped_column( Boolean, default=True, nullable=False, ) role: Mapped[str] = mapped_column( String(32), default="user", nullable=False, index=True, ) firebase_uid: Mapped[str | None] = mapped_column( String(128), unique=True, nullable=True, index=True ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.utcnow, nullable=False, ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False, ) sessions: Mapped[list["Session"]] = relationship( back_populates="user", cascade="all, delete-orphan", ) class Session(Base): __tablename__ = "sessions" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, ) user_id: Mapped[uuid.UUID] = mapped_column( ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True, ) refresh_token_hash: Mapped[str] = mapped_column( String(128), unique=True, nullable=False, index=True, ) device_name: Mapped[str | None] = mapped_column( String(255), nullable=True, ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.utcnow, nullable=False, ) expires_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, ) last_used_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) revoked_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) user: Mapped["User"] = relationship( back_populates="sessions", ) class EmailVerificationToken(Base): __tablename__ = "email_verification_tokens" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True) token_hash: Mapped[str] = mapped_column(String(128), unique=True, nullable=False, index=True) expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False) used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.utcnow, nullable=False) class PasswordResetToken(Base): __tablename__ = "password_reset_tokens" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True) token_hash: Mapped[str] = mapped_column(String(128), unique=True, nullable=False, index=True) expires_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False) used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.utcnow, nullable=False) class AuditEvent(Base): __tablename__ = "audit_events" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) event_type: Mapped[str] = mapped_column(String(96), nullable=False, index=True) actor_user_id: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True) target_user_id: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True) ip_address: Mapped[str | None] = mapped_column(String(64), nullable=True) user_agent: Mapped[str | None] = mapped_column(String(512), nullable=True) metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.utcnow, nullable=False, index=True) class Vehicle(Base): __tablename__ = "vehicles" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True) nickname: Mapped[str] = mapped_column(String(120), nullable=False) make: Mapped[str] = mapped_column(String(120), nullable=False) model: Mapped[str] = mapped_column(String(120), nullable=False) year: Mapped[int | None] = mapped_column(nullable=True) vin: Mapped[str | None] = mapped_column(String(17), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.utcnow, nullable=False) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False) class Ride(Base): __tablename__ = "rides" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True) vehicle_id: Mapped[uuid.UUID | None] = mapped_column(ForeignKey("vehicles.id", ondelete="SET NULL"), nullable=True, index=True) started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False) ended_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) distance_miles: Mapped[float | None] = mapped_column(nullable=True) top_speed_mph: Mapped[float | None] = mapped_column(nullable=True) avg_speed_mph: Mapped[float | None] = mapped_column(nullable=True) status: Mapped[str] = mapped_column(String(32), nullable=False, default="completed") created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.utcnow, nullable=False) class TelemetryPoint(Base): __tablename__ = "telemetry_points" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) ride_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("rides.id", ondelete="CASCADE"), nullable=False, index=True) recorded_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, index=True) latitude: Mapped[float | None] = mapped_column(nullable=True) longitude: Mapped[float | None] = mapped_column(nullable=True) speed_mph: Mapped[float | None] = mapped_column(nullable=True) lean_angle_deg: Mapped[float | None] = mapped_column(nullable=True) g_force: Mapped[float | None] = mapped_column(nullable=True) accel_g: Mapped[float | None] = mapped_column(nullable=True) brake_g: Mapped[float | None] = mapped_column(nullable=True) class Subscription(Base): __tablename__ = "subscriptions" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), nullable=False, unique=True, index=True) plan: Mapped[str] = mapped_column(String(32), nullable=False, default="free") status: Mapped[str] = mapped_column(String(32), nullable=False, default="active") current_period_start: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) current_period_end: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.utcnow, nullable=False) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False) class Entitlement(Base): __tablename__ = "entitlements" id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) user_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True) key: Mapped[str] = mapped_column(String(96), nullable=False) value: Mapped[dict | None] = mapped_column(JSON, nullable=True) expires_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.utcnow, nullable=False) class AuthRateLimit(Base): __tablename__ = "auth_rate_limits" key: Mapped[str] = mapped_column(String(255), primary_key=True) window_started: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False) attempts: Mapped[int] = mapped_column(nullable=False, default=0) # ============================================================ # Operations (ops schema) Models # ============================================================ class Operator(Base): __tablename__ = "operators" __table_args__ = {"schema": "ops"} id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) identity: Mapped[str] = mapped_column( String(320), unique=True, nullable=False, index=True ) role: Mapped[str] = mapped_column( String(32), nullable=False, default="operator", index=True ) status: Mapped[str] = mapped_column( String(32), nullable=False, default="active" ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.utcnow, nullable=False ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False ) sessions: Mapped[list["OpsSession"]] = relationship( back_populates="operator", cascade="all, delete-orphan" ) class OpsSession(Base): __tablename__ = "sessions" __table_args__ = {"schema": "ops"} id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) operator_id: Mapped[uuid.UUID] = mapped_column( ForeignKey("ops.operators.id", ondelete="CASCADE"), nullable=False, index=True ) session_token_hash: Mapped[str] = mapped_column( String(128), unique=True, nullable=False, index=True ) expires_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False ) last_activity_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.utcnow, nullable=False ) revoked_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) operator: Mapped["Operator"] = relationship(back_populates="sessions") class OpsAuditEvent(Base): __tablename__ = "audit_events" __table_args__ = {"schema": "ops"} id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) timestamp: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.utcnow, nullable=False, index=True ) action: Mapped[str] = mapped_column(String(96), nullable=False, index=True) actor_identity: Mapped[str] = mapped_column(String(320), nullable=False) actor_role: Mapped[str | None] = mapped_column(String(32), nullable=True) target_resource: Mapped[str | None] = mapped_column(String(96), nullable=True) reason_code: Mapped[str | None] = mapped_column(String(64), nullable=True) case_reference: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True) result: Mapped[str] = mapped_column(String(32), nullable=False, default="success") metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) class OpsIncident(Base): __tablename__ = "incidents" __table_args__ = {"schema": "ops"} id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) case_reference: Mapped[str] = mapped_column( String(32), unique=True, nullable=False, index=True ) title: Mapped[str] = mapped_column(String(255), nullable=False) summary: Mapped[str] = mapped_column(String, nullable=False) severity: Mapped[str] = mapped_column( String(32), nullable=False, default="medium", index=True ) status: Mapped[str] = mapped_column( String(32), nullable=False, default="open", index=True ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.utcnow, nullable=False, index=True ) created_by_operator_id: Mapped[uuid.UUID] = mapped_column( ForeignKey("ops.operators.id", ondelete="RESTRICT"), nullable=False ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False ) closed_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True ) closed_by_operator_id: Mapped[uuid.UUID | None] = mapped_column( ForeignKey("ops.operators.id", ondelete="RESTRICT"), nullable=True ) resolution: Mapped[str | None] = mapped_column(String, nullable=True) assigned_to: Mapped[str | None] = mapped_column(String(320), nullable=True, default=None) notes: Mapped[list["OpsIncidentNote"]] = relationship( back_populates="incident", cascade="all, delete-orphan", order_by="OpsIncidentNote.created_at.desc()" ) class OpsIncidentNote(Base): __tablename__ = "incident_notes" __table_args__ = {"schema": "ops"} id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) incident_id: Mapped[uuid.UUID] = mapped_column( ForeignKey("ops.incidents.id", ondelete="CASCADE"), nullable=False, index=True ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.utcnow, nullable=False, index=True ) created_by_operator_id: Mapped[uuid.UUID] = mapped_column( ForeignKey("ops.operators.id", ondelete="RESTRICT"), nullable=False ) body: Mapped[str] = mapped_column(String, nullable=False) incident: Mapped["OpsIncident"] = relationship(back_populates="notes") class OpsAction(Base): __tablename__ = "ops_actions" __table_args__ = ( Index( "ix_ops_actions_idempotency_key", "requested_by_id", "action_type", "idempotency_key", unique=True, postgresql_where=text("idempotency_key IS NOT NULL"), ), {"schema": "ops"}, ) id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) action_type: Mapped[str] = mapped_column(String(255), nullable=False) requested_by_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("ops.operators.id", ondelete="RESTRICT"), nullable=False) request_timestamp: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False) start_timestamp: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) completion_timestamp: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) state: Mapped[str] = mapped_column(String(50), server_default="queued", nullable=False) status_message: Mapped[str | None] = mapped_column(Text, nullable=True) deployed_revision: Mapped[str | None] = mapped_column(String(255), nullable=True) target_revision: Mapped[str | None] = mapped_column(String(255), nullable=True) preflight_result: Mapped[str | None] = mapped_column(Text, nullable=True) health_result: Mapped[str | None] = mapped_column(Text, nullable=True) rollback_status: Mapped[str | None] = mapped_column(Text, nullable=True) failure_code: Mapped[str | None] = mapped_column(String(255), nullable=True) idempotency_key: Mapped[str | None] = mapped_column(String(255), nullable=True) class OpsSupportTicket(Base): __tablename__ = "support_tickets" __table_args__ = {"schema": "ops"} id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) case_reference: Mapped[str] = mapped_column(String(32), unique=True, nullable=False, index=True) ticket_type: Mapped[str] = mapped_column(String(64), nullable=False) reporter_email: Mapped[str] = mapped_column(String(320), nullable=False) subject: Mapped[str] = mapped_column(String(255), nullable=False) description: Mapped[str] = mapped_column(Text, nullable=False) severity: Mapped[str] = mapped_column(String(32), nullable=False, default="medium", index=True) status: Mapped[str] = mapped_column(String(32), nullable=False, default="open", index=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.utcnow, nullable=False, index=True) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False) resolved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) diagnostic_bundle: Mapped[dict | None] = mapped_column(JSON, nullable=True) notes: Mapped[list["OpsSupportTicketNote"]] = relationship( back_populates="ticket", cascade="all, delete-orphan", order_by="OpsSupportTicketNote.created_at.desc()" ) class OpsSupportTicketNote(Base): __tablename__ = "support_ticket_notes" __table_args__ = {"schema": "ops"} id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) ticket_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("ops.support_tickets.id", ondelete="CASCADE"), nullable=False, index=True) author_identity: Mapped[str] = mapped_column(String(320), nullable=False) body: Mapped[str] = mapped_column(Text, nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.utcnow, nullable=False, index=True) ticket: Mapped["OpsSupportTicket"] = relationship(back_populates="notes") class OpsCmsDocument(Base): __tablename__ = "cms_documents" __table_args__ = {"schema": "ops"} slug: Mapped[str] = mapped_column(String(96), primary_key=True) title: Mapped[str] = mapped_column(String(255), nullable=False) description: Mapped[str] = mapped_column(Text, nullable=False) published_json: Mapped[dict] = mapped_column(JSON, nullable=False) draft_json: Mapped[dict | None] = mapped_column(JSON, nullable=True) updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False) updated_by: Mapped[str] = mapped_column(String(320), nullable=False, default="system") revisions: Mapped[list["OpsCmsRevision"]] = relationship( back_populates="document", cascade="all, delete-orphan", order_by="OpsCmsRevision.created_at.desc()" ) class OpsCmsRevision(Base): __tablename__ = "cms_document_revisions" __table_args__ = {"schema": "ops"} revision_id: Mapped[str] = mapped_column(String(96), primary_key=True) document_slug: Mapped[str] = mapped_column(ForeignKey("ops.cms_documents.slug", ondelete="CASCADE"), nullable=False, index=True) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), default=datetime.utcnow, nullable=False, index=True) created_by: Mapped[str] = mapped_column(String(320), nullable=False) status: Mapped[str] = mapped_column(String(32), nullable=False) change_summary: Mapped[str] = mapped_column(Text, nullable=False) content_json: Mapped[dict] = mapped_column(JSON, nullable=False) document: Mapped["OpsCmsDocument"] = relationship(back_populates="revisions") # ============================================================ # Road Alerts & External Provider Usage Models # ============================================================ class ProviderMonthlyUsage(Base): __tablename__ = "provider_monthly_usage" __table_args__ = ( Index("ix_provider_period", "provider_id", "period_month", unique=True), ) id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) provider_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True) period_month: Mapped[str] = mapped_column(String(7), nullable=False, index=True) # Format: YYYY-MM requests_reserved: Mapped[int] = mapped_column(nullable=False, default=0) requests_executed: Mapped[int] = mapped_column(nullable=False, default=0) monthly_limit: Mapped[int] = mapped_column(nullable=False, default=500) estimated_cost_usd: Mapped[float] = mapped_column(nullable=False, default=0.0) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.utcnow, nullable=False ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False )