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:
2026-07-04 22:08:20 +00:00
parent 7002b23eeb
commit 0f872336b1
41 changed files with 935 additions and 94 deletions
+3 -1
View File
@@ -3,7 +3,7 @@
from .core import dataset, logging, Path, or_, defaultdict, datetime, timedelta, timezone, TTLCache, DATABASE_URL, DEFAULT_CORRECTION_PROMPT, DEFAULT_MODIFIER_PROMPT, INTERNAL_GATEWAY_URL, ensure_data_dirs, logger, db
from .core import refresh_snapshot, _local_cache_versions, _cache_version_cache, _cache_state_ready, _ensure_cache_state, get_cache_version, bump_cache_version, sync_local_cache, _index, _drop_index, _uid_index, get_table, _in_clause, _now_iso
from .settings import _settings_cache, get_setting, get_int_setting, set_setting, clear_settings_cache, internal_gateway_key
from .users import get_users_by_uids, _admins_cache, invalidate_admins_cache, get_admin_uids, set_user_timezone, get_primary_admin_uid, search_users_by_username
from .users import get_users_by_uids, _admins_cache, invalidate_admins_cache, get_admin_uids, set_user_timezone, set_last_seen, get_online_users, get_primary_admin_uid, search_users_by_username
from .relations import _relations_cache, get_user_relations, get_blocked_uids, get_muted_uids, get_silenced_uids, invalidate_user_relations
from .pagination import PAGE_SIZE, paginate, interleave_by_author, paginate_diverse, get_user_post_count, build_pagination
from .soft_delete import SOFT_DELETE_TABLES, ensure_soft_delete_columns, soft_delete, soft_delete_in, restore, purge, list_deleted, count_deleted, restore_event, purge_event
@@ -66,6 +66,8 @@ __all__ = [
"invalidate_admins_cache",
"get_admin_uids",
"set_user_timezone",
"set_last_seen",
"get_online_users",
"get_primary_admin_uid",
"search_users_by_username",
"_relations_cache",
+1
View File
@@ -45,6 +45,7 @@ def get_follow_list(
"uid": person["uid"],
"username": person["username"],
"bio": (person.get("bio") or "")[:140],
"last_seen": person.get("last_seen"),
"followed_at": row.get("created_at"),
}
)
+3
View File
@@ -36,6 +36,7 @@ def init_db():
_index(db, "users", "idx_users_email", ["email"])
_index(db, "users", "idx_users_api_key", ["api_key"])
_index(db, "users", "idx_users_role", ["role", "created_at"])
_index(db, "users", "idx_users_last_seen", ["last_seen"])
_index(db, "posts", "idx_posts_user_uid", ["user_uid"])
_index(db, "posts", "idx_posts_created_at", ["created_at"])
_index(db, "posts", "idx_posts_topic", ["topic"])
@@ -1117,6 +1118,8 @@ def backfill_api_keys() -> int:
users.create_column_by_example("timezone", "")
if not users.has_column("avatar_seed"):
users.create_column_by_example("avatar_seed", "")
if not users.has_column("last_seen"):
users.create_column_by_example("last_seen", "")
with db:
db.query(
"UPDATE users SET ai_modifier_enabled = 1 WHERE ai_modifier_enabled IS NULL"
+24
View File
@@ -47,6 +47,30 @@ def set_user_timezone(user_uid: str, tz_name: str) -> None:
users.update({"uid": user_uid, "timezone": tz_name}, ["uid"])
def set_last_seen(user_uid: str, iso: str) -> None:
if "users" not in db.tables or not user_uid or not iso:
return
users = db["users"]
if not users.has_column("last_seen"):
users.create_column_by_example("last_seen", "")
users.update({"uid": user_uid, "last_seen": iso}, ["uid"])
def get_online_users(cutoff_iso: str, limit: int = 30) -> list:
if "users" not in db.tables:
return []
users = db["users"]
if "last_seen" not in users.columns:
return []
return list(
users.find(
last_seen={">=": cutoff_iso},
order_by=["username"],
_limit=limit,
)
)
def get_primary_admin_uid():
sync_local_cache("admins", _admins_cache)
cached = _admins_cache.get("primary")