Skip to content

Commit baf2874

Browse files
committed
feat(admin): add role eager loading and admin details helper for usage warnings
- Add selectinload for Admin.role in get_usage_percentage_reached_admins and get_active_to_limited_admins queries to prevent N+1 queries - Add load_role parameter to load_admin_attrs function for flexible role relationship loading - Create _admin_usage_warning_details helper function to properly construct AdminDetails with role and permission overrides for usage limit notifications - Update bulk_create_from_template, modify_with_template, and create_user_from_template handlers to accept and pass admin parameter to get_user_templates - Import Admin model and AdminRoleData, RoleLimits types in review_admins module for proper type handling
1 parent 9179f52 commit baf2874

4 files changed

Lines changed: 55 additions & 17 deletions

File tree

app/db/crud/admin.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from sqlalchemy import and_, case, delete, func, insert, not_, select, update
44
from sqlalchemy.ext.asyncio import AsyncSession
5+
from sqlalchemy.orm import selectinload
56

67
from app.db.crud.general import (
78
_build_trunc_expression,
@@ -31,12 +32,19 @@
3132
logger = get_logger("admin-crud")
3233

3334

34-
async def load_admin_attrs(admin: Admin, load_users: bool = True, load_usage_logs: bool = True):
35+
async def load_admin_attrs(
36+
admin: Admin,
37+
load_users: bool = True,
38+
load_usage_logs: bool = True,
39+
load_role: bool = True,
40+
):
3541
try:
3642
if load_users:
3743
await admin.awaitable_attrs.users
3844
if load_usage_logs:
3945
await admin.awaitable_attrs.usage_logs
46+
if load_role:
47+
await admin.awaitable_attrs.role
4048
except AttributeError:
4149
pass
4250

@@ -452,12 +460,16 @@ async def get_usage_percentage_reached_admins(
452460
.exists()
453461
)
454462

455-
stmt = select(Admin).where(
456-
Admin.status == AdminStatus.active,
457-
Admin.data_limit.isnot(None),
458-
Admin.data_limit > 0,
459-
(Admin.used_traffic * 100) >= (Admin.data_limit * percentage),
460-
not_(existing_reminder_subq),
463+
stmt = (
464+
select(Admin)
465+
.options(selectinload(Admin.role))
466+
.where(
467+
Admin.status == AdminStatus.active,
468+
Admin.data_limit.isnot(None),
469+
Admin.data_limit > 0,
470+
(Admin.used_traffic * 100) >= (Admin.data_limit * percentage),
471+
not_(existing_reminder_subq),
472+
)
461473
)
462474

463475
if admin_ids is not None:
@@ -491,7 +503,7 @@ async def delete_admin_notification_reminders(
491503

492504
async def get_active_to_limited_admins(db: AsyncSession) -> list[Admin]:
493505
"""Return ALL active admins that have exceeded their data_limit (for status flip)."""
494-
stmt = select(Admin).where(
506+
stmt = select(Admin).options(selectinload(Admin.role)).where(
495507
Admin.status == AdminStatus.active,
496508
Admin.data_limit.isnot(None),
497509
Admin.data_limit > 0,

app/jobs/review_admins.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@
2020
update_admin_status,
2121
)
2222
from app.db.crud.user import get_users
23-
from app.db.models import AdminStatus, ReminderType, UserStatus
24-
from app.models.admin import AdminDetails
23+
from app.db.models import Admin, AdminStatus, ReminderType, UserStatus
24+
from app.models.admin import AdminDetails, AdminRoleData
25+
from app.models.admin_role import RoleLimits
2526
from app.models.user import UserListQuery
2627
from app.models.validators import ListValidator
2728
from app.node.sync import remove_users as sync_remove_users
@@ -32,6 +33,29 @@
3233
logger = get_logger("review-admins")
3334

3435

36+
def _admin_usage_warning_details(admin: Admin) -> AdminDetails:
37+
return AdminDetails(
38+
id=admin.id,
39+
username=admin.username,
40+
used_traffic=int(admin.used_traffic or 0),
41+
data_limit=admin.data_limit,
42+
status=admin.status,
43+
telegram_id=admin.telegram_id,
44+
discord_webhook=admin.discord_webhook,
45+
discord_id=admin.discord_id,
46+
sub_domain=admin.sub_domain,
47+
profile_title=admin.profile_title,
48+
support_url=admin.support_url,
49+
notification_enable=admin.notification_enable,
50+
sub_template=admin.sub_template,
51+
note=admin.note,
52+
role=AdminRoleData.model_validate(admin.role) if admin.role else None,
53+
permission_overrides=RoleLimits.model_validate(admin.permission_overrides)
54+
if admin.permission_overrides
55+
else None,
56+
)
57+
58+
3559
async def _send_usage_limit_warning_notifications(db):
3660
notify_settings = await notification_enable()
3761
admin_notify = notify_settings.admin
@@ -55,7 +79,7 @@ async def _send_usage_limit_warning_notifications(db):
5579
if not admin.data_limit or admin.data_limit <= 0:
5680
continue
5781
usage_percentage = int((admin.used_traffic * 100) / admin.data_limit)
58-
admin_model = AdminDetails.model_validate(admin)
82+
admin_model = _admin_usage_warning_details(admin)
5983
await notification.admin_usage_limit_reached(admin_model, usage_percentage, threshold)
6084
reminder_rows.append(
6185
{

app/telegram/handlers/admin/bulk_actions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ async def bulk_actions(event: CallbackQuery, admin: AdminDetails):
6969
@router.callback_query(
7070
HasPermission("users", "create"), BulkActionPanel.Callback.filter(BulkAction.create_from_template == F.action)
7171
)
72-
async def bulk_create_from_template(event: CallbackQuery, db: AsyncSession, state: FSMContext):
73-
templates = await user_templates.get_user_templates(db, UserTemplateListQuery())
72+
async def bulk_create_from_template(event: CallbackQuery, db: AsyncSession, state: FSMContext, admin: AdminDetails):
73+
templates = await user_templates.get_user_templates(db, UserTemplateListQuery(), admin)
7474
if not templates:
7575
return await event.answer(Texts.there_is_no_template)
7676

app/telegram/handlers/admin/user.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -511,8 +511,10 @@ async def activate_next_plan(
511511
@router.callback_query(
512512
HasPermission("users", "update"), UserPanel.Callback.filter(UserPanelAction.modify_with_template == F.action)
513513
)
514-
async def modify_with_template(event: CallbackQuery, db: AsyncSession, callback_data: UserPanel.Callback):
515-
templates = await user_templates.get_user_templates(db, UserTemplateListQuery())
514+
async def modify_with_template(
515+
event: CallbackQuery, db: AsyncSession, admin: AdminDetails, callback_data: UserPanel.Callback
516+
):
517+
templates = await user_templates.get_user_templates(db, UserTemplateListQuery(), admin)
516518
if not templates:
517519
return await event.answer(Texts.there_is_no_template)
518520

@@ -542,8 +544,8 @@ async def modify_with_template_done(
542544
HasPermission("users", "create"),
543545
AdminPanel.Callback.filter(AdminPanelAction.create_user_from_template == F.action),
544546
)
545-
async def create_user_from_template(event: CallbackQuery, db: AsyncSession):
546-
templates = await user_templates.get_user_templates(db, UserTemplateListQuery())
547+
async def create_user_from_template(event: CallbackQuery, db: AsyncSession, admin: AdminDetails):
548+
templates = await user_templates.get_user_templates(db, UserTemplateListQuery(), admin)
547549
if not templates:
548550
return await event.answer(Texts.there_is_no_template)
549551
await event.message.edit_text(Texts.choose_a_template, reply_markup=ChooseTemplate(templates).as_markup())

0 commit comments

Comments
 (0)