11import json
22import logging
3- import mysql .connector
4- from redis import Redis
53from 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
810from bot .db .database import Database
911
1012
1113class 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+
0 commit comments