omyfish / apps /omyfish_web /admin.py
fenghebonjour's picture
Upload folder using huggingface_hub
ac92d5c verified
Raw
History Blame Contribute Delete
5.17 kB
"""Admin dashboard tab + sidebar subscription card for the Streamlit app.
Talks to the same repositories as the FastAPI backend (in-process)."""
from datetime import datetime, timedelta, timezone
import streamlit as st
def _subs_repo():
from apps.omyfish_api.db.engine import ensure_db
from apps.omyfish_api.repositories.subscription_repository import SubscriptionRepository
ensure_db()
return SubscriptionRepository()
# ── Sidebar subscription card (any logged-in user) ────────────────────────────
def subscription_sidebar(user: dict) -> None:
from apps.omyfish_api import billing
from apps.omyfish_api.billing import BillingNotConfigured
repo = _subs_repo()
sub = repo.get_for_user(user["id"]) or repo.start_trial(user["id"])
status = sub["status"]
if status == "trialing":
days_left = max(0, (sub["trial_end"] - datetime.now(timezone.utc)).days)
st.info(f"Free trial — {days_left} day{'s' if days_left != 1 else ''} left")
elif status == "active":
st.success(f"Subscribed — {billing.PLANS[sub['plan']]['label']}"
if sub.get("plan") in billing.PLANS else "Subscribed")
else:
st.warning("Trial ended — subscribe to keep full access")
if status != "active":
col1, col2 = st.columns(2)
for col, plan in ((col1, "monthly"), (col2, "yearly")):
with col:
if st.button(billing.PLANS[plan]["label"], key=f"sub_{plan}",
use_container_width=True):
try:
url = billing.create_checkout_session(
user["id"], user["email"], plan)
st.session_state["checkout_url"] = url
except BillingNotConfigured:
st.session_state["checkout_url"] = None
if "checkout_url" in st.session_state:
if st.session_state["checkout_url"]:
st.link_button("Continue to secure checkout →",
st.session_state.pop("checkout_url"),
use_container_width=True)
else:
st.session_state.pop("checkout_url")
st.caption("Payments are not configured on this deployment.")
# ── Admin tab ─────────────────────────────────────────────────────────────────
def render_admin() -> None:
from apps.omyfish_api.db.engine import get_db
from sqlalchemy import text
repo = _subs_repo()
with get_db() as db:
user_count = db.execute(text("SELECT COUNT(*) FROM users")).scalar()
obs_count = db.execute(text("SELECT COUNT(*) FROM observations")).scalar()
stats = repo.stats()
c1, c2, c3, c4, c5 = st.columns(5)
c1.metric("Users", user_count)
c2.metric("Observations", obs_count)
c3.metric("Active subs", stats["subscriptions"].get("active", 0))
c4.metric("On trial", stats["subscriptions"].get("trialing", 0))
c5.metric("MRR (CAD)", f"${stats['mrr_cad']:.2f}")
st.divider()
st.subheader("Subscriptions")
subs = repo.list_all()
if subs:
st.dataframe(
[{
"Email": s["email"],
"Status": s["status"],
"Plan": s.get("plan") or "—",
"Trial ends": s["trial_end"].strftime("%Y-%m-%d")
if s.get("trial_end") else "—",
"Period ends": s["current_period_end"].strftime("%Y-%m-%d")
if s.get("current_period_end") else "—",
} for s in subs],
use_container_width=True, hide_index=True,
)
else:
st.caption("No subscriptions yet.")
st.divider()
st.subheader("Manage")
if not subs:
return
emails = {s["email"]: s["user_id"] for s in subs}
target_email = st.selectbox("User", sorted(emails))
target = emails[target_email]
a1, a2, a3 = st.columns(3)
with a1:
if st.button("Grant 1 year (comp)", use_container_width=True):
repo.set_active(target, plan="yearly",
period_end=datetime.now(timezone.utc) + timedelta(days=365))
st.rerun()
with a2:
if st.button("Extend trial 7 days", use_container_width=True):
sub = repo.get_for_user(target)
base = max(sub["trial_end"] or datetime.now(timezone.utc),
datetime.now(timezone.utc))
from apps.omyfish_api.db.engine import get_db as _gdb
with _gdb() as db:
db.execute(
text("UPDATE subscriptions SET status='trialing', trial_end=:te "
"WHERE user_id=:uid"),
{"te": (base + timedelta(days=7)).isoformat(), "uid": target},
)
st.rerun()
with a3:
if st.button("Revoke", use_container_width=True, type="secondary"):
repo.set_status(target, "canceled")
st.rerun()