Skip to content

Commit f1e9978

Browse files
committed
Implement ManageMandatoryMembership manager
1 parent a18262a commit f1e9978

3 files changed

Lines changed: 130 additions & 22 deletions

File tree

Lines changed: 102 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,134 @@
11
import json
22
import logging
3-
import mysql.connector
4-
from redis import Redis
53
from datetime import datetime
4+
from typing import List, Optional, Dict
65

6+
import mysql.connector
7+
from redis import Redis
78

9+
from bot.data.config import ADMIN
810
from bot.db.database import Database
911

1012

1113
class ManageMandatoryMembership:
14+
"""Manage list of mandatory channels with Redis and MySQL."""
15+
1216
_REDIS_HASH = "mandatory_membership"
1317

14-
def __init__(self, db: Database, redis_client: Redis, root_logger: logging.Logger):
18+
def __init__(self, db: Database, redis_client: Redis, root_logger: logging.Logger) -> None:
1519
self.db = db
1620
self.redis = redis_client
1721
self.log = root_logger
18-
self._create_table()
22+
self._ensure_table()
1923

20-
def channels(self):
21-
pass
24+
# ------------------------------------------------------------------
25+
def channels(self) -> List[int]:
26+
"""Return list of active channel IDs."""
27+
try:
28+
redis_key = f"{self._REDIS_HASH}:all"
29+
cached = self.redis.get(redis_key)
30+
if cached:
31+
return json.loads(cached)
2232

23-
def update(self, channel_id: int, initiator_user_id: int, is_active: bool):
33+
self.db.cursor.execute(
34+
"SELECT channel_id FROM channels WHERE is_active = TRUE AND deleted_at IS NULL"
35+
)
36+
rows = self.db.cursor.fetchall() or []
37+
channel_ids = [row["channel_id"] for row in rows]
38+
self.redis.set(redis_key, json.dumps(channel_ids))
39+
return channel_ids
40+
except mysql.connector.Error as err:
41+
self.log.error(f"MySQL error in channels(): {err}")
42+
self.db.reconnect()
43+
return []
44+
except Exception as err:
45+
self.log.error(f"Error in channels(): {err}")
46+
return []
2447

25-
current_time = datetime.now()
48+
# ------------------------------------------------------------------
49+
def update(self, channel_id: int, is_active: bool, updater_user_id: int) -> None:
50+
"""Insert or update a channel record.
2651
27-
redis_key = f"{self.ns}:channels"
52+
The channel is created if it does not exist. Existing records can only be
53+
updated by their creator or the super admin defined in :data:`ADMIN`.
54+
All changes are synchronized between MySQL and Redis.
55+
"""
56+
try:
57+
now = datetime.now()
58+
redis_key = f"{self._REDIS_HASH}:{channel_id}"
59+
cached = self.redis.get(redis_key)
60+
channel: Optional[Dict] = json.loads(cached) if cached else None
2861

29-
redis_data = self.redis.get(redis_key)
30-
31-
62+
if channel is None:
63+
self.db.cursor.execute(
64+
"SELECT * FROM channels WHERE channel_id=%s AND deleted_at IS NULL",
65+
(channel_id,),
66+
)
67+
channel = self.db.cursor.fetchone()
68+
if channel:
69+
self.redis.set(redis_key, json.dumps(channel))
3270

33-
cache_data = {
34-
"channel_id": channel_id,
35-
"is_active": True,
36-
"initiator_user_id": initiator_user_id,
37-
"created_at": current_time.isoformat()
38-
}
39-
self.redis.set(redis_key, json.dumps(cache_data))
71+
if channel:
72+
initiator_id = channel.get("initiator_user_id") if isinstance(channel, dict) else channel["initiator_user_id"]
73+
if updater_user_id != ADMIN and updater_user_id != initiator_id:
74+
self.log.warning(
75+
"User %s is not allowed to update channel %s", updater_user_id, channel_id
76+
)
77+
return
4078

79+
sql = (
80+
"UPDATE channels SET is_active=%s, updater_user_id=%s, updated_at=%s WHERE channel_id=%s"
81+
)
82+
self.db.cursor.execute(sql, (is_active, updater_user_id, now, channel_id))
83+
self.db.connection.commit()
4184

