From 8a95a765228c1042fbe773e42006e585d7832c02 Mon Sep 17 00:00:00 2001 From: Manmohan Sharma Date: Thu, 16 Apr 2026 13:50:08 -0700 Subject: [PATCH] fix: add missing models/ dirs to auth and chat-api services Root .gitignore had `models/` which matched both ML weights AND SQLAlchemy model dirs. Changed to `/models/` (root only). Added auth/src/models/ (User) and chat-api/src/models/ (Conversation, Message). Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 2 +- services/auth/src/models/__init__.py | 3 + services/auth/src/models/user.py | 61 ++++++++++++++++++ services/chat-api/src/models/__init__.py | 4 ++ services/chat-api/src/models/conversation.py | 60 ++++++++++++++++++ services/chat-api/src/models/message.py | 66 ++++++++++++++++++++ 6 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 services/auth/src/models/__init__.py create mode 100644 services/auth/src/models/user.py create mode 100644 services/chat-api/src/models/__init__.py create mode 100644 services/chat-api/src/models/conversation.py create mode 100644 services/chat-api/src/models/message.py diff --git a/.gitignore b/.gitignore index 74878379..48fc66b7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ __pycache__/ dev-ignore/ report.md eval_bundle/ -models/ +/models/ .terraform/ *.tfstate *.tfstate.* diff --git a/services/auth/src/models/__init__.py b/services/auth/src/models/__init__.py new file mode 100644 index 00000000..35b01f31 --- /dev/null +++ b/services/auth/src/models/__init__.py @@ -0,0 +1,3 @@ +from .user import User + +__all__ = ["User"] diff --git a/services/auth/src/models/user.py b/services/auth/src/models/user.py new file mode 100644 index 00000000..281e0714 --- /dev/null +++ b/services/auth/src/models/user.py @@ -0,0 +1,61 @@ +"""SQLAlchemy 2.0 async model for the `users` table.""" +from __future__ import annotations + +import uuid +from datetime import datetime, timezone + +import sqlalchemy as sa +from sqlalchemy.orm import Mapped, mapped_column + +from ..database import Base + + +def _utcnow() -> datetime: + return datetime.now(timezone.utc) + + +class User(Base): + __tablename__ = "users" + __table_args__ = ( + sa.UniqueConstraint("provider", "provider_id", name="uq_users_provider_identity"), + ) + + id: Mapped[uuid.UUID] = mapped_column( + sa.Uuid(as_uuid=True), + primary_key=True, + default=uuid.uuid4, + ) + email: Mapped[str] = mapped_column(sa.String(255), unique=True, nullable=False) + name: Mapped[str | None] = mapped_column(sa.String(255), nullable=True) + avatar_url: Mapped[str | None] = mapped_column(sa.Text(), nullable=True) + provider: Mapped[str] = mapped_column(sa.String(50), nullable=False) + provider_id: Mapped[str] = mapped_column(sa.String(255), nullable=False) + created_at: Mapped[datetime] = mapped_column( + sa.DateTime(timezone=True), + default=_utcnow, + nullable=False, + ) + updated_at: Mapped[datetime] = mapped_column( + sa.DateTime(timezone=True), + default=_utcnow, + onupdate=_utcnow, + nullable=False, + ) + last_login_at: Mapped[datetime] = mapped_column( + sa.DateTime(timezone=True), + default=_utcnow, + nullable=False, + ) + + def to_dict(self) -> dict: + return { + "id": str(self.id), + "email": self.email, + "name": self.name, + "avatar_url": self.avatar_url, + "provider": self.provider, + "provider_id": self.provider_id, + "created_at": self.created_at.isoformat() if self.created_at else None, + "updated_at": self.updated_at.isoformat() if self.updated_at else None, + "last_login_at": self.last_login_at.isoformat() if self.last_login_at else None, + } diff --git a/services/chat-api/src/models/__init__.py b/services/chat-api/src/models/__init__.py new file mode 100644 index 00000000..97ae934d --- /dev/null +++ b/services/chat-api/src/models/__init__.py @@ -0,0 +1,4 @@ +from .conversation import Conversation +from .message import Message + +__all__ = ["Conversation", "Message"] diff --git a/services/chat-api/src/models/conversation.py b/services/chat-api/src/models/conversation.py new file mode 100644 index 00000000..9589f320 --- /dev/null +++ b/services/chat-api/src/models/conversation.py @@ -0,0 +1,60 @@ +"""SQLAlchemy 2.0 async model for the `conversations` table.""" +from __future__ import annotations + +import uuid +from datetime import datetime, timezone + +import sqlalchemy as sa +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from ..database import Base + + +def _utcnow() -> datetime: + return datetime.now(timezone.utc) + + +class Conversation(Base): + __tablename__ = "conversations" + + id: Mapped[uuid.UUID] = mapped_column( + sa.Uuid(as_uuid=True), + primary_key=True, + default=uuid.uuid4, + ) + user_id: Mapped[uuid.UUID] = mapped_column( + sa.Uuid(as_uuid=True), + sa.ForeignKey("users.id", ondelete="CASCADE"), + nullable=False, + index=True, + ) + title: Mapped[str] = mapped_column(sa.String(500), nullable=False, default="New conversation") + model_tag: Mapped[str] = mapped_column(sa.String(100), nullable=False, default="default") + created_at: Mapped[datetime] = mapped_column( + sa.DateTime(timezone=True), + default=_utcnow, + nullable=False, + ) + updated_at: Mapped[datetime] = mapped_column( + sa.DateTime(timezone=True), + default=_utcnow, + onupdate=_utcnow, + nullable=False, + ) + + messages: Mapped[list["Message"]] = relationship( # noqa: F821 + "Message", + back_populates="conversation", + cascade="all, delete-orphan", + order_by="Message.created_at", + ) + + def to_dict(self) -> dict: + return { + "id": str(self.id), + "user_id": str(self.user_id), + "title": self.title, + "model_tag": self.model_tag, + "created_at": self.created_at.isoformat() if self.created_at else None, + "updated_at": self.updated_at.isoformat() if self.updated_at else None, + } diff --git a/services/chat-api/src/models/message.py b/services/chat-api/src/models/message.py new file mode 100644 index 00000000..35c249e6 --- /dev/null +++ b/services/chat-api/src/models/message.py @@ -0,0 +1,66 @@ +"""SQLAlchemy 2.0 async model for the `messages` table.""" +from __future__ import annotations + +import uuid +from datetime import datetime, timezone + +import sqlalchemy as sa +from sqlalchemy.orm import Mapped, mapped_column, relationship + +from ..database import Base + + +def _utcnow() -> datetime: + return datetime.now(timezone.utc) + + +MESSAGE_ROLES = ("user", "assistant", "system") + + +class Message(Base): + __tablename__ = "messages" + __table_args__ = ( + sa.CheckConstraint( + "role IN ('user', 'assistant', 'system')", + name="ck_messages_role", + ), + sa.Index("ix_messages_conversation_created", "conversation_id", "created_at"), + ) + + id: Mapped[uuid.UUID] = mapped_column( + sa.Uuid(as_uuid=True), + primary_key=True, + default=uuid.uuid4, + ) + conversation_id: Mapped[uuid.UUID] = mapped_column( + sa.Uuid(as_uuid=True), + sa.ForeignKey("conversations.id", ondelete="CASCADE"), + nullable=False, + ) + role: Mapped[str] = mapped_column(sa.String(20), nullable=False) + content: Mapped[str] = mapped_column(sa.Text(), nullable=False) + token_count: Mapped[int | None] = mapped_column(sa.Integer(), nullable=True) + model_tag: Mapped[str | None] = mapped_column(sa.String(100), nullable=True) + inference_time_ms: Mapped[int | None] = mapped_column(sa.Integer(), nullable=True) + created_at: Mapped[datetime] = mapped_column( + sa.DateTime(timezone=True), + default=_utcnow, + nullable=False, + ) + + conversation: Mapped["Conversation"] = relationship( # noqa: F821 + "Conversation", + back_populates="messages", + ) + + def to_dict(self) -> dict: + return { + "id": str(self.id), + "conversation_id": str(self.conversation_id), + "role": self.role, + "content": self.content, + "token_count": self.token_count or 0, + "model_tag": self.model_tag or "", + "inference_time_ms": self.inference_time_ms or 0, + "created_at": self.created_at.isoformat() if self.created_at else None, + }