KrishnaCosmic commited on
Commit
fb5eec5
·
1 Parent(s): ad2ceb8

Add PR persistence feature

Browse files
config/turso.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Turso (libsql) Database Connection Utility.
3
+
4
+ Provides async database operations for messages and mentorship data.
5
+ """
6
+
7
+ import os
8
+ import logging
9
+ from typing import List, Dict, Any, Optional
10
+ import libsql_client
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ # Database URL and auth token from environment
15
+ TURSO_DATABASE_URL = os.environ.get('TURSO_DATABASE_URL', '')
16
+ TURSO_AUTH_TOKEN = os.environ.get('TURSO_AUTH_TOKEN', '')
17
+
18
+
19
+ class TursoDatabase:
20
+ """Turso database wrapper with async operations."""
21
+
22
+ def __init__(self):
23
+ self._client = None
24
+ self._initialized = False
25
+
26
+ def _get_client(self):
27
+ """Get or create the libsql client."""
28
+ if self._client is None:
29
+ if not TURSO_DATABASE_URL:
30
+ raise ValueError("TURSO_DATABASE_URL environment variable is not set")
31
+
32
+ # Convert libsql:// to https:// for HTTP transport
33
+ url = TURSO_DATABASE_URL
34
+ if url.startswith("libsql://"):
35
+ url = url.replace("libsql://", "https://")
36
+
37
+ self._client = libsql_client.create_client_sync(
38
+ url=url,
39
+ auth_token=TURSO_AUTH_TOKEN if TURSO_AUTH_TOKEN else None
40
+ )
41
+ return self._client
42
+
43
+ def execute(self, sql: str, params: Optional[tuple] = None) -> List[Dict[str, Any]]:
44
+ """Execute a SQL query and return results."""
45
+ client = self._get_client()
46
+ try:
47
+ if params:
48
+ result = client.execute(sql, params)
49
+ else:
50
+ result = client.execute(sql)
51
+
52
+ # Convert result rows to list of dicts
53
+ if result.rows:
54
+ columns = result.columns
55
+ return [dict(zip(columns, row)) for row in result.rows]
56
+ return []
57
+ except Exception as e:
58
+ logger.error(f"Turso execute error: {e}")
59
+ raise
60
+
61
+ def executemany(self, sql: str, params_list: List[tuple]) -> int:
62
+ """Execute a SQL query with multiple parameter sets."""
63
+ client = self._get_client()
64
+ count = 0
65
+ try:
66
+ for params in params_list:
67
+ client.execute(sql, params)
68
+ count += 1
69
+ return count
70
+ except Exception as e:
71
+ logger.error(f"Turso executemany error: {e}")
72
+ raise
73
+
74
+ def init_tables(self):
75
+ """Initialize database tables if they don't exist."""
76
+ if self._initialized:
77
+ return
78
+
79
+ try:
80
+ # Messages table
81
+ self.execute("""
82
+ CREATE TABLE IF NOT EXISTS messages (
83
+ id TEXT PRIMARY KEY,
84
+ sender_id TEXT NOT NULL,
85
+ receiver_id TEXT NOT NULL,
86
+ content TEXT NOT NULL,
87
+ read INTEGER DEFAULT 0,
88
+ timestamp TEXT NOT NULL
89
+ )
90
+ """)
91
+
92
+ # Mentorships table
93
+ self.execute("""
94
+ CREATE TABLE IF NOT EXISTS mentorships (
95
+ id TEXT PRIMARY KEY,
96
+ mentor_id TEXT NOT NULL,
97
+ mentor_username TEXT,
98
+ mentee_id TEXT NOT NULL,
99
+ mentee_username TEXT,
100
+ status TEXT DEFAULT 'active',
101
+ created_at TEXT NOT NULL,
102
+ disconnected_at TEXT
103
+ )
104
+ """)
105
+
106
+ # Mentorship requests table
107
+ self.execute("""
108
+ CREATE TABLE IF NOT EXISTS mentorship_requests (
109
+ id TEXT PRIMARY KEY,
110
+ mentee_id TEXT NOT NULL,
111
+ mentee_username TEXT,
112
+ mentor_id TEXT NOT NULL,
113
+ mentor_username TEXT,
114
+ issue_id TEXT,
115
+ message TEXT,
116
+ status TEXT DEFAULT 'pending',
117
+ created_at TEXT NOT NULL
118
+ )
119
+ """)
120
+
121
+ self._initialized = True
122
+ logger.info("Turso tables initialized successfully")
123
+ except Exception as e:
124
+ logger.error(f"Failed to initialize Turso tables: {e}")
125
+ raise
126
+
127
+ # Message operations
128
+ def insert_message(self, message: Dict[str, Any]) -> bool:
129
+ """Insert a message into the messages table."""
130
+ try:
131
+ self.execute(
132
+ """
133
+ INSERT OR IGNORE INTO messages (id, sender_id, receiver_id, content, read, timestamp)
134
+ VALUES (?, ?, ?, ?, ?, ?)
135
+ """,
136
+ (
137
+ message.get('id'),
138
+ message.get('sender_id'),
139
+ message.get('receiver_id'),
140
+ message.get('content'),
141
+ 1 if message.get('read') else 0,
142
+ message.get('timestamp') if isinstance(message.get('timestamp'), str)
143
+ else message.get('timestamp').isoformat() if message.get('timestamp') else None
144
+ )
145
+ )
146
+ return True
147
+ except Exception as e:
148
+ logger.error(f"Failed to insert message: {e}")
149
+ return False
150
+
151
+ def get_messages(self, user_id: str, other_user_id: str) -> List[Dict[str, Any]]:
152
+ """Get messages between two users."""
153
+ return self.execute(
154
+ """
155
+ SELECT * FROM messages
156
+ WHERE (sender_id = ? AND receiver_id = ?)
157
+ OR (sender_id = ? AND receiver_id = ?)
158
+ ORDER BY timestamp ASC
159
+ """,
160
+ (user_id, other_user_id, other_user_id, user_id)
161
+ )
162
+
163
+ # Mentorship operations
164
+ def insert_mentorship(self, mentorship: Dict[str, Any]) -> bool:
165
+ """Insert a mentorship record."""
166
+ try:
167
+ self.execute(
168
+ """
169
+ INSERT OR IGNORE INTO mentorships
170
+ (id, mentor_id, mentor_username, mentee_id, mentee_username, status, created_at, disconnected_at)
171
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
172
+ """,
173
+ (
174
+ mentorship.get('id'),
175
+ mentorship.get('mentor_id'),
176
+ mentorship.get('mentor_username'),
177
+ mentorship.get('mentee_id'),
178
+ mentorship.get('mentee_username'),
179
+ mentorship.get('status', 'active'),
180
+ mentorship.get('created_at') if isinstance(mentorship.get('created_at'), str)
181
+ else mentorship.get('created_at').isoformat() if mentorship.get('created_at') else None,
182
+ mentorship.get('disconnected_at')
183
+ )
184
+ )
185
+ return True
186
+ except Exception as e:
187
+ logger.error(f"Failed to insert mentorship: {e}")
188
+ return False
189
+
190
+ def insert_mentorship_request(self, request: Dict[str, Any]) -> bool:
191
+ """Insert a mentorship request."""
192
+ try:
193
+ self.execute(
194
+ """
195
+ INSERT OR IGNORE INTO mentorship_requests
196
+ (id, mentee_id, mentee_username, mentor_id, mentor_username, issue_id, message, status, created_at)
197
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
198
+ """,
199
+ (
200
+ request.get('id'),
201
+ request.get('mentee_id'),
202
+ request.get('mentee_username'),
203
+ request.get('mentor_id'),
204
+ request.get('mentor_username'),
205
+ request.get('issue_id'),
206
+ request.get('message'),
207
+ request.get('status', 'pending'),
208
+ request.get('created_at') if isinstance(request.get('created_at'), str)
209
+ else request.get('created_at').isoformat() if request.get('created_at') else None
210
+ )
211
+ )
212
+ return True
213
+ except Exception as e:
214
+ logger.error(f"Failed to insert mentorship request: {e}")
215
+ return False
216
+
217
+ def get_mentorships_for_mentee(self, mentee_id: str) -> List[Dict[str, Any]]:
218
+ """Get all mentorships for a mentee."""
219
+ return self.execute(
220
+ "SELECT * FROM mentorships WHERE mentee_id = ? AND status = 'active'",
221
+ (mentee_id,)
222
+ )
223
+
224
+
225
+ # Singleton instance
226
+ turso_db = TursoDatabase()
requirements.txt CHANGED
@@ -42,4 +42,7 @@ asyncio-throttle>=1.0.2
42
 
