feat: add online presence tracking with last_seen column and configurable timeout
Add `last_seen` column to users table with index, implement `set_last_seen` and `get_online_users` database functions, expose presence config env vars (`PRESENCE_TIMEOUT_SECONDS`, `PRESENCE_ONLINE_LIMIT`, `PRESENCE_ONLINE_MARGIN_SECONDS`), include `last_seen` in follow list responses, and update profile docs to mention online indicator.
This commit is contained in:
@@ -267,6 +267,106 @@ def test_activity_comment_card_renders_overlay_link(app_server):
|
||||
assert f'class="card-link" href="/posts/{post_slug}#comment-{comment_uid}"' in r.text
|
||||
|
||||
|
||||
def test_profile_json_exposes_online_presence(app_server):
|
||||
import time
|
||||
|
||||
name = f"pres{int(time.time() * 1000)}"
|
||||
session = requests.Session()
|
||||
session.post(
|
||||
f"{BASE_URL}/auth/signup",
|
||||
data={
|
||||
"username": name,
|
||||
"email": f"{name}@t.dev",
|
||||
"password": "secret123",
|
||||
"confirm_password": "secret123",
|
||||
},
|
||||
allow_redirects=True,
|
||||
)
|
||||
r = session.get(
|
||||
f"{BASE_URL}/profile/{name}", headers={"Accept": "application/json"}
|
||||
)
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert body["profile_online"] is True
|
||||
assert body["profile_user"]["last_seen"]
|
||||
|
||||
|
||||
def test_profile_json_offline_when_last_seen_stale(app_server):
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from devplacepy.config import PRESENCE_TIMEOUT_SECONDS
|
||||
from devplacepy.database import get_table, refresh_snapshot
|
||||
|
||||
owner = _seed_owner()
|
||||
username = get_table("users").find_one(uid=owner)["username"]
|
||||
stale = (
|
||||
datetime.now(timezone.utc)
|
||||
- timedelta(seconds=PRESENCE_TIMEOUT_SECONDS + 120)
|
||||
).isoformat()
|
||||
get_table("users").update({"uid": owner, "last_seen": stale}, ["uid"])
|
||||
refresh_snapshot()
|
||||
|
||||
r = requests.get(
|
||||
f"{BASE_URL}/profile/{username}", headers={"Accept": "application/json"}
|
||||
)
|
||||
assert r.status_code == 200
|
||||
body = r.json()
|
||||
assert body["profile_online"] is False
|
||||
assert body["profile_user"]["last_seen"] == stale
|
||||
|
||||
|
||||
def test_profile_hero_avatar_has_presence_dot(app_server):
|
||||
import time
|
||||
from devplacepy.database import get_table, refresh_snapshot
|
||||
|
||||
name = f"phero{int(time.time() * 1000)}"
|
||||
s = requests.Session()
|
||||
s.post(
|
||||
f"{BASE_URL}/auth/signup",
|
||||
data={
|
||||
"username": name,
|
||||
"email": f"{name}@t.dev",
|
||||
"password": "secret123",
|
||||
"confirm_password": "secret123",
|
||||
},
|
||||
allow_redirects=True,
|
||||
)
|
||||
refresh_snapshot()
|
||||
uid = get_table("users").find_one(username=name)["uid"]
|
||||
html = s.get(f"{BASE_URL}/profile/{name}").text
|
||||
assert "profile-avatar-wrap" in html
|
||||
assert "presence-dot" in html
|
||||
assert f'data-presence-uid="{uid}"' in html
|
||||
|
||||
|
||||
def test_followers_list_avatar_has_presence_dot(app_server):
|
||||
import time
|
||||
from devplacepy.database import get_table, refresh_snapshot
|
||||
|
||||
stamp = int(time.time() * 1000)
|
||||
target = f"folt{stamp}"
|
||||
follower = f"folf{stamp}"
|
||||
st = requests.Session()
|
||||
sf = requests.Session()
|
||||
for sess, name in ((st, target), (sf, follower)):
|
||||
sess.post(
|
||||
f"{BASE_URL}/auth/signup",
|
||||
data={
|
||||
"username": name,
|
||||
"email": f"{name}@t.dev",
|
||||
"password": "secret123",
|
||||
"confirm_password": "secret123",
|
||||
},
|
||||
allow_redirects=True,
|
||||
)
|
||||
sf.post(f"{BASE_URL}/follow/{target}", allow_redirects=True)
|
||||
refresh_snapshot()
|
||||
follower_uid = get_table("users").find_one(username=follower)["uid"]
|
||||
html = st.get(f"{BASE_URL}/profile/{target}?tab=followers").text
|
||||
assert "follow-user" in html
|
||||
assert f'data-presence-uid="{follower_uid}"' in html
|
||||
assert "data-presence-last-seen" in html
|
||||
|
||||
|
||||
def test_viewing_profile_marks_notification_read(app_server):
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
|
||||
Reference in New Issue
Block a user