From 3fafd6760076de398f94da78f10d268573bb49b4 Mon Sep 17 00:00:00 2001 From: emi Date: Sun, 26 Jul 2026 18:27:41 +0000 Subject: [PATCH] bookmarks: account-synced saved locations API --- backend/accounts/models.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/backend/accounts/models.py b/backend/accounts/models.py index bc3dc54..56ed510 100644 --- a/backend/accounts/models.py +++ b/backend/accounts/models.py @@ -195,3 +195,31 @@ class PendingDigest(Base): # duplicating, which is what makes the form idempotent. UniqueConstraint("email", name="uq_pending_digest_email"), ) + + +class Bookmark(Base): + """A saved location, owned by a user — the "bookmarked locations" feature. + + Keyed like ``Subscription`` on ``grid.snap(lat, lon)["id"]``: one bookmark per + (user, cell), so re-bookmarking the same cell is an upsert of the label rather + than a duplicate row (see ``create_bookmark`` / ``import_bookmarks`` in + api_accounts.py, which enforce this at the application layer too). + """ + __tablename__ = "bookmark" + + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + user_id: Mapped[uuid.UUID] = mapped_column( + GUID, ForeignKey("user.id", ondelete="CASCADE"), nullable=False + ) + # grid.snap(lat, lon)["id"] — the stable per-location key used across the app. + cell_id: Mapped[str] = mapped_column(String(40), nullable=False) + label: Mapped[str] = mapped_column(String(80), nullable=False) + lat: Mapped[float] = mapped_column(Float, nullable=False) + lon: Mapped[float] = mapped_column(Float, nullable=False) + created_at: Mapped[float] = mapped_column(Float, nullable=False, default=time.time) + + __table_args__ = ( + # One bookmark per location per user — re-bookmarking upserts the label. + UniqueConstraint("user_id", "cell_id", name="uq_bookmark_user_cell"), + Index("idx_bookmark_user", "user_id"), + )