Skip to content

Commit a9123ae

Browse files
committed
feat(user): add user retrieval by usernames and IDs, enforce admin quota during bulk creation
1 parent 2221939 commit a9123ae

3 files changed

Lines changed: 178 additions & 55 deletions

File tree

app/db/crud/user.py

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,33 @@ async def get_users_count_by_admin(db: AsyncSession, admin_id: int | None) -> in
738738
return (await db.execute(stmt)).scalar_one() or 0
739739

740740

741+
async def lock_admin_quota_row(db: AsyncSession, admin_id: int) -> None:
742+
"""Lock an admin row before quota-sensitive user creation."""
743+
if db.bind.dialect.name == "sqlite":
744+
await db.execute(update(Admin).where(Admin.id == admin_id).values(id=Admin.id))
745+
return
746+
747+
await db.execute(select(Admin.id).where(Admin.id == admin_id).with_for_update())
748+
749+
750+
async def get_users_by_usernames(db: AsyncSession, usernames: Sequence[str]) -> list[User]:
751+
if not usernames:
752+
return []
753+
754+
result = await db.execute(_build_user_select_stmt().where(User.username.in_(usernames)))
755+
users_by_username = {user.username: user for user in result.unique().scalars().all()}
756+
return [users_by_username[username] for username in usernames if username in users_by_username]
757+
758+
759+
async def get_users_by_ids(db: AsyncSession, user_ids: Sequence[int]) -> list[User]:
760+
if not user_ids:
761+
return []
762+
763+
result = await db.execute(_build_user_select_stmt().where(User.id.in_(user_ids)))
764+
users_by_id = {user.id: user for user in result.unique().scalars().all()}
765+
return [users_by_id[user_id] for user_id in user_ids if user_id in users_by_id]
766+
767+
741768
async def get_users_count(db: AsyncSession, status: UserStatus = None, admin_id: int = None) -> int:
742769
"""
743770
Gets the total count of users with optional filters.
@@ -797,7 +824,9 @@ async def get_users_count_by_status(
797824
return all_statuses
798825

799826

800-
async def create_user(db: AsyncSession, new_user: UserCreate, groups: list[Group], admin: Admin) -> User:
827+
async def create_user(
828+
db: AsyncSession, new_user: UserCreate, groups: list[Group], admin: Admin, *, commit: bool = True
829+
) -> User:
801830
"""
802831
Creates a new user.
803832
@@ -829,13 +858,14 @@ async def create_user(db: AsyncSession, new_user: UserCreate, groups: list[Group
829858
if new_user.next_plan:
830859
db_user.next_plan = NextPlan(user_id=db_user.id, **new_user.next_plan.model_dump())
831860
db.add(db_user.next_plan)
832-
await db.commit()
833-
await refresh_and_load_user(db, db_user)
861+
if commit:
862+
await db.commit()
863+
await refresh_and_load_user(db, db_user)
834864
return db_user
835865

836866

837867
async def create_users_bulk(
838-
db: AsyncSession, new_users: list[UserCreate], groups: list[Group], admin: Admin
868+
db: AsyncSession, new_users: list[UserCreate], groups: list[Group], admin: Admin, *, commit: bool = True
839869
) -> list[User]:
840870
"""
841871
Creates multiple users in a single commit for better performance.
@@ -868,10 +898,10 @@ async def create_users_bulk(
868898
db.add_all(next_plans)
869899
await db.flush()
870900

871-
await db.commit()
872-
873-
for user in db_users:
874-
await refresh_and_load_user(db, user)
901+
if commit:
902+
await db.commit()
903+
for user in db_users:
904+
await refresh_and_load_user(db, user)
875905

876906
return db_users
877907

@@ -925,6 +955,7 @@ async def modify_user(
925955
modify: UserModify,
926956
*,
927957
groups: list[Group] | None = None,
958+
commit: bool = True,
928959
) -> User:
929960
"""
930961
Modify a user's information.
@@ -1019,8 +1050,9 @@ async def modify_user(
10191050
if remove_expiration_reminder:
10201051
await delete_user_passed_notification_reminders(db, id, ReminderType.expiration_date, days_left)
10211052

1022-
await db.commit()
1023-
await refresh_and_load_user(db, db_user)
1053+
if commit:
1054+
await db.commit()
1055+
await refresh_and_load_user(db, db_user)
10241056
return db_user
10251057

10261058

@@ -1047,7 +1079,9 @@ async def clear_user_node_usages(db: AsyncSession, user_id: int, *, before: date
10471079
await db.execute(stmt)
10481080

10491081

1050-
async def reset_user_data_usage(db: AsyncSession, db_user: User, *, clean_chart_data: bool = False) -> User:
1082+
async def reset_user_data_usage(
1083+
db: AsyncSession, db_user: User, *, clean_chart_data: bool = False, commit: bool = True
1084+
) -> User:
10511085
"""
10521086
Resets the data usage of a user and logs the reset.
10531087
@@ -1065,13 +1099,14 @@ async def reset_user_data_usage(db: AsyncSession, db_user: User, *, clean_chart_
10651099
if db_user.status not in [UserStatus.expired, UserStatus.disabled]:
10661100
db_user.status = UserStatus.active
10671101

1068-
await db.commit()
1069-
await refresh_and_load_user(db, db_user)
1102+
if commit:
1103+
await db.commit()
1104+
await refresh_and_load_user(db, db_user)
10701105
return db_user
10711106

10721107

10731108
async def bulk_reset_user_data_usage(
1074-
db: AsyncSession, users: list[User], *, clean_chart_data: bool = False
1109+
db: AsyncSession, users: list[User], *, clean_chart_data: bool = False, commit: bool = True
10751110
) -> list[User]:
10761111
"""
10771112
Resets the data usage for a list of users and logs the reset.
@@ -1089,9 +1124,10 @@ async def bulk_reset_user_data_usage(
10891124
await clear_user_node_usages(db, db_user.id)
10901125
if db_user.status not in [UserStatus.expired, UserStatus.disabled]:
10911126
db_user.status = UserStatus.active
1092-
await db.commit()
1093-
for user in users:
1094-
await refresh_and_load_user(db, user)
1127+
if commit:
1128+
await db.commit()
1129+
for user in users:
1130+
await refresh_and_load_user(db, user)
10951131
return users
10961132

10971133

app/jobs/review_admins.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,13 @@ async def _send_usage_limit_warning_notifications(db):
5757
usage_percentage = int((admin.used_traffic * 100) / admin.data_limit)
5858
admin_model = AdminDetails.model_validate(admin)
5959
await notification.admin_usage_limit_reached(admin_model, usage_percentage, threshold)
60-
reminder_rows.append({
61-
"admin_id": admin.id,
62-
"type": ReminderType.data_usage,
63-
"threshold": threshold,
64-
})
60+
reminder_rows.append(
61+
{
62+
"admin_id": admin.id,
63+
"type": ReminderType.data_usage,
64+
"threshold": threshold,
65+
}
66+
)
6567

6668
if reminder_rows:
6769
await bulk_create_admin_notification_reminders(db, reminder_rows)

0 commit comments

Comments
 (0)