mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-19 07:33:18 +02:00
ghcr.io pull baseline (~10k/day rising → ~8-12k active installs) puts
sponsor conversion at 0.08% — roughly an order of magnitude under
industry-benchmark for OSS with visible CTA. The Settings banner from
0d4b9d4e gives passive every-visit visibility on one page; this adds
opt-out-able active visibility at moments where the user has just
earned something with Bambuddy.
Five trigger families with a 14-day cross-family cooldown: prints
(100/500/1000/2500/5000), cost (100/500/1000 tracked filament +
energy), archives (50/250/1000), anniversary (1 year), version-update
(re-armable on each major bump). New sponsor_toast_state table with
nullable user_id so auth-disabled installs get the same trigger logic
through one code path (NULL-keyed install-default row).
40 lines
1.7 KiB
Python
40 lines
1.7 KiB
Python
"""Per-user (or install-default) state for the sponsor-prompt toast.
|
|
|
|
A single row stores which sponsor-toast milestones have already fired for a
|
|
given user, when the most recent toast was shown (for the 14-day cooldown),
|
|
and the app version last seen so we can fire the "version-update" trigger
|
|
exactly once per major bump.
|
|
|
|
``user_id`` is nullable: in auth-disabled installs (no user concept), the
|
|
service stores everything against a single NULL-keyed row.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import DateTime, ForeignKey, Integer, String, Text, UniqueConstraint, func
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from backend.app.core.database import Base
|
|
|
|
|
|
class SponsorToastState(Base):
|
|
__tablename__ = "sponsor_toast_state"
|
|
__table_args__ = (UniqueConstraint("user_id", name="uq_sponsor_toast_state_user_id"),)
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
user_id: Mapped[int | None] = mapped_column(
|
|
Integer,
|
|
ForeignKey("users.id", ondelete="CASCADE"),
|
|
nullable=True,
|
|
index=True,
|
|
)
|
|
last_shown_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
|
# JSON-serialised list[str] of milestone keys already fired (e.g. ["prints-100", "cost-100"]).
|
|
# Stored as Text for SQLite/Postgres uniformity; the service serialises with json.dumps.
|
|
milestones_seen: Mapped[str] = mapped_column(Text, nullable=False, default="[]")
|
|
last_seen_version: Mapped[str | None] = mapped_column(String(50), nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
|
|
updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now())
|