File size: 6,921 Bytes
75788a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
import sqlite3
from datetime import datetime, timedelta
from typing import Optional, Dict, List, Any
from enum import Enum
import json
import hashlib

class Platform(str, Enum):
    WHATSAPP = "whatsapp"
    TELEGRAM = "telegram"
    UI = "ui"

class Database:
    def __init__(self, db_path: str = "bot_data.db"):
        self.db_path = db_path
        self.init_db()
    
    def get_connection(self):
        conn = sqlite3.connect(self.db_path)
        conn.row_factory = sqlite3.Row
        return conn
    
    def init_db(self):
        conn = self.get_connection()
        cursor = conn.cursor()
        
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                platform TEXT NOT NULL,
                platform_user_id TEXT NOT NULL,
                name TEXT,
                age INTEGER,
                location_str TEXT,
                language_pref TEXT DEFAULT 'en',
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                UNIQUE(platform, platform_user_id)
            )
        """)
        
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS query_cache (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id INTEGER,
                category TEXT NOT NULL,
                budget_inr INTEGER NOT NULL,
                location_key TEXT,
                response_text TEXT NOT NULL,
                picks_json TEXT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                ttl_expires_at TIMESTAMP NOT NULL,
                FOREIGN KEY (user_id) REFERENCES users(id)
            )
        """)
        
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS events (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id INTEGER,
                channel TEXT NOT NULL,
                type TEXT NOT NULL,
                payload_meta TEXT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                FOREIGN KEY (user_id) REFERENCES users(id)
            )
        """)
        
        cursor.execute("""
            CREATE INDEX IF NOT EXISTS idx_users_platform 
            ON users(platform, platform_user_id)
        """)
        
        cursor.execute("""
            CREATE INDEX IF NOT EXISTS idx_cache_lookup 
            ON query_cache(category, budget_inr, location_key, ttl_expires_at)
        """)
        
        conn.commit()
        conn.close()
    
    def get_user(self, platform: Platform, platform_user_id: str) -> Optional[Dict]:
        conn = self.get_connection()
        cursor = conn.cursor()
        
        cursor.execute("""
            SELECT * FROM users 
            WHERE platform = ? AND platform_user_id = ?
        """, (platform.value, platform_user_id))
        
        row = cursor.fetchone()
        conn.close()
        
        if row:
            return dict(row)
        return None
    
    def upsert_user(self, platform: Platform, platform_user_id: str, **fields) -> int:
        conn = self.get_connection()
        cursor = conn.cursor()
        
        existing = self.get_user(platform, platform_user_id)
        
        if existing:
            update_fields = []
            values = []
            for key, value in fields.items():
                if key in ['name', 'age', 'location_str', 'language_pref']:
                    update_fields.append(f"{key} = ?")
                    values.append(value)
            
            if update_fields:
                values.extend([datetime.now(), platform.value, platform_user_id])
                cursor.execute(f"""
                    UPDATE users 
                    SET {', '.join(update_fields)}, updated_at = ?
                    WHERE platform = ? AND platform_user_id = ?
                """, values)
            
            user_id = existing['id']
        else:
            cursor.execute("""
                INSERT INTO users (platform, platform_user_id, name, age, location_str, language_pref)
                VALUES (?, ?, ?, ?, ?, ?)
            """, (
                platform.value, 
                platform_user_id,
                fields.get('name'),
                fields.get('age'),
                fields.get('location_str'),
                fields.get('language_pref', 'en')
            ))
            user_id = cursor.lastrowid
        
        conn.commit()
        conn.close()
        return user_id
    
    def cache_get(self, category: str, budget_inr: int, location_key: str) -> Optional[Dict]:
        conn = self.get_connection()
        cursor = conn.cursor()
        
        cursor.execute("""
            SELECT * FROM query_cache
            WHERE category = ? AND budget_inr = ? AND location_key = ?
            AND ttl_expires_at > ?
            ORDER BY created_at DESC
            LIMIT 1
        """, (category, budget_inr, location_key, datetime.now()))
        
        row = cursor.fetchone()
        conn.close()
        
        if row:
            result = dict(row)
            if result.get('picks_json'):
                result['picks_json'] = json.loads(result['picks_json'])
            return result
        return None
    
    def cache_put(
        self, 
        user_id: int,
        category: str, 
        budget_inr: int, 
        location_key: str,
        response_text: str,
        picks_json: List[Dict],
        ttl_hours: int = 24
    ) -> int:
        conn = self.get_connection()
        cursor = conn.cursor()
        
        ttl_expires_at = datetime.now() + timedelta(hours=ttl_hours)
        
        cursor.execute("""
            INSERT INTO query_cache 
            (user_id, category, budget_inr, location_key, response_text, picks_json, ttl_expires_at)
            VALUES (?, ?, ?, ?, ?, ?, ?)
        """, (
            user_id,
            category,
            budget_inr,
            location_key,
            response_text,
            json.dumps(picks_json),
            ttl_expires_at
        ))
        
        cache_id = cursor.lastrowid
        conn.commit()
        conn.close()
        return cache_id
    
    def log_event(self, user_id: int, channel: str, event_type: str, payload_meta: Dict = None):
        conn = self.get_connection()
        cursor = conn.cursor()
        
        cursor.execute("""
            INSERT INTO events (user_id, channel, type, payload_meta)
            VALUES (?, ?, ?, ?)
        """, (user_id, channel, event_type, json.dumps(payload_meta) if payload_meta else None))
        
        conn.commit()
        conn.close()
    
    @staticmethod
    def make_location_key(location_str: Optional[str]) -> str:
        if not location_str:
            return "default"
        return hashlib.md5(location_str.lower().strip().encode()).hexdigest()[:8]

db = Database()