feat: add referral network graph visualization admin API

4 endpoints for referral network analysis: full graph with batched
aggregation queries, user detail with recursive CTE branch counting,
campaign detail with conversion metrics, and search with LIKE escaping.

All endpoints rate-limited, scoped queries to prevent full-table scans,
depth-limited recursive CTE, fail_closed on expensive graph endpoint.
This commit is contained in:
Fringg
2026-03-19 07:55:51 +03:00
parent 69bb399b63
commit c08c903e8f
3 changed files with 1044 additions and 2 deletions
+2
View File
@@ -20,6 +20,7 @@ from .admin_pinned_messages import router as admin_pinned_messages_router
from .admin_policies import router as admin_policies_router
from .admin_promo_offers import router as admin_promo_offers_router
from .admin_promocodes import promo_groups_router as admin_promo_groups_router, router as admin_promocodes_router
from .admin_referral_network import router as admin_referral_network_router
from .admin_remnawave import router as admin_remnawave_router
from .admin_roles import router as admin_roles_router
from .admin_sales_stats import router as admin_sales_stats_router
@@ -99,6 +100,7 @@ router.include_router(admin_wheel_router)
router.include_router(admin_tariffs_router)
router.include_router(admin_servers_router)
router.include_router(admin_stats_router)
router.include_router(admin_referral_network_router)
router.include_router(admin_sales_stats_router)
router.include_router(admin_ban_system_router)
router.include_router(admin_broadcasts_router)
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -400,9 +400,11 @@ return c
return fail_closed
@staticmethod
async def is_rate_limited(user_id: int, action: str, limit: int, window: int) -> bool:
async def is_rate_limited(
user_id: int, action: str, limit: int, window: int, *, fail_closed: bool = False,
) -> bool:
key = cache_key('rate_limit', user_id, action)
return await RateLimitCache._atomic_rate_check(key, limit, window)
return await RateLimitCache._atomic_rate_check(key, limit, window, fail_closed=fail_closed)
@staticmethod
async def reset_rate_limit(user_id: int, action: str) -> bool: