File size: 7,096 Bytes
c8b1f17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
import json
import time
from typing import Dict, List, Any, Optional
from sqlalchemy.orm import Session
from .models import ConfigItem, SensitiveWord, WhitelistItem, SessionLocal

class ConfigManager:
    _instance = None
    _cache = {}
    _last_refresh = 0
    CACHE_TTL = 60 # 1 minute cache

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._refresh_cache_if_needed(force=True)
        return cls._instance

    def get_db(self):
        db = SessionLocal()
        try:
            yield db
        finally:
            db.close()

    def _refresh_cache_if_needed(self, force=False):
        if force or time.time() - self._last_refresh > self.CACHE_TTL:
            self.refresh_cache()

    def refresh_cache(self):
        # print("Refreshing config cache from DB...")
        db = SessionLocal()
        try:
            # 1. Refresh Sensitive Words
            words = db.query(SensitiveWord).filter(SensitiveWord.is_active == True).all()
            sensitive_dict = {}
            # Reconstruct nested structure: category -> subcategory -> list of words
            for w in words:
                if w.category not in sensitive_dict:
                    sensitive_dict[w.category] = {}

                # If subcategory is empty/null, use 'default' or handle appropriately
                # Original JSON structure: "discrimination": {"age": [...], "gender": [...]}
                sub = w.subcategory if w.subcategory else "general"

                if sub not in sensitive_dict[w.category]:
                    sensitive_dict[w.category][sub] = []

                sensitive_dict[w.category][sub].append(w.word)

            # 2. Refresh Whitelist
            whitelist = db.query(WhitelistItem).filter(WhitelistItem.is_active == True).all()
            whitelist_list = [w.word for w in whitelist]

            # 3. Refresh Configs
            configs = db.query(ConfigItem).filter(ConfigItem.is_active == True).all()
            config_dict = {}
            for c in configs:
                try:
                    if c.type in ['json', 'list', 'dict', 'bool']:
                        val = json.loads(c.value)
                    elif c.type == 'int':
                        val = int(c.value)
                    elif c.type == 'float':
                        val = float(c.value)
                    else:
                        val = c.value
                    config_dict[c.key] = val
                except Exception as e:
                    print(f"Error parsing config {c.key}: {e}")
                    config_dict[c.key] = c.value

            self._cache = {
                "sensitive_words": sensitive_dict,
                "whitelist": whitelist_list,
                "configs": config_dict
            }
            self._last_refresh = time.time()
        except Exception as e:
            # Silently handle database not initialized yet (first startup)
            # Tables will be created in main.py lifespan
            if "no such table" not in str(e):
                print(f"Error refreshing cache: {e}")
        finally:
            db.close()

    def get_sensitive_words(self) -> Dict:
        self._refresh_cache_if_needed()
        return self._cache.get("sensitive_words", {})

    def get_whitelist(self) -> List[str]:
        self._refresh_cache_if_needed()
        return self._cache.get("whitelist", [])

    def get_config(self, key: str, default: Any = None) -> Any:
        self._refresh_cache_if_needed()
        if key == "all":
            return self._cache.get("configs", {})
        return self._cache.get("configs", {}).get(key, default)

    def get_all_configs(self) -> Dict:
        self._refresh_cache_if_needed()
        return self._cache.get("configs", {})

    # CRUD Operations (Direct DB access, clears cache)
    def add_sensitive_word(self, word: str, category: str, subcategory: Optional[str] = None, severity: str = 'medium'):
        db = SessionLocal()
        try:
            # Check if exists to update or add
            existing = db.query(SensitiveWord).filter(
                SensitiveWord.word == word, 
                SensitiveWord.category == category,
                SensitiveWord.subcategory == subcategory
            ).first()
            
            if existing:
                existing.severity = severity
                existing.is_active = True
            else:
                item = SensitiveWord(word=word, category=category, subcategory=subcategory, severity=severity)
                db.add(item)
            
            db.commit()
            self._last_refresh = 0 # Invalidate cache
            return True
        except Exception as e:
            db.rollback()
            print(f"Error adding sensitive word: {e}")
            return False
        finally:
            db.close()

    def add_whitelist_item(self, word: str, category: str = 'general'):
        db = SessionLocal()
        try:
            existing = db.query(WhitelistItem).filter(WhitelistItem.word == word).first()
            if existing:
                existing.is_active = True
            else:
                item = WhitelistItem(word=word, category=category)
                db.add(item)
            db.commit()
            self._last_refresh = 0
            return True
        except Exception as e:
            db.rollback()
            print(f"Error adding whitelist item: {e}")
            return False
        finally:
            db.close()

    def remove_sensitive_word(self, word: str) -> bool:
        db = SessionLocal()
        try:
            item = db.query(SensitiveWord).filter(SensitiveWord.word == word).first()
            if item:
                item.is_active = False # Soft delete
                db.commit()
                self._last_refresh = 0
                return True
            return False
        except Exception as e:
            db.rollback()
            return False
        finally:
            db.close()

    def update_config(self, key: str, value: Any, type_: str = 'string', group: str = 'general') -> bool:
        db = SessionLocal()
        try:
            str_val = value
            if type_ in ['json', 'list', 'dict', 'bool']:
                str_val = json.dumps(value, ensure_ascii=False)
            elif type_ in ['int', 'float']:
                str_val = str(value)
            
            item = db.query(ConfigItem).filter(ConfigItem.key == key).first()
            if item:
                item.value = str_val
                item.type = type_
                # item.group_name = group # Optional: update group
            else:
                item = ConfigItem(key=key, value=str_val, type=type_, group_name=group)
                db.add(item)
            
            db.commit()
            self._last_refresh = 0
            return True
        except Exception as e:
            db.rollback()
            print(f"Error updating config: {e}")
            return False
        finally:
            db.close()

config_manager = ConfigManager()