43
  # JWT Authentication
44
  PyJWT>=2.8.0
 
 
 
45
  pyspark
 
42
 
43
  # JWT Authentication
44
  PyJWT>=2.8.0
45
+
46
+ # Turso (libsql) Database
47
+ libsql-client>=0.3.0
48
  pyspark
routes/data_routes.py CHANGED
@@ -15,6 +15,7 @@ import jwt
15
  import os
16
 
17
  from config.database import db
 
18
  from config.settings import settings
19
 
20
  logger = logging.getLogger(__name__)
@@ -360,9 +361,16 @@ async def send_message(request: SendMessageRequest, user: dict = Depends(get_cur
360
  "read": False
361
  }
362
 
 
363
  await db.messages.insert_one(message)
364
  message.pop("_id", None) # Remove MongoDB's _id if present
365
 
 
 
 
 
 
 
366
  return message
367
 
368
 
 
15
  import os
16
 
17
  from config.database import db
18
+ from config.turso import turso_db
19
  from config.settings import settings
20
 
21
  logger = logging.getLogger(__name__)
 
361
  "read": False
362
  }
363
 
364
+ # Insert into MongoDB
365
  await db.messages.insert_one(message)
366
  message.pop("_id", None) # Remove MongoDB's _id if present
367
 
368
+ # Also insert into Turso for migration
369
+ try:
370
+ turso_db.insert_message(message)
371
+ except Exception as e:
372
+ logger.warning(f"Failed to insert message into Turso: {e}")
373
+
374
  return message
375
 
376
 
scripts/migrate_to_turso.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ MongoDB to Turso Migration Script
4
+
5
+ Migrates messages, mentorships, and mentorship_requests from MongoDB to Turso.
6
+ Run this script once to copy existing data.
7
+
8
+ Usage:
9
+ cd /Users/krishna./Desktop/Projects/OpenTriage_AI
10
+ python scripts/migrate_to_turso.py
11
+ """
12
+
13
+ import asyncio
14
+ import logging
15
+ import sys
16
+ from pathlib import Path
17
+
18
+ # Add parent directory to path for imports
19
+ sys.path.insert(0, str(Path(__file__).parent.parent))
20
+
21
+ from dotenv import load_dotenv
22
+ load_dotenv()
23
+
24
+ from config.database import db
25
+ from config.turso import turso_db
26
+
27
+ logging.basicConfig(
28
+ level=logging.INFO,
29
+ format="%(asctime)s - %(levelname)s - %(message)s"
30
+ )
31
+ logger = logging.getLogger(__name__)
32
+
33
+
34
+ async def migrate_messages():
35
+ """Migrate all messages from MongoDB to Turso."""
36
+ logger.info("Migrating messages...")
37
+
38
+ cursor = db.messages.find({}, {"_id": 0})
39
+ messages = await cursor.to_list(length=None)
40
+
41
+ success_count = 0
42
+ for msg in messages:
43
+ if turso_db.insert_message(msg):
44
+ success_count += 1
45
+
46
+ logger.info(f"Migrated {success_count}/{len(messages)} messages")
47
+ return success_count
48
+
49
+
50
+ async def migrate_mentorships():
51
+ """Migrate all mentorships from MongoDB to Turso."""
52
+ logger.info("Migrating mentorships...")
53
+
54
+ cursor = db.mentorships.find({}, {"_id": 0})
55
+ mentorships = await cursor.to_list(length=None)
56
+
57
+ success_count = 0
58
+ for m in mentorships:
59
+ if turso_db.insert_mentorship(m):
60
+ success_count += 1
61
+
62
+ logger.info(f"Migrated {success_count}/{len(mentorships)} mentorships")
63
+ return success_count
64
+
65
+
66
+ async def migrate_mentorship_requests():
67
+ """Migrate all mentorship requests from MongoDB to Turso."""
68
+ logger.info("Migrating mentorship requests...")
69
+
70
+ cursor = db.mentorship_requests.find({}, {"_id": 0})
71
+ requests = await cursor.to_list(length=None)
72
+
73
+ success_count = 0
74
+ for req in requests:
75
+ if turso_db.insert_mentorship_request(req):
76
+ success_count += 1
77
+
78
+ logger.info(f"Migrated {success_count}/{len(requests)} mentorship requests")
79
+ return success_count
80
+
81
+
82
+ async def main():
83
+ """Run the migration."""
84
+ logger.info("=" * 50)
85
+ logger.info("MongoDB to Turso Migration")
86
+ logger.info("=" * 50)
87
+
88
+ # Initialize Turso tables
89
+ logger.info("Initializing Turso tables...")
90
+ turso_db.init_tables()
91
+
92
+ # Run migrations
93
+ msg_count = await migrate_messages()
94
+ mentorship_count = await migrate_mentorships()
95
+ request_count = await migrate_mentorship_requests()
96
+
97
+ logger.info("=" * 50)
98
+ logger.info("Migration Complete!")
99
+ logger.info(f" Messages: {msg_count}")
100
+ logger.info(f" Mentorships: {mentorship_count}")
101
+ logger.info(f" Mentorship Requests: {request_count}")
102
+ logger.info("=" * 50)
103
+
104
+
105
+ if __name__ == "__main__":
106
+ asyncio.run(main())