Add member count API for public chat

This commit is contained in:
2026-04-03 19:20:21 +03:00
Unverified
parent 579b55477e
commit 3b45cfda5c
4 changed files with 60 additions and 3 deletions
@@ -0,0 +1,10 @@
---
description: Do not filter cache, API, or DB fields by matching fixed UI/placeholder English strings
alwaysApply: true
---
# No magic-string “sanitization” of user or message data
- **Never** strip, null out, or rewrite stored or displayed values by comparing them to hard-coded UI strings (e.g. `"Direct messages"`, `"Direct message"`, `"User 123"`, etc.). Those strings can be legitimate **usernames, display names, or message text**.
- **Prefer**: fix the source (dont persist placeholders; use `null`/absent fields; fix the writer). If legacy bad rows exist, use an explicit **schema/version/migration** or a **documented sentinel** agreed with the backend—not substring or equality checks on natural language.
- Applies especially to: local storage/cache layers, list previews, and any code that “cleans” strings before show or read.
+26 -2
View File
@@ -1,7 +1,7 @@
from datetime import datetime
from collections import defaultdict, deque
import time
from fastapi import APIRouter, Depends, HTTPException, status, Request
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status, Request
from sqlalchemy.orm import Session
from sqlalchemy import inspect, text
import uuid
@@ -25,6 +25,17 @@ _FAILED_ATTEMPT_THRESHOLD = 5
_failed_login_attempts: dict[str, deque[float]] = defaultdict(deque)
async def _broadcast_registered_user_count_task():
from ..db import SessionLocal
from .messaging import messagingManager
db = SessionLocal()
try:
await messagingManager.broadcast_registered_user_count(db)
finally:
db.close()
def _record_failed_login(identifier: str) -> bool:
now = time.time()
attempts = _failed_login_attempts[identifier]
@@ -173,7 +184,12 @@ def login(request: Request, login_request: LoginRequest, db: Session = Depends(g
@router.post("/register")
@rate_limit_per_ip("3/hour")
def register(request: Request, register_request: RegisterRequest, db: Session = Depends(get_db)):
def register(
request: Request,
register_request: RegisterRequest,
background_tasks: BackgroundTasks,
db: Session = Depends(get_db),
):
username = register_request.username.strip()
display_name = register_request.display_name.strip()
password = register_request.password.strip()
@@ -288,6 +304,8 @@ def register(request: Request, register_request: RegisterRequest, db: Session =
owner=is_owner,
)
background_tasks.add_task(_broadcast_registered_user_count_task)
return {
"status": "success",
"message": "Регистрация прошла успешно",
@@ -550,6 +568,12 @@ async def _delete_user_data(user: User, db: Session):
# Log error but don't fail the request
pass
try:
from .messaging import messagingManager
await messagingManager.broadcast_registered_user_count(db)
except Exception:
pass
@router.post("/delete")
async def delete_account(
+13
View File
@@ -1045,6 +1045,8 @@ class MessaggingSocketManager:
elif update_type == "statusUpdate":
# Deduplicate by user ID
sig_data = {"type": update_type, "userId": data.get("userId")}
elif update_type == "registeredUserCount":
sig_data = {"type": update_type, "count": data.get("count")}
else:
# For unknown types, use full data (less efficient but safe)
sig_data = {"type": update_type, "data": data}
@@ -1297,6 +1299,17 @@ class MessaggingSocketManager:
if websocket in self.user_by_ws:
await self._send_update(websocket, message_type, update_data, db)
async def broadcast_registered_user_count(self, db: Session):
"""Notify all clients of the current non-deleted user count (public chat member count)."""
try:
n = db.query(User).filter(User.deleted.is_(False)).count()
except Exception:
return
try:
await self.broadcast({"type": "registeredUserCount", "data": {"count": n}}, db)
except Exception:
pass
async def send_update_to_user(self, user_id: int, update_type: str, update_data: dict, db: Session | None = None):
"""Send an update to a specific user (batched)"""
for websocket in self.connections:
+10
View File
@@ -276,6 +276,16 @@ async def update_user_bio(
}
@router.get("/user/stats/registered-count")
def get_registered_user_count(
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Number of registered accounts (non-deleted users)."""
n = db.query(User).filter(User.deleted.is_(False)).count()
return {"count": n}
@router.get("/user/{username}")
async def get_user_by_username(
username: str,