File size: 4,768 Bytes
767deae
4ca0250
767deae
 
 
26078c9
767deae
26078c9
 
 
 
 
767deae
 
 
 
 
 
 
 
 
1829c17
767deae
 
 
 
 
 
1829c17
 
 
 
 
 
26078c9
 
 
 
767deae
 
26078c9
 
 
 
 
 
767deae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1829c17
767deae
 
 
 
 
 
 
 
 
1829c17
767deae
26078c9
767deae
 
 
 
 
 
 
 
1829c17
 
 
 
 
 
 
 
 
 
 
 
 
 
767deae
 
 
26078c9
767deae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26078c9
 
 
767deae
26078c9
767deae
26078c9
 
 
 
 
 
 
 
 
 
767deae
 
 
26078c9
 
 
 
 
 
767deae
26078c9
 
 
 
 
 
 
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
"""
Database layer — SQLite for user data and thread metadata.
LangGraph checkpointing also uses SQLite (configured in graph.py).
"""

import sqlite3
import time
from config import DB_PATH


conn = sqlite3.connect(database=DB_PATH, check_same_thread=False)

# --- Schema ---

conn.execute("""
    CREATE TABLE IF NOT EXISTS users (
        google_id       TEXT PRIMARY KEY,
        email           TEXT NOT NULL,
        name            TEXT NOT NULL,
        picture         TEXT,
        openrouter_key  TEXT,
        tavily_key      TEXT,
        student_profile TEXT,
        created_at      REAL NOT NULL,
        last_login      REAL NOT NULL
    )
""")

# Upgrade path: add tavily_key column to pre-existing databases.
try:
    conn.execute("ALTER TABLE users ADD COLUMN tavily_key TEXT")
except sqlite3.OperationalError:
    pass  # column already exists

conn.execute("""
    CREATE TABLE IF NOT EXISTS thread_titles (
        thread_id   TEXT PRIMARY KEY,
        title       TEXT,
        last_active REAL,
        user_id     TEXT
    )
""")

conn.commit()


# --- User operations ---

def upsert_user(google_id: str, email: str, name: str, picture: str = "") -> dict:
    """Create or update a user from Google OAuth data."""
    now = time.time()
    existing = conn.execute(
        "SELECT google_id FROM users WHERE google_id = ?", (google_id,)
    ).fetchone()

    if existing:
        conn.execute(
            "UPDATE users SET last_login = ?, name = ?, picture = ? WHERE google_id = ?",
            (now, name, picture, google_id),
        )
    else:
        conn.execute(
            "INSERT INTO users (google_id, email, name, picture, created_at, last_login) "
            "VALUES (?, ?, ?, ?, ?, ?)",
            (google_id, email, name, picture, now, now),
        )
    conn.commit()
    return {"google_id": google_id, "email": email, "name": name, "picture": picture}


def get_user(google_id: str) -> dict | None:
    row = conn.execute(
        "SELECT google_id, email, name, picture, openrouter_key, student_profile, tavily_key "
        "FROM users WHERE google_id = ?",
        (google_id,),
    ).fetchone()
    if not row:
        return None
    return {
        "google_id": row[0], "email": row[1], "name": row[2],
        "picture": row[3], "openrouter_key": row[4] or "",
        "student_profile": row[5] or "",
        "tavily_key": row[6] or "",
    }


def save_user_api_key(google_id: str, key: str):
    conn.execute(
        "UPDATE users SET openrouter_key = ? WHERE google_id = ?", (key, google_id)
    )
    conn.commit()


def save_tavily_key(google_id: str, key: str):
    conn.execute(
        "UPDATE users SET tavily_key = ? WHERE google_id = ?", (key, google_id)
    )
    conn.commit()


def get_tavily_key(google_id: str) -> str:
    row = conn.execute(
        "SELECT tavily_key FROM users WHERE google_id = ?", (google_id,)
    ).fetchone()
    return row[0] if row and row[0] else ""


def save_student_profile(google_id: str, profile: str):
    conn.execute(
        "UPDATE users SET student_profile = ? WHERE google_id = ?", (profile, google_id)
    )
    conn.commit()


def get_user_api_key(google_id: str) -> str:
    row = conn.execute(
        "SELECT openrouter_key FROM users WHERE google_id = ?", (google_id,)
    ).fetchone()
    return row[0] if row and row[0] else ""


# --- Thread operations ---

def get_threads(user_id: str = ""):
    if user_id:
        cursor = conn.execute(
            "SELECT thread_id, title FROM thread_titles "
            "WHERE user_id = ? ORDER BY last_active DESC",
            (user_id,),
        )
    else:
        cursor = conn.execute(
            "SELECT thread_id, title FROM thread_titles ORDER BY last_active DESC"
        )
    return [{"id": row[0], "title": row[1]} for row in cursor]


def upsert_thread(thread_id: str, message: str, now: float, user_id: str = ""):
    row = conn.execute(
        "SELECT thread_id FROM thread_titles WHERE thread_id = ?", (thread_id,)
    ).fetchone()

    if row:
        conn.execute(
            "UPDATE thread_titles SET last_active = ? WHERE thread_id = ?",
            (now, thread_id),
        )
    else:
        title = message[:30] + ("..." if len(message) > 30 else "")
        conn.execute(
            "INSERT INTO thread_titles (thread_id, title, last_active, user_id) "
            "VALUES (?, ?, ?, ?)",
            (thread_id, title, now, user_id),
        )
    conn.commit()


def rename_thread(thread_id: str, title: str):
    conn.execute(
        "UPDATE thread_titles SET title = ? WHERE thread_id = ?", (title, thread_id)
    )
    conn.commit()


def delete_thread(thread_id: str):
    conn.execute("DELETE FROM thread_titles WHERE thread_id = ?", (thread_id,))
    conn.commit()