from sqlalchemy import ForeignKey, Integer, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column from scribe.models import Base from scribe.models.base import TimestampMixin, iso class RepoBinding(Base, TimestampMixin): """Maps a git repository to the Scribe project it represents. The SessionStart hook sends the working repo's normalized remote (`repo_key`, e.g. ``git.fabledsword.com/bvandeusen/fabledscribe``) and the server resolves it to a project — so the operator can work across many repos/projects without pinning a project id in plugin config. The key is a *stable identifier* (the git remote), not a dir-name heuristic. """ __tablename__ = "repo_bindings" __table_args__ = ( UniqueConstraint("user_id", "repo_key", name="uq_repo_bindings_user_repo"), ) id: Mapped[int] = mapped_column(primary_key=True) user_id: Mapped[int] = mapped_column( Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False ) project_id: Mapped[int] = mapped_column( Integer, ForeignKey("projects.id", ondelete="CASCADE"), nullable=False ) repo_key: Mapped[str] = mapped_column(Text, nullable=False) # The branch the coverage refresh reads for this binding (#2873); NULL = # the forge's default branch. Chosen at bind time so a dev-first project # can have its ledger follow dev instead of waiting for the merge. ref: Mapped[str | None] = mapped_column(Text, nullable=True) def to_dict(self) -> dict: return { "id": self.id, "user_id": self.user_id, "project_id": self.project_id, "repo_key": self.repo_key, "ref": self.ref, "created_at": iso(self.created_at), "updated_at": iso(self.updated_at), }