matplobbot-shared 0.1.25__tar.gz → 0.1.27__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of matplobbot-shared might be problematic. Click here for more details.
- {matplobbot_shared-0.1.25 → matplobbot_shared-0.1.27}/PKG-INFO +1 -1
- {matplobbot_shared-0.1.25 → matplobbot_shared-0.1.27}/matplobbot_shared.egg-info/PKG-INFO +1 -1
- {matplobbot_shared-0.1.25 → matplobbot_shared-0.1.27}/setup.py +1 -1
- {matplobbot_shared-0.1.25 → matplobbot_shared-0.1.27}/shared_lib/database.py +141 -16
- {matplobbot_shared-0.1.25 → matplobbot_shared-0.1.27}/README.md +0 -0
- {matplobbot_shared-0.1.25 → matplobbot_shared-0.1.27}/matplobbot_shared.egg-info/SOURCES.txt +0 -0
- {matplobbot_shared-0.1.25 → matplobbot_shared-0.1.27}/matplobbot_shared.egg-info/dependency_links.txt +0 -0
- {matplobbot_shared-0.1.25 → matplobbot_shared-0.1.27}/matplobbot_shared.egg-info/requires.txt +0 -0
- {matplobbot_shared-0.1.25 → matplobbot_shared-0.1.27}/matplobbot_shared.egg-info/top_level.txt +0 -0
- {matplobbot_shared-0.1.25 → matplobbot_shared-0.1.27}/setup.cfg +0 -0
- {matplobbot_shared-0.1.25 → matplobbot_shared-0.1.27}/shared_lib/__init__.py +0 -0
- {matplobbot_shared-0.1.25 → matplobbot_shared-0.1.27}/shared_lib/i18n.py +0 -0
- {matplobbot_shared-0.1.25 → matplobbot_shared-0.1.27}/shared_lib/services/__init__.py +0 -0
- {matplobbot_shared-0.1.25 → matplobbot_shared-0.1.27}/shared_lib/services/schedule_service.py +0 -0
- {matplobbot_shared-0.1.25 → matplobbot_shared-0.1.27}/shared_lib/services/university_api.py +0 -0
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="matplobbot-shared",
|
|
5
|
-
version="0.1.
|
|
5
|
+
version="0.1.27", # Let's use the version from your requirements.txt
|
|
6
6
|
packages=find_packages(include=['shared_lib', 'shared_lib.*']),
|
|
7
7
|
description="Shared library for the Matplobbot ecosystem (database, services, i18n).",
|
|
8
8
|
author="Ackrome",
|
|
@@ -262,24 +262,149 @@ async def get_activity_over_time_data_from_db(db_conn, period='day'):
|
|
|
262
262
|
rows = await db_conn.fetch(query)
|
|
263
263
|
return [{"period": row['period_start'], "count": row['actions_count']} for row in rows]
|
|
264
264
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
265
|
+
|
|
266
|
+
async def get_user_profile_data_from_db(
|
|
267
|
+
db_conn,
|
|
268
|
+
user_id: int,
|
|
269
|
+
page: int = 1,
|
|
270
|
+
page_size: int = 50,
|
|
271
|
+
sort_by: str = 'timestamp',
|
|
272
|
+
sort_order: str = 'desc'
|
|
273
|
+
):
|
|
274
|
+
"""Извлекает детали профиля пользователя и пагинированный список его действий."""
|
|
275
|
+
# --- Безопасная сортировка ---
|
|
276
|
+
allowed_sort_columns = ['id', 'action_type', 'action_details', 'timestamp'] # These are ua columns
|
|
277
|
+
if sort_by not in allowed_sort_columns:
|
|
278
|
+
sort_by = 'timestamp' # Значение по умолчанию
|
|
279
|
+
sort_order = 'ASC' if sort_order.lower() == 'asc' else 'DESC' # Безопасное определение порядка
|
|
280
|
+
|
|
281
|
+
# --- Единый запрос для получения всех данных ---
|
|
282
|
+
# Используем CTE и оконные функции для эффективности.
|
|
283
|
+
# 1. Выбираем все действия пользователя.
|
|
284
|
+
# 2. С помощью оконной функции COUNT(*) OVER () получаем общее количество действий без дополнительного запроса.
|
|
285
|
+
# 3. Присоединяем информацию о пользователе.
|
|
286
|
+
# 4. Применяем пагинацию и сортировку.
|
|
287
|
+
query = f"""
|
|
268
288
|
WITH UserActions AS (
|
|
269
|
-
SELECT
|
|
270
|
-
|
|
289
|
+
SELECT
|
|
290
|
+
id,
|
|
291
|
+
action_type,
|
|
292
|
+
action_details,
|
|
293
|
+
TO_CHAR(timestamp, 'YYYY-MM-DD HH24:MI:SS') AS timestamp
|
|
294
|
+
FROM user_actions
|
|
295
|
+
WHERE user_id = $1
|
|
271
296
|
)
|
|
272
|
-
SELECT
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
297
|
+
SELECT
|
|
298
|
+
u.user_id,
|
|
299
|
+
u.full_name,
|
|
300
|
+
COALESCE(u.username, 'Нет username') AS username,
|
|
301
|
+
u.avatar_pic_url,
|
|
302
|
+
(SELECT COUNT(*) FROM UserActions) as total_actions,
|
|
303
|
+
ua.id as action_id,
|
|
304
|
+
ua.action_type,
|
|
305
|
+
ua.action_details,
|
|
306
|
+
ua.timestamp
|
|
307
|
+
FROM users u
|
|
308
|
+
LEFT JOIN UserActions ua ON 1=1
|
|
309
|
+
WHERE u.user_id = $2
|
|
310
|
+
ORDER BY ua.{sort_by} {sort_order}
|
|
311
|
+
LIMIT $3 OFFSET $4;
|
|
277
312
|
"""
|
|
313
|
+
offset = (page - 1) * page_size
|
|
278
314
|
rows = await db_conn.fetch(query, user_id, user_id, page_size, offset)
|
|
279
|
-
if not rows:
|
|
280
|
-
|
|
315
|
+
if not rows:
|
|
316
|
+
# If user exists but has no actions, we might get no rows. Check user existence separately.
|
|
317
|
+
user_exists = await db_conn.fetchrow("SELECT 1 FROM users WHERE user_id = $1", user_id)
|
|
318
|
+
if not user_exists:
|
|
319
|
+
return None # User not found
|
|
320
|
+
# User exists but has no actions, return empty actions list
|
|
321
|
+
user_details_row = await db_conn.fetchrow("SELECT user_id, full_name, COALESCE(username, 'Нет username') AS username, avatar_pic_url FROM users WHERE user_id = $1", user_id)
|
|
322
|
+
return {
|
|
323
|
+
"user_details": dict(user_details_row),
|
|
324
|
+
"actions": [],
|
|
325
|
+
"total_actions": 0
|
|
326
|
+
}
|
|
327
|
+
|
|
281
328
|
first_row = dict(rows[0])
|
|
282
|
-
user_details = {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
329
|
+
user_details = {
|
|
330
|
+
"user_id": first_row["user_id"],
|
|
331
|
+
"full_name": first_row["full_name"],
|
|
332
|
+
"username": first_row["username"],
|
|
333
|
+
"avatar_pic_url": first_row["avatar_pic_url"]
|
|
334
|
+
}
|
|
335
|
+
total_actions = first_row["total_actions"]
|
|
336
|
+
|
|
337
|
+
# Собираем действия, если они есть (может быть пользователь без действий)
|
|
338
|
+
actions = []
|
|
339
|
+
for row in rows:
|
|
340
|
+
row_dict = dict(row)
|
|
341
|
+
if row_dict["action_id"] is not None: # action_id не NULL
|
|
342
|
+
actions.append({
|
|
343
|
+
"id": row_dict["action_id"],
|
|
344
|
+
"action_type": row_dict["action_type"],
|
|
345
|
+
"action_details": row_dict["action_details"],
|
|
346
|
+
"timestamp": row_dict["timestamp"]
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
"user_details": user_details,
|
|
351
|
+
"actions": actions,
|
|
352
|
+
"total_actions": total_actions
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async def get_users_for_action(db_conn, action_type: str, action_details: str, page: int = 1, page_size: int = 15, sort_by: str = 'full_name', sort_order: str = 'asc'):
|
|
356
|
+
"""Извлекает пагинированный список уникальных пользователей, совершивших определенное действие."""
|
|
357
|
+
# Note: action_type for messages is 'text_message' in the DB
|
|
358
|
+
db_action_type = 'text_message' if action_type == 'message' else action_type
|
|
359
|
+
offset = (page - 1) * page_size
|
|
360
|
+
|
|
361
|
+
# --- Безопасная сортировка ---
|
|
362
|
+
allowed_sort_columns = ['user_id', 'full_name', 'username'] # These are u columns
|
|
363
|
+
if sort_by not in allowed_sort_columns:
|
|
364
|
+
sort_by = 'full_name' # Значение по умолчанию
|
|
365
|
+
sort_order = 'DESC' if sort_order.lower() == 'desc' else 'ASC' # Безопасное определение порядка
|
|
366
|
+
order_by_clause = f"ORDER BY u.{sort_by} {sort_order}"
|
|
367
|
+
|
|
368
|
+
# Query for total count of distinct users
|
|
369
|
+
count_query = """
|
|
370
|
+
SELECT COUNT(DISTINCT u.user_id)
|
|
371
|
+
FROM users u
|
|
372
|
+
JOIN user_actions ua ON u.user_id = ua.user_id
|
|
373
|
+
WHERE ua.action_type = $1 AND ua.action_details = $2;
|
|
374
|
+
"""
|
|
375
|
+
total_users = await db_conn.fetchval(count_query, db_action_type, action_details)
|
|
376
|
+
|
|
377
|
+
# Query for the paginated list of users
|
|
378
|
+
users_query = f"""
|
|
379
|
+
SELECT DISTINCT
|
|
380
|
+
u.user_id,
|
|
381
|
+
u.full_name,
|
|
382
|
+
COALESCE(u.username, 'Нет username') AS username
|
|
383
|
+
FROM users u
|
|
384
|
+
JOIN user_actions ua ON u.user_id = ua.user_id
|
|
385
|
+
WHERE ua.action_type = $1 AND ua.action_details = $2
|
|
386
|
+
{order_by_clause}
|
|
387
|
+
LIMIT $3 OFFSET $4;
|
|
388
|
+
"""
|
|
389
|
+
rows = await db_conn.fetch(users_query, db_action_type, action_details, page_size, offset)
|
|
390
|
+
users = [dict(row) for row in rows]
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
"users": users,
|
|
394
|
+
"total_users": total_users
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async def get_all_user_actions(db_conn, user_id: int):
|
|
398
|
+
"""Извлекает ВСЕ действия для указанного пользователя без пагинации."""
|
|
399
|
+
query = """
|
|
400
|
+
SELECT
|
|
401
|
+
id,
|
|
402
|
+
action_type,
|
|
403
|
+
action_details,
|
|
404
|
+
TO_CHAR(timestamp, 'YYYY-MM-DD HH24:MI:SS') AS timestamp
|
|
405
|
+
FROM user_actions
|
|
406
|
+
WHERE user_id = $1
|
|
407
|
+
ORDER BY timestamp DESC;
|
|
408
|
+
"""
|
|
409
|
+
rows = await db_conn.fetch(query, user_id)
|
|
410
|
+
return [dict(row) for row in rows]
|
|
File without changes
|
{matplobbot_shared-0.1.25 → matplobbot_shared-0.1.27}/matplobbot_shared.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{matplobbot_shared-0.1.25 → matplobbot_shared-0.1.27}/matplobbot_shared.egg-info/requires.txt
RENAMED
|
File without changes
|
{matplobbot_shared-0.1.25 → matplobbot_shared-0.1.27}/matplobbot_shared.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matplobbot_shared-0.1.25 → matplobbot_shared-0.1.27}/shared_lib/services/schedule_service.py
RENAMED
|
File without changes
|
|
File without changes
|