85+
channel = dict(channel)
86+
channel.update(
87+
{
88+
"is_active": is_active,
89+
"updater_user_id": updater_user_id,
90+
"updated_at": now.isoformat(),
91+
}
92+
)
93+
else:
94+
sql = (
95+
"INSERT INTO channels (channel_id, initiator_user_id, updater_user_id, "
96+
"is_active, created_at, updated_at) VALUES (%s, %s, %s, %s, %s, %s)"
97+
)
98+
self.db.cursor.execute(
99+
sql,
100+
(channel_id, updater_user_id, updater_user_id, is_active, now, now),
101+
)
102+
self.db.connection.commit()
103+
channel = {
104+
"channel_id": channel_id,
105+
"initiator_user_id": updater_user_id,
106+
"updater_user_id": updater_user_id,
107+
"is_active": is_active,
108+
"created_at": now.isoformat(),
109+
"updated_at": now.isoformat(),
110+
"deleted_at": None,
111+
}
42112

113+
self.redis.set(redis_key, json.dumps(channel))
114+
self.redis.delete(f"{self._REDIS_HASH}:all")
115+
except mysql.connector.Error as err:
116+
self.log.error(f"MySQL error in update(): {err}")
117+
self.db.reconnect()
118+
except Exception as err:
119+
self.log.error(f"Error in update(): {err}")
43120

44-
def _create_table(self):
121+
# ------------------------------------------------------------------
122+
def _ensure_table(self) -> None:
123+
"""Ensure the ``channels`` table exists."""
45124
try:
46125
sql = """
47126
CREATE TABLE IF NOT EXISTS `channels` (
48127
`id` INT AUTO_INCREMENT PRIMARY KEY,
49128
`channel_id` BIGINT,
50129
`initiator_user_id` BIGINT,
51130
`updater_user_id` BIGINT,
52-
`is_active`BOOLEAN DEFAULT TRUE,
131+
`is_active` BOOLEAN DEFAULT TRUE,
53132
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
54133
`updated_at` TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
55134
`deleted_at` TIMESTAMP NULL DEFAULT NULL
@@ -59,6 +138,7 @@ def _create_table(self):
59138
self.db.connection.commit()
60139
except mysql.connector.Error as err:
61140
self.log.error(err)
62-
self.db()
141+
self.db.reconnect()
63142
except Exception as err:
64143
self.log.error(err)
144+

bot/loader.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from bot.core.feature_manager import FeatureManager
1212
from bot.core.settings_manager import SettingsManager
1313
from bot.core.admin_rights_manager import AdminsManager
14+
from bot.core.manage_mandatory_membership import ManageMandatoryMembership
1415

1516

1617

@@ -45,6 +46,7 @@
4546
FM = FeatureManager(db=db, root_logger=root_logger, redis_client=redis)
4647
AM = AdminsManager(db=db, redis_client=redis, root_logger=root_logger)
4748
SM = SettingsManager(db=db, redis_client=redis, root_logger=root_logger)
49+
MMM = ManageMandatoryMembership(db=db, redis_client=redis, root_logger=root_logger)
4850

4951
translator = Translator(db=db, FM=FM, root_logger=root_logger, redis_client=redis)
5052

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import pytest
2+
3+
from bot.loader import MMM, db, redis
4+
from bot.data.config import ADMIN
5+
6+
TEST_CHANNEL = 999999999
7+
8+
@pytest.fixture(autouse=True)
9+
def cleanup():
10+
redis.delete(f"{MMM._REDIS_HASH}:{TEST_CHANNEL}")
11+
redis.delete(f"{MMM._REDIS_HASH}:all")
12+
db.cursor.execute("DELETE FROM channels WHERE channel_id=%s", (TEST_CHANNEL,))
13+
db.connection.commit()
14+
yield
15+
redis.delete(f"{MMM._REDIS_HASH}:{TEST_CHANNEL}")
16+
redis.delete(f"{MMM._REDIS_HASH}:all")
17+
db.cursor.execute("DELETE FROM channels WHERE channel_id=%s", (TEST_CHANNEL,))
18+
db.connection.commit()
19+
20+
21+
def test_update_and_channels():
22+
MMM.update(TEST_CHANNEL, True, ADMIN)
23+
assert redis.get(f"{MMM._REDIS_HASH}:{TEST_CHANNEL}") is not None
24+
channels = MMM.channels()
25+
assert TEST_CHANNEL in channels
26+

0 commit comments

Comments
 (0)