@@ -49,7 +49,7 @@ async def pre_register_user(
4949 product_name : ProductName ,
5050) -> UserAccountGet :
5151
52- found = await search_users_as_admin (
52+ found = await search_users_accounts (
5353 app , email_glob = profile .email , product_name = product_name , include_products = False
5454 )
5555 if found :
@@ -83,7 +83,7 @@ async def pre_register_user(
8383 ** details ,
8484 )
8585
86- found = await search_users_as_admin (
86+ found = await search_users_accounts (
8787 app , email_glob = profile .email , product_name = product_name , include_products = False
8888 )
8989
@@ -134,69 +134,6 @@ async def get_user_id_from_gid(app: web.Application, primary_gid: GroupID) -> Us
134134 return await _users_repository .get_user_id_from_pgid (app , primary_gid = primary_gid )
135135
136136
137- async def search_users_as_admin (
138- app : web .Application ,
139- * ,
140- email_glob : str ,
141- product_name : ProductName | None = None ,
142- include_products : bool = False ,
143- ) -> list [UserAccountGet ]:
144- """
145- WARNING: this information is reserved for admin users. Note that the returned model include UserForAdminGet
146-
147- NOTE: Functions in the service layer typically validate the caller's access rights
148- using parameters like product_name and user_id. However, this function skips
149- such checks as it is designed for scenarios (e.g., background tasks) where
150- no caller or context is available.
151- """
152-
153- def _glob_to_sql_like (glob_pattern : str ) -> str :
154- # Escape SQL LIKE special characters in the glob pattern
155- sql_like_pattern = glob_pattern .replace ("%" , r"\%" ).replace ("_" , r"\_" )
156- # Convert glob wildcards to SQL LIKE wildcards
157- return sql_like_pattern .replace ("*" , "%" ).replace ("?" , "_" )
158-
159- rows = await _users_repository .search_merged_pre_and_registered_users (
160- get_asyncpg_engine (app ),
161- email_like = _glob_to_sql_like (email_glob ),
162- product_name = product_name ,
163- )
164-
165- async def _list_products_or_none (user_id ):
166- if user_id is not None and include_products :
167- products = await _users_repository .get_user_products (
168- get_asyncpg_engine (app ), user_id = user_id
169- )
170- return [_ .product_name for _ in products ]
171- return None
172-
173- return [
174- UserAccountGet (
175- first_name = r .first_name or r .pre_first_name ,
176- last_name = r .last_name or r .pre_last_name ,
177- email = r .email or r .pre_email ,
178- institution = r .institution ,
179- phone = r .phone or r .pre_phone ,
180- address = r .address ,
181- city = r .city ,
182- state = r .state ,
183- postal_code = r .postal_code ,
184- country = r .country ,
185- extras = r .extras or {},
186- invited_by = r .invited_by ,
187- pre_registration_id = r .id ,
188- account_request_status = r .account_request_status ,
189- account_request_reviewed_by = r .account_request_reviewed_by ,
190- account_request_reviewed_at = r .account_request_reviewed_at ,
191- products = await _list_products_or_none (r .user_id ),
192- # NOTE: old users will not have extra details
193- registered = r .user_id is not None if r .pre_email else r .status is not None ,
194- status = r .status ,
195- )
196- for r in rows
197- ]
198-
199-
200137async def get_users_in_group (app : web .Application , * , gid : GroupID ) -> set [UserID ]:
201138 return await _users_repository .get_users_ids_in_group (
202139 get_asyncpg_engine (app ), group_id = gid
@@ -214,64 +151,6 @@ async def is_user_in_product(
214151 )
215152
216153
217- async def list_all_users_as_admin (
218- app : web .Application ,
219- * ,
220- product_name : ProductName ,
221- filter_any_account_request_status : list [AccountRequestStatus ] | None = None ,
222- pagination_limit : int = 50 ,
223- pagination_offset : int = 0 ,
224- ) -> tuple [list [dict [str , Any ]], int ]:
225- """
226- Get a paginated list of users for admin view with filtering options.
227-
228- Args:
229- app: The web application instance
230- filter_approved: If set, filters users by their approval status
231- pagination_limit: Maximum number of users to return
232- pagination_offset: Number of users to skip for pagination
233-
234- Returns:
235- A tuple containing (list of user dictionaries, total count of users)
236- """
237- engine = get_asyncpg_engine (app )
238-
239- # Get user data with pagination
240- users_data , total_count = (
241- await _users_repository .list_merged_pre_and_registered_users (
242- engine ,
243- product_name = product_name ,
244- filter_any_account_request_status = filter_any_account_request_status ,
245- pagination_limit = pagination_limit ,
246- pagination_offset = pagination_offset ,
247- )
248- )
249-
250- # For each user, append additional information if needed
251- result = []
252- for user in users_data :
253- # Add any additional processing needed for admin view
254- user_dict = dict (user )
255-
256- # Add products information if needed
257- user_id = user .get ("user_id" )
258- if user_id :
259- products = await _users_repository .get_user_products (
260- engine , user_id = user_id
261- )
262- user_dict ["products" ] = [p .product_name for p in products ]
263-
264- user_dict ["registered" ] = (
265- user_id is not None
266- if user .get ("pre_email" )
267- else user .get ("status" ) is not None
268- )
269-
270- result .append (user_dict )
271-
272- return result , total_count
273-
274-
275154#
276155# GET USER PROPERTIES
277156#
@@ -456,6 +335,132 @@ async def update_my_profile(
456335 )
457336
458337
338+ #
339+ # USER ACCOUNTS
340+ #
341+
342+
343+ async def list_user_accounts (
344+ app : web .Application ,
345+ * ,
346+ product_name : ProductName ,
347+ filter_any_account_request_status : list [AccountRequestStatus ] | None = None ,
348+ pagination_limit : int = 50 ,
349+ pagination_offset : int = 0 ,
350+ ) -> tuple [list [dict [str , Any ]], int ]:
351+ """
352+ Get a paginated list of users for admin view with filtering options.
353+
354+ Args:
355+ app: The web application instance
356+ filter_approved: If set, filters users by their approval status
357+ pagination_limit: Maximum number of users to return
358+ pagination_offset: Number of users to skip for pagination
359+
360+ Returns:
361+ A tuple containing (list of user dictionaries, total count of users)
362+ """
363+ engine = get_asyncpg_engine (app )
364+
365+ # Get user data with pagination
366+ users_data , total_count = (
367+ await _users_repository .list_merged_pre_and_registered_users (
368+ engine ,
369+ product_name = product_name ,
370+ filter_any_account_request_status = filter_any_account_request_status ,
371+ pagination_limit = pagination_limit ,
372+ pagination_offset = pagination_offset ,
373+ )
374+ )
375+
376+ # For each user, append additional information if needed
377+ result = []
378+ for user in users_data :
379+ # Add any additional processing needed for admin view
380+ user_dict = dict (user )
381+
382+ # Add products information if needed
383+ user_id = user .get ("user_id" )
384+ if user_id :
385+ products = await _users_repository .get_user_products (
386+ engine , user_id = user_id
387+ )
388+ user_dict ["products" ] = [p .product_name for p in products ]
389+
390+ user_dict ["registered" ] = (
391+ user_id is not None
392+ if user .get ("pre_email" )
393+ else user .get ("status" ) is not None
394+ )
395+
396+ result .append (user_dict )
397+
398+ return result , total_count
399+
400+
401+ async def search_users_accounts (
402+ app : web .Application ,
403+ * ,
404+ email_glob : str ,
405+ product_name : ProductName | None = None ,
406+ include_products : bool = False ,
407+ ) -> list [UserAccountGet ]:
408+ """
409+ WARNING: this information is reserved for admin users. Note that the returned model include UserForAdminGet
410+
411+ NOTE: Functions in the service layer typically validate the caller's access rights
412+ using parameters like product_name and user_id. However, this function skips
413+ such checks as it is designed for scenarios (e.g., background tasks) where
414+ no caller or context is available.
415+ """
416+
417+ def _glob_to_sql_like (glob_pattern : str ) -> str :
418+ # Escape SQL LIKE special characters in the glob pattern
419+ sql_like_pattern = glob_pattern .replace ("%" , r"\%" ).replace ("_" , r"\_" )
420+ # Convert glob wildcards to SQL LIKE wildcards
421+ return sql_like_pattern .replace ("*" , "%" ).replace ("?" , "_" )
422+
423+ rows = await _users_repository .search_merged_pre_and_registered_users (
424+ get_asyncpg_engine (app ),
425+ email_like = _glob_to_sql_like (email_glob ),
426+ product_name = product_name ,
427+ )
428+
429+ async def _list_products_or_none (user_id ):
430+ if user_id is not None and include_products :
431+ products = await _users_repository .get_user_products (
432+ get_asyncpg_engine (app ), user_id = user_id
433+ )
434+ return [_ .product_name for _ in products ]
435+ return None
436+
437+ return [
438+ UserAccountGet (
439+ first_name = r .first_name or r .pre_first_name ,
440+ last_name = r .last_name or r .pre_last_name ,
441+ email = r .email or r .pre_email ,
442+ institution = r .institution ,
443+ phone = r .phone or r .pre_phone ,
444+ address = r .address ,
445+ city = r .city ,
446+ state = r .state ,
447+ postal_code = r .postal_code ,
448+ country = r .country ,
449+ extras = r .extras or {},
450+ invited_by = r .invited_by ,
451+ pre_registration_id = r .id ,
452+ account_request_status = r .account_request_status ,
453+ account_request_reviewed_by = r .account_request_reviewed_by ,
454+ account_request_reviewed_at = r .account_request_reviewed_at ,
455+ products = await _list_products_or_none (r .user_id ),
456+ # NOTE: old users will not have extra details
457+ registered = r .user_id is not None if r .pre_email else r .status is not None ,
458+ status = r .status ,
459+ )
460+ for r in rows
461+ ]
462+
463+
459464async def approve_user_account (
460465 app : web .Application ,
461466 * ,
0 commit comments