Amogh1221 commited on
Commit
bb43d2d
·
verified ·
1 Parent(s): 734d0ce

Delete persistence

Browse files
persistence/__pycache__/sqlite_memory_store.cpython-311.pyc DELETED
Binary file (33.9 kB)
 
persistence/__pycache__/sqlite_memory_store.cpython-313.pyc DELETED
Binary file (24.9 kB)
 
persistence/__pycache__/user_store.cpython-311.pyc DELETED
Binary file (7.25 kB)
 
persistence/sqlite_memory_store.py DELETED
@@ -1,480 +0,0 @@
1
- import os
2
- import json
3
- import sqlite3
4
- import uuid
5
- from contextlib import contextmanager
6
- from datetime import datetime, timezone
7
- from typing import Optional, Any
8
- from core.ports import LLM2Output, LLMProvider
9
- from dotenv import load_dotenv
10
-
11
- try:
12
- import libsql_experimental as libsql
13
- except ImportError:
14
- libsql = None
15
-
16
- load_dotenv()
17
- TURSO_DATABASE_URL = os.getenv("TURSO_DATABASE_URL")
18
- TURSO_AUTH_TOKEN = os.getenv("TURSO_AUTH_TOKEN")
19
- CLOUD_MODE = os.getenv("CLOUD_MODE", "false").lower() == "true"
20
-
21
- class SQLiteBaseStore:
22
- """Base class for SQLite stores providing connection and initialization."""
23
- def __init__(self, db_path: str):
24
- self.db_path = db_path
25
- self._init_db()
26
-
27
- @contextmanager
28
- def _get_conn(self):
29
- """
30
- Yields a short-lived SQLite/Turso connection wrapped in a transaction block.
31
- Ensures the connection is properly closed to prevent file descriptor/memory leaks.
32
- """
33
- if CLOUD_MODE and TURSO_DATABASE_URL and libsql:
34
- conn = libsql.connect(TURSO_DATABASE_URL, auth_token=TURSO_AUTH_TOKEN)
35
- else:
36
- os.makedirs(os.path.dirname(os.path.abspath(self.db_path)), exist_ok=True)
37
- conn = sqlite3.connect(self.db_path, check_same_thread=False)
38
-
39
- conn.row_factory = sqlite3.Row
40
- try:
41
- # The inner 'with conn:' handles committing or rolling back the transaction
42
- with conn:
43
- yield conn
44
- finally:
45
- conn.close()
46
-
47
- def _init_db(self):
48
- with self._get_conn() as conn:
49
- conn.executescript("""
50
- CREATE TABLE IF NOT EXISTS users (
51
- id TEXT PRIMARY KEY,
52
- username TEXT UNIQUE NOT NULL,
53
- email TEXT UNIQUE NOT NULL,
54
- password_hash TEXT NOT NULL,
55
- nationality TEXT,
56
- emergency_contact TEXT,
57
- emergency_contact_name TEXT,
58
- emergency_contact_phone TEXT,
59
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
60
- );
61
-
62
- CREATE TABLE IF NOT EXISTS otp_tokens (
63
- email TEXT PRIMARY KEY,
64
- otp_code TEXT NOT NULL,
65
- expires_at TIMESTAMP NOT NULL
66
- );
67
-
68
- CREATE TABLE IF NOT EXISTS patients (
69
- patient_id TEXT PRIMARY KEY,
70
- user_id TEXT,
71
- name TEXT NOT NULL,
72
- age INTEGER,
73
- gender TEXT,
74
- occupation TEXT,
75
- primary_concern TEXT,
76
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
77
- FOREIGN KEY (user_id) REFERENCES users(id)
78
- );
79
-
80
- CREATE TABLE IF NOT EXISTS sessions (
81
- session_id TEXT PRIMARY KEY,
82
- patient_id TEXT,
83
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
84
- last_active_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
85
- is_active INTEGER DEFAULT 1,
86
- rolling_summary TEXT DEFAULT '',
87
- summarized_msg_count INTEGER DEFAULT 0,
88
- FOREIGN KEY (patient_id) REFERENCES patients(patient_id)
89
- );
90
-
91
- CREATE TABLE IF NOT EXISTS messages (
92
- id INTEGER PRIMARY KEY AUTOINCREMENT,
93
- session_id TEXT NOT NULL,
94
- role TEXT NOT NULL,
95
- content TEXT NOT NULL,
96
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
97
- FOREIGN KEY (session_id) REFERENCES sessions(session_id)
98
- );
99
-
100
- CREATE TABLE IF NOT EXISTS patient_profile (
101
- patient_id TEXT PRIMARY KEY,
102
- profile_json TEXT NOT NULL DEFAULT '{}',
103
- long_term_memory_json TEXT NOT NULL DEFAULT '{}',
104
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
105
- FOREIGN KEY (patient_id) REFERENCES patients(patient_id)
106
- );
107
-
108
- CREATE INDEX IF NOT EXISTS idx_messages_session
109
- ON messages(session_id, id);
110
- """)
111
-
112
- # Safe migrations
113
- try:
114
- conn.execute("ALTER TABLE patients ADD COLUMN gender TEXT")
115
- conn.execute("ALTER TABLE patients ADD COLUMN occupation TEXT")
116
- conn.execute("ALTER TABLE patients ADD COLUMN primary_concern TEXT")
117
- except sqlite3.OperationalError:
118
- pass
119
-
120
- try:
121
- conn.execute("ALTER TABLE users ADD COLUMN emergency_contact_name TEXT")
122
- conn.execute("ALTER TABLE users ADD COLUMN emergency_contact_phone TEXT")
123
- except sqlite3.OperationalError:
124
- pass
125
-
126
- try:
127
- conn.execute("ALTER TABLE patient_profile ADD COLUMN long_term_memory_json TEXT NOT NULL DEFAULT '{}'")
128
- except sqlite3.OperationalError:
129
- pass
130
-
131
- try:
132
- conn.execute("ALTER TABLE sessions ADD COLUMN summarized_msg_count INTEGER DEFAULT 0")
133
- except sqlite3.OperationalError:
134
- pass
135
-
136
- try:
137
- conn.execute("ALTER TABLE sessions ADD COLUMN is_active INTEGER DEFAULT 1")
138
- except sqlite3.OperationalError:
139
- pass
140
-
141
- try:
142
- conn.execute("ALTER TABLE patients ADD COLUMN user_id TEXT")
143
- except sqlite3.OperationalError:
144
- pass
145
-
146
- def _now(self) -> str:
147
- return datetime.now(timezone.utc).isoformat()
148
-
149
-
150
- class SQLiteProfileStore(SQLiteBaseStore):
151
- """Concrete implementation of ProfileStore using SQLite."""
152
-
153
- def create_patient(
154
- self,
155
- name: str,
156
- age: Optional[int] = None,
157
- gender: Optional[str] = None,
158
- occupation: Optional[str] = None,
159
- primary_concern: Optional[str] = None,
160
- user_id: Optional[str] = None
161
- ) -> str:
162
- patient_id = str(uuid.uuid4())
163
- with self._get_conn() as conn:
164
- conn.execute(
165
- "INSERT INTO patients(patient_id, user_id, name, age, gender, occupation, primary_concern) VALUES(?,?,?,?,?,?,?)",
166
- (patient_id, user_id, name, age, gender, occupation, primary_concern),
167
- )
168
- return patient_id
169
-
170
- def list_patients(self) -> list[dict]:
171
- with self._get_conn() as conn:
172
- rows = conn.execute(
173
- "SELECT patient_id, name, age, gender, occupation, primary_concern, created_at FROM patients ORDER BY name"
174
- ).fetchall()
175
- return [dict(r) for r in rows]
176
-
177
- def list_patients_for_user(self, user_id: str) -> list[dict]:
178
- with self._get_conn() as conn:
179
- rows = conn.execute(
180
- "SELECT patient_id, name, age, gender, occupation, primary_concern, created_at FROM patients WHERE user_id=? ORDER BY name",
181
- (user_id,)
182
- ).fetchall()
183
- return [dict(r) for r in rows]
184
-
185
- def get_patient(self, patient_id: str) -> dict:
186
- with self._get_conn() as conn:
187
- row = conn.execute(
188
- """
189
- SELECT p.patient_id, p.name, p.age, p.gender, p.occupation, p.primary_concern, p.created_at, u.nationality
190
- FROM patients p
191
- LEFT JOIN users u ON p.user_id = u.id
192
- WHERE p.patient_id=?
193
- """,
194
- (patient_id,),
195
- ).fetchone()
196
- return dict(row) if row else {}
197
-
198
- def update_primary_concern(self, patient_id: str, new_concern: str) -> None:
199
- with self._get_conn() as conn:
200
- conn.execute(
201
- "UPDATE patients SET primary_concern=? WHERE patient_id=?",
202
- (new_concern, patient_id)
203
- )
204
-
205
- def get_patient_profile(self, patient_id: str) -> dict:
206
- with self._get_conn() as conn:
207
- row = conn.execute(
208
- "SELECT profile_json FROM patient_profile WHERE patient_id=?",
209
- (patient_id,),
210
- ).fetchone()
211
- if not row:
212
- return {}
213
- try:
214
- return json.loads(row["profile_json"])
215
- except Exception:
216
- return {}
217
-
218
- def get_long_term_memory(self, patient_id: str) -> dict:
219
- with self._get_conn() as conn:
220
- row = conn.execute(
221
- "SELECT long_term_memory_json FROM patient_profile WHERE patient_id=?",
222
- (patient_id,),
223
- ).fetchone()
224
- if not row:
225
- return {}
226
- try:
227
- return json.loads(row["long_term_memory_json"])
228
- except Exception:
229
- return {}
230
-
231
- def update_long_term_memory(self, patient_id: str, llm_output: Any) -> None:
232
- memory = self.get_long_term_memory(patient_id)
233
- list_fields = [
234
- "emotional_themes", "thinking_patterns", "behavioral_patterns",
235
- "interpersonal_dynamics", "stressors", "unclear_areas", "protective_factors"
236
- ]
237
-
238
- for field in list_fields:
239
- if hasattr(llm_output, field):
240
- memory[field] = getattr(llm_output, field)
241
-
242
- if hasattr(llm_output, "risk_assessment"):
243
- memory["risk_assessment"] = getattr(llm_output, "risk_assessment")
244
-
245
- memory["last_analyzed"] = self._now()
246
-
247
- with self._get_conn() as conn:
248
- conn.execute(
249
- """
250
- INSERT INTO patient_profile(patient_id, long_term_memory_json, updated_at)
251
- VALUES(?,?,?)
252
- ON CONFLICT(patient_id) DO UPDATE SET
253
- long_term_memory_json=excluded.long_term_memory_json,
254
- updated_at=excluded.updated_at
255
- """,
256
- (patient_id, json.dumps(memory), self._now()),
257
- )
258
-
259
- def update_patient_profile(self, patient_id: str, llm_output: Any) -> None:
260
- profile = self.get_patient_profile(patient_id)
261
- list_fields = [
262
- "emotional_themes", "thinking_patterns", "behavioral_patterns",
263
- "interpersonal_dynamics", "stressors", "unclear_areas", "protective_factors"
264
- ]
265
-
266
- for field in list_fields:
267
- if hasattr(llm_output, field):
268
- profile[field] = getattr(llm_output, field)
269
-
270
- if hasattr(llm_output, "risk_assessment"):
271
- profile["risk_assessment"] = getattr(llm_output, "risk_assessment")
272
- if hasattr(llm_output, "session_summary"):
273
- profile["last_session_summary"] = getattr(llm_output, "session_summary")
274
-
275
- profile["last_analyzed"] = self._now()
276
-
277
- with self._get_conn() as conn:
278
- conn.execute(
279
- """
280
- INSERT INTO patient_profile(patient_id, profile_json, updated_at)
281
- VALUES(?,?,?)
282
- ON CONFLICT(patient_id) DO UPDATE SET
283
- profile_json=excluded.profile_json,
284
- updated_at=excluded.updated_at
285
- """,
286
- (patient_id, json.dumps(profile), self._now()),
287
- )
288
-
289
- def get_patient_sessions(self, patient_id: str) -> list[dict]:
290
- with self._get_conn() as conn:
291
- rows = conn.execute(
292
- "SELECT session_id, created_at, last_active_at, rolling_summary, is_active FROM sessions WHERE patient_id=? ORDER BY created_at DESC",
293
- (patient_id,)
294
- ).fetchall()
295
- return [dict(r) for r in rows]
296
-
297
- def build_profile_recap(self, patient_id: str) -> Optional[str]:
298
- memory = self.get_long_term_memory(patient_id)
299
- if not memory:
300
- memory = self.get_patient_profile(patient_id)
301
- if not memory:
302
- return None
303
-
304
- lines = ["[Returning patient — prior session context (not to be quoted back verbatim)]:"]
305
-
306
- sessions = self.get_patient_sessions(patient_id)
307
- valid_summaries = []
308
- for s in sessions:
309
- summ = s.get("rolling_summary")
310
- if summ and summ.strip() and "no summary available" not in summ.lower():
311
- valid_summaries.append(summ)
312
- if len(valid_summaries) >= 3:
313
- break
314
-
315
- if valid_summaries:
316
- # Reverse to be chronologically ordered (oldest to newest)
317
- valid_summaries.reverse()
318
- lines.append("Recent session summaries:")
319
- for i, summ in enumerate(valid_summaries):
320
- lines.append(f" - {summ}")
321
- if memory.get("emotional_themes"):
322
- lines.append(f"Emotional themes: {'; '.join(memory['emotional_themes'][:4])}")
323
- if memory.get("stressors"):
324
- lines.append(f"Key stressors: {'; '.join(memory['stressors'][:4])}")
325
- if memory.get("risk_assessment") and memory["risk_assessment"] != "Not yet assessed":
326
- lines.append(f"Last risk status: {memory['risk_assessment'][:120]}")
327
- if memory.get("protective_factors"):
328
- lines.append(f"Protective factors: {'; '.join(memory['protective_factors'][:3])}")
329
- if memory.get("last_analyzed"):
330
- lines.append(f"Last analyzed: {memory['last_analyzed']}")
331
-
332
- return "\n".join(lines)
333
-
334
- def reset_patient_data(self, patient_id: str) -> None:
335
- with self._get_conn() as conn:
336
- conn.execute("DELETE FROM messages WHERE session_id IN (SELECT session_id FROM sessions WHERE patient_id = ?)", (patient_id,))
337
- conn.execute("DELETE FROM sessions WHERE patient_id = ?", (patient_id,))
338
- conn.execute(
339
- "UPDATE patient_profile SET profile_json = '{}', long_term_memory_json = '{}', updated_at = ? WHERE patient_id = ?",
340
- (self._now(), patient_id)
341
- )
342
-
343
- def delete_patient(self, patient_id: str) -> None:
344
- with self._get_conn() as conn:
345
- conn.execute("DELETE FROM messages WHERE session_id IN (SELECT session_id FROM sessions WHERE patient_id = ?)", (patient_id,))
346
- conn.execute("DELETE FROM sessions WHERE patient_id = ?", (patient_id,))
347
- conn.execute("DELETE FROM patient_profile WHERE patient_id = ?", (patient_id,))
348
- conn.execute("DELETE FROM patients WHERE patient_id = ?", (patient_id,))
349
-
350
-
351
- class SQLiteSessionStore(SQLiteBaseStore):
352
- """Concrete implementation of SessionStore using SQLite."""
353
-
354
- def __init__(self, db_path: str, working_memory_turns: int):
355
- super().__init__(db_path)
356
- self.working_memory_turns = working_memory_turns
357
-
358
- def create_session(self, patient_id: Optional[str] = None) -> str:
359
- session_id = str(uuid.uuid4())
360
- if patient_id is None:
361
- patient_id = str(uuid.uuid4())
362
-
363
- with self._get_conn() as conn:
364
- row = conn.execute(
365
- "SELECT 1 FROM patients WHERE patient_id=?", (patient_id,)
366
- ).fetchone()
367
- if not row:
368
- conn.execute(
369
- "INSERT INTO patients(patient_id, name, age) VALUES(?,?,?)",
370
- (patient_id, "Guest Patient", None),
371
- )
372
-
373
- with self._get_conn() as conn:
374
- conn.execute(
375
- "INSERT INTO sessions(session_id, patient_id, is_active, created_at, last_active_at) VALUES(?,?,1,?,?)",
376
- (session_id, patient_id, self._now(), self._now()),
377
- )
378
- return session_id
379
-
380
- def end_session(self, session_id: str) -> None:
381
- with self._get_conn() as conn:
382
- conn.execute(
383
- "UPDATE sessions SET last_active_at=?, is_active=0 WHERE session_id=?",
384
- (self._now(), session_id),
385
- )
386
-
387
- def get_abandoned_sessions(self, timeout_minutes: int) -> list[str]:
388
- with self._get_conn() as conn:
389
- rows = conn.execute(
390
- f"SELECT session_id FROM sessions WHERE is_active=1 AND last_active_at < datetime('now', '-{timeout_minutes} minutes')"
391
- ).fetchall()
392
- return [r["session_id"] for r in rows]
393
-
394
- def session_exists(self, session_id: str) -> bool:
395
- with self._get_conn() as conn:
396
- row = conn.execute(
397
- "SELECT 1 FROM sessions WHERE session_id=?", (session_id,)
398
- ).fetchone()
399
- return row is not None
400
-
401
- def append_message(self, session_id: str, role: str, content: str) -> None:
402
- with self._get_conn() as conn:
403
- conn.execute(
404
- "INSERT INTO messages(session_id, role, content, created_at) VALUES(?,?,?,?)",
405
- (session_id, role, content, self._now()),
406
- )
407
- conn.execute(
408
- "UPDATE sessions SET last_active_at=? WHERE session_id=?",
409
- (self._now(), session_id),
410
- )
411
-
412
- def get_active_session(self, patient_id: str) -> Optional[str]:
413
- with self._get_conn() as conn:
414
- row = conn.execute(
415
- "SELECT session_id FROM sessions WHERE patient_id=? AND is_active=1 ORDER BY created_at DESC LIMIT 1",
416
- (patient_id,)
417
- ).fetchone()
418
- return row["session_id"] if row else None
419
-
420
- def get_all_messages(self, session_id: str) -> list[dict]:
421
- with self._get_conn() as conn:
422
- rows = conn.execute(
423
- "SELECT role, content FROM messages WHERE session_id=? ORDER BY id",
424
- (session_id,),
425
- ).fetchall()
426
- return [{"role": r["role"], "content": r["content"]} for r in rows]
427
-
428
- def _get_rolling_summary(self, session_id: str) -> tuple[str, int]:
429
- with self._get_conn() as conn:
430
- row = conn.execute(
431
- "SELECT rolling_summary, summarized_msg_count FROM sessions WHERE session_id=?", (session_id,)
432
- ).fetchone()
433
- if not row:
434
- return "", 0
435
- return (row["rolling_summary"] or ""), (row["summarized_msg_count"] or 0)
436
-
437
- def _set_rolling_summary(self, session_id: str, summary: str, count: int):
438
- with self._get_conn() as conn:
439
- conn.execute(
440
- "UPDATE sessions SET rolling_summary=?, summarized_msg_count=? WHERE session_id=?",
441
- (summary, count, session_id),
442
- )
443
-
444
- def get_working_context(self, session_id: str, llm_engine: Optional[LLMProvider] = None) -> list:
445
- all_msgs = self.get_all_messages(session_id)
446
- if len(all_msgs) <= self.working_memory_turns:
447
- return all_msgs
448
-
449
- tail = all_msgs[-self.working_memory_turns:]
450
- summary, _ = self._get_rolling_summary(session_id)
451
-
452
- context = []
453
- if summary:
454
- context.append({
455
- "role": "system",
456
- "content": f"[Earlier session summary — do not quote back to patient]: {summary}",
457
- })
458
- context.extend(tail)
459
- return context
460
-
461
- def get_patient_id(self, session_id: str) -> Optional[str]:
462
- with self._get_conn() as conn:
463
- row = conn.execute(
464
- "SELECT patient_id FROM sessions WHERE session_id=?", (session_id,)
465
- ).fetchone()
466
- return row["patient_id"] if row else None
467
-
468
- def get_session_count(self, patient_id: str) -> int:
469
- with self._get_conn() as conn:
470
- row = conn.execute(
471
- "SELECT COUNT(*) FROM sessions WHERE patient_id=?", (patient_id,)
472
- ).fetchone()
473
- return row[0]
474
-
475
- def save_session_summary(self, session_id: str, summary: str) -> None:
476
- with self._get_conn() as conn:
477
- conn.execute(
478
- "UPDATE sessions SET rolling_summary = ? WHERE session_id = ?",
479
- (summary, session_id)
480
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
persistence/user_store.py DELETED
@@ -1,91 +0,0 @@
1
- import uuid
2
- from typing import Optional
3
- from datetime import datetime, timezone
4
- from persistence.sqlite_memory_store import SQLiteBaseStore
5
-
6
- class SQLiteUserStore(SQLiteBaseStore):
7
- """Concrete implementation for User authentication persistence using SQLite."""
8
-
9
- def _now(self) -> str:
10
- return datetime.now(timezone.utc).isoformat()
11
-
12
- def create_user(
13
- self,
14
- username: str,
15
- email: str,
16
- password_hash: str,
17
- nationality: Optional[str] = None,
18
- emergency_contact_name: Optional[str] = None,
19
- emergency_contact_phone: Optional[str] = None
20
- ) -> str:
21
- user_id = str(uuid.uuid4())
22
- with self._get_conn() as conn:
23
- conn.execute(
24
- """
25
- INSERT INTO users(id, username, email, password_hash, nationality, emergency_contact_name, emergency_contact_phone, created_at)
26
- VALUES(?,?,?,?,?,?,?,?)
27
- """,
28
- (user_id, username, email, password_hash, nationality, emergency_contact_name, emergency_contact_phone, self._now())
29
- )
30
- return user_id
31
-
32
- def get_user_by_username(self, username: str) -> Optional[dict]:
33
- with self._get_conn() as conn:
34
- row = conn.execute(
35
- "SELECT * FROM users WHERE username=?",
36
- (username,)
37
- ).fetchone()
38
- return dict(row) if row else None
39
-
40
- def get_user_by_email(self, email: str) -> Optional[dict]:
41
- with self._get_conn() as conn:
42
- row = conn.execute(
43
- "SELECT * FROM users WHERE email=?",
44
- (email,)
45
- ).fetchone()
46
- return dict(row) if row else None
47
-
48
- def get_user_by_id(self, user_id: str) -> Optional[dict]:
49
- with self._get_conn() as conn:
50
- row = conn.execute(
51
- "SELECT * FROM users WHERE id=?",
52
- (user_id,)
53
- ).fetchone()
54
- return dict(row) if row else None
55
-
56
- def update_password(self, email: str, new_password_hash: str) -> None:
57
- with self._get_conn() as conn:
58
- conn.execute(
59
- "UPDATE users SET password_hash=? WHERE email=?",
60
- (new_password_hash, email)
61
- )
62
-
63
- def delete_user(self, user_id: str) -> None:
64
- with self._get_conn() as conn:
65
- conn.execute("DELETE FROM users WHERE id=?", (user_id,))
66
-
67
- # OTP Token Management
68
- def store_otp(self, email: str, otp_code: str, expires_at: datetime) -> None:
69
- with self._get_conn() as conn:
70
- conn.execute(
71
- """
72
- INSERT INTO otp_tokens(email, otp_code, expires_at)
73
- VALUES(?,?,?)
74
- ON CONFLICT(email) DO UPDATE SET
75
- otp_code=excluded.otp_code,
76
- expires_at=excluded.expires_at
77
- """,
78
- (email, otp_code, expires_at.isoformat())
79
- )
80
-
81
- def get_otp(self, email: str) -> Optional[dict]:
82
- with self._get_conn() as conn:
83
- row = conn.execute(
84
- "SELECT * FROM otp_tokens WHERE email=?",
85
- (email,)
86
- ).fetchone()
87
- return dict(row) if row else None
88
-
89
- def delete_otp(self, email: str) -> None:
90
- with self._get_conn() as conn:
91
- conn.execute("DELETE FROM otp_tokens WHERE email=?", (email,))