import asyncio from datetime import datetime, timedelta from sqlalchemy import and_ from sqlalchemy.future import select from typing import Dict, List, Optional, Type, Union from server.constants.private import MESSAGE_TYPE_TABLE_MAP, AccountsDeleteInclude, AccountsGetInclude, MessagesType from server.db.models.base import Base, db_session from server.db.models.core import Account from server.db.models.users import Device, Favorites, Settings from server.utils.private import generate_filter_or_in def get_account_filters( app_slug: str, id_list: Optional[List[int]] = None, user_id_list: Optional[List[str]] = None, is_active: Optional[bool] = None, days_count: Optional[int] = None, ) -> list: """Generate account filters. Args: app_slug: App code. id_list: Account ID list. user_id_list: User ID list. is_active: Active or not. days_count: Last updated before days count from now. Returns: List of filters. """ filter_list = [Account.app_slug == app_slug] id_filter = generate_filter_or_in(Account, id=id_list, user_id=user_id_list) if id_filter is not None: filter_list.append(id_filter) if is_active is not None: filter_list.append(Account.is_active.is_(is_active)) if days_count is not None: filter_list.append(Account.updated_at <= (datetime.utcnow() - timedelta(days=days_count))) return filter_list async def search_accounts( app_slug: str, id_list: Optional[List[int]] = None, user_id_list: Optional[List[str]] = None, is_active: Optional[bool] = None, days_count: Optional[int] = None, limit: Optional[int] = None, include: AccountsGetInclude = AccountsGetInclude.ID, ) -> List[Union[str, int]]: """Search accounts. Args: app_slug: App code. id_list: Account ID list. user_id_list: User ID list. is_active: Only active/inactive. days_count: Last updated before days count from now. limit: Max result items count. include: Result item type. Returns: Account ID or User ID list. """ filter_list = get_account_filters(app_slug, id_list, user_id_list, is_active, days_count) result_field = Account.id if include == AccountsGetInclude.ID else Account.user_id query = select(result_field).where(and_(*filter_list)).order_by(result_field) if limit: query = query.limit(limit) async with db_session() as session: return (await session.execute(query)).scalars().all() async def make_accounts_inactive( id_list: Optional[List[int]], include_list: List[AccountsDeleteInclude] ) -> Dict[str, List[int]]: """Make accounts inactive. Args: id_list: Account ID list. include_list: Make other objects inactive as well. Returns: Table name to updated rows ID list. """ table_list = [Account] task_list = [ Account.update( {"is_active": False}, filters=[Account.id.in_(id_list)], return_source=Account.id, return_many=True, return_single_column=True, ), ] if AccountsDeleteInclude.DEVICES in include_list: table_list.append(Device) task_list.append( Device.update( {"status": False}, filters=[Device.account_id.in_(id_list), Device.status.is_(True)], return_source=Device.id, return_many=True, return_single_column=True, ) ) result = await asyncio.gather(*task_list) return {table.__tablename__: count for table, count in zip(table_list, result)} async def _delete_by_accounts(table: Type[Base], account_id_list: List[int]) -> List[int]: """Delete records in tables that depend (FK) on accounts. Args: table: Table to delete records in. account_id_list: Account ID list. Returns: Removed rows ID list. """ return await table.delete( filters=[table.account_id.in_(account_id_list)], return_source=table.id, return_single_column=True ) async def delete_accounts(id_list: List[int], include_list: List[MessagesType]) -> Dict[str, int]: """Delete accounts. Args: id_list: Account ID list. include_list: Remove included messages. Returns: Table name to removed ID list map. """ if not id_list: return {} table_list = [Device, Favorites, Settings] if include_list: table_list.extend([MESSAGE_TYPE_TABLE_MAP[i] for i in include_list]) result = await asyncio.gather(*[_delete_by_accounts(table, id_list) for table in table_list]) result = dict(zip([i.__tablename__ for i in table_list], result)) result[Account.__tablename__] = await Account.delete( filters=[Account.id.in_(id_list)], force_delete=True, return_source=Account.id, return_single_column=True ) return result