Spaces:
Running
Running
Upload 12 files
Browse files- app/utils/db_http.py +131 -140
- main.py +5 -1
app/utils/db_http.py
CHANGED
@@ -5,9 +5,9 @@ This module provides functions for interacting with the Turso database using the
|
|
5 |
|
6 |
import os
|
7 |
import logging
|
8 |
-
import requests
|
9 |
import time
|
10 |
-
|
|
|
11 |
|
12 |
# Configure logging
|
13 |
logger = logging.getLogger("auth-server")
|
@@ -15,32 +15,32 @@ logger = logging.getLogger("auth-server")
|
|
15 |
def get_http_url() -> Tuple[str, str]:
|
16 |
"""
|
17 |
Get the HTTP URL and auth token for the Turso database.
|
18 |
-
|
19 |
Returns:
|
20 |
Tuple[str, str]: The HTTP URL and auth token.
|
21 |
"""
|
22 |
# Extract the database URL and auth token
|
23 |
db_url = os.getenv("TURSO_DATABASE_URL", "")
|
24 |
auth_token = os.getenv("TURSO_AUTH_TOKEN", "")
|
25 |
-
|
26 |
# Convert URL from libsql:// to https://
|
27 |
if db_url.startswith("libsql://"):
|
28 |
http_url = db_url.replace("libsql://", "https://")
|
29 |
else:
|
30 |
http_url = db_url
|
31 |
-
|
32 |
# Ensure the URL doesn't have a trailing slash
|
33 |
http_url = http_url.rstrip('/')
|
34 |
-
|
35 |
return http_url, auth_token
|
36 |
|
37 |
def get_headers(auth_token: str) -> Dict[str, str]:
|
38 |
"""
|
39 |
Get the headers for the HTTP request.
|
40 |
-
|
41 |
Args:
|
42 |
auth_token (str): The authentication token.
|
43 |
-
|
44 |
Returns:
|
45 |
Dict[str, str]: The headers for the HTTP request.
|
46 |
"""
|
@@ -52,23 +52,24 @@ def get_headers(auth_token: str) -> Dict[str, str]:
|
|
52 |
def execute_query(sql: str, params: List[Dict[str, Any]] = None, operation_id: str = None) -> Dict[str, Any]:
|
53 |
"""
|
54 |
Execute a SQL query using the HTTP API.
|
55 |
-
|
56 |
Args:
|
57 |
sql (str): The SQL query to execute.
|
58 |
params (List[Dict[str, Any]], optional): The parameters for the query. Defaults to None.
|
59 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
60 |
-
|
61 |
Returns:
|
62 |
Dict[str, Any]: The response from the database.
|
63 |
"""
|
64 |
if operation_id is None:
|
65 |
operation_id = f"query_{int(time.time())}"
|
66 |
-
|
67 |
-
|
68 |
-
|
|
|
69 |
http_url, auth_token = get_http_url()
|
70 |
headers = get_headers(auth_token)
|
71 |
-
|
72 |
# Prepare the query
|
73 |
query = {
|
74 |
"requests": [
|
@@ -82,48 +83,48 @@ def execute_query(sql: str, params: List[Dict[str, Any]] = None, operation_id: s
|
|
82 |
{"type": "close"}
|
83 |
]
|
84 |
}
|
85 |
-
|
86 |
# Send the request
|
87 |
try:
|
88 |
-
response =
|
89 |
response.raise_for_status()
|
90 |
result = response.json()
|
91 |
-
logger.
|
92 |
return result
|
93 |
except Exception as e:
|
94 |
logger.error(f"[{operation_id}] Error executing query: {str(e)}")
|
95 |
raise
|
96 |
|
97 |
-
def execute_pipeline(
|
98 |
"""
|
99 |
Execute a pipeline of SQL queries using the HTTP API.
|
100 |
-
|
101 |
Args:
|
102 |
-
|
103 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
104 |
-
|
105 |
Returns:
|
106 |
Dict[str, Any]: The response from the database.
|
107 |
"""
|
108 |
if operation_id is None:
|
109 |
operation_id = f"pipeline_{int(time.time())}"
|
110 |
-
|
111 |
-
logger.
|
112 |
-
|
113 |
http_url, auth_token = get_http_url()
|
114 |
headers = get_headers(auth_token)
|
115 |
-
|
116 |
# Prepare the pipeline
|
117 |
pipeline = {
|
118 |
-
"requests":
|
119 |
}
|
120 |
-
|
121 |
# Send the request
|
122 |
try:
|
123 |
-
response =
|
124 |
response.raise_for_status()
|
125 |
result = response.json()
|
126 |
-
logger.
|
127 |
return result
|
128 |
except Exception as e:
|
129 |
logger.error(f"[{operation_id}] Error executing pipeline: {str(e)}")
|
@@ -132,25 +133,25 @@ def execute_pipeline(requests: List[Dict[str, Any]], operation_id: str = None) -
|
|
132 |
def insert_record(table: str, data: Dict[str, Any], operation_id: str = None) -> Optional[int]:
|
133 |
"""
|
134 |
Insert a record into a table.
|
135 |
-
|
136 |
Args:
|
137 |
table (str): The table to insert into.
|
138 |
data (Dict[str, Any]): The data to insert.
|
139 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
140 |
-
|
141 |
Returns:
|
142 |
Optional[int]: The ID of the inserted record, or None if the insert failed.
|
143 |
"""
|
144 |
if operation_id is None:
|
145 |
operation_id = f"insert_{int(time.time())}"
|
146 |
-
|
147 |
-
logger.
|
148 |
-
|
149 |
# Prepare the SQL and parameters
|
150 |
columns = list(data.keys())
|
151 |
placeholders = ["?"] * len(columns)
|
152 |
sql = f"INSERT INTO {table} ({', '.join(columns)}) VALUES ({', '.join(placeholders)})"
|
153 |
-
|
154 |
params = []
|
155 |
for key, value in data.items():
|
156 |
if isinstance(value, str):
|
@@ -163,42 +164,32 @@ def insert_record(table: str, data: Dict[str, Any], operation_id: str = None) ->
|
|
163 |
params.append({"type": "null", "value": None})
|
164 |
else:
|
165 |
params.append({"type": "text", "value": str(value)})
|
166 |
-
|
167 |
-
# Execute the
|
168 |
-
pipeline_requests = [
|
169 |
-
{
|
170 |
-
"type": "execute",
|
171 |
-
"stmt": {
|
172 |
-
"sql": sql,
|
173 |
-
"args": params
|
174 |
-
}
|
175 |
-
},
|
176 |
-
{
|
177 |
-
"type": "execute",
|
178 |
-
"stmt": {"sql": "SELECT last_insert_rowid()"}
|
179 |
-
}
|
180 |
-
]
|
181 |
-
|
182 |
try:
|
183 |
-
|
184 |
-
|
185 |
-
|
|
|
186 |
if "results" in result and len(result["results"]) > 0:
|
187 |
if result["results"][0]["type"] == "error":
|
188 |
error_msg = result["results"][0]["error"]["message"]
|
189 |
logger.error(f"[{operation_id}] Insert error: {error_msg}")
|
190 |
return None
|
191 |
-
|
192 |
-
# Try to get the last inserted ID
|
|
|
|
|
193 |
last_id = None
|
194 |
-
if "results" in
|
195 |
-
if
|
196 |
-
rows =
|
197 |
if rows and len(rows) > 0:
|
198 |
last_id = int(rows[0][0]["value"])
|
199 |
logger.info(f"[{operation_id}] Record inserted with ID: {last_id}")
|
200 |
return last_id
|
201 |
-
|
|
|
202 |
logger.warning(f"[{operation_id}] Insert succeeded but couldn't get ID")
|
203 |
return None
|
204 |
except Exception as e:
|
@@ -208,26 +199,26 @@ def insert_record(table: str, data: Dict[str, Any], operation_id: str = None) ->
|
|
208 |
def update_record(table: str, data: Dict[str, Any], condition: str, condition_params: List[Dict[str, Any]], operation_id: str = None) -> bool:
|
209 |
"""
|
210 |
Update a record in a table.
|
211 |
-
|
212 |
Args:
|
213 |
table (str): The table to update.
|
214 |
data (Dict[str, Any]): The data to update.
|
215 |
condition (str): The condition for the update (e.g., "id = ?").
|
216 |
condition_params (List[Dict[str, Any]]): The parameters for the condition.
|
217 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
218 |
-
|
219 |
Returns:
|
220 |
bool: True if the update succeeded, False otherwise.
|
221 |
"""
|
222 |
if operation_id is None:
|
223 |
operation_id = f"update_{int(time.time())}"
|
224 |
-
|
225 |
-
logger.
|
226 |
-
|
227 |
# Prepare the SQL and parameters
|
228 |
set_clauses = [f"{key} = ?" for key in data.keys()]
|
229 |
sql = f"UPDATE {table} SET {', '.join(set_clauses)} WHERE {condition}"
|
230 |
-
|
231 |
params = []
|
232 |
for key, value in data.items():
|
233 |
if isinstance(value, str):
|
@@ -240,25 +231,25 @@ def update_record(table: str, data: Dict[str, Any], condition: str, condition_pa
|
|
240 |
params.append({"type": "null", "value": None})
|
241 |
else:
|
242 |
params.append({"type": "text", "value": str(value)})
|
243 |
-
|
244 |
# Add condition parameters
|
245 |
params.extend(condition_params)
|
246 |
-
|
247 |
try:
|
248 |
result = execute_query(sql, params, operation_id)
|
249 |
-
|
250 |
# Check for errors
|
251 |
if "results" in result and len(result["results"]) > 0:
|
252 |
if result["results"][0]["type"] == "error":
|
253 |
error_msg = result["results"][0]["error"]["message"]
|
254 |
logger.error(f"[{operation_id}] Update error: {error_msg}")
|
255 |
return False
|
256 |
-
|
257 |
# Check affected rows
|
258 |
affected_rows = result["results"][0]["response"]["result"]["affected_row_count"]
|
259 |
-
logger.
|
260 |
return affected_rows > 0
|
261 |
-
|
262 |
return False
|
263 |
except Exception as e:
|
264 |
logger.error(f"[{operation_id}] Error updating record: {str(e)}")
|
@@ -267,50 +258,50 @@ def update_record(table: str, data: Dict[str, Any], condition: str, condition_pa
|
|
267 |
def delete_record(table: str, condition: str, condition_params: List[Dict[str, Any]], operation_id: str = None) -> bool:
|
268 |
"""
|
269 |
Delete a record from a table.
|
270 |
-
|
271 |
Args:
|
272 |
table (str): The table to delete from.
|
273 |
condition (str): The condition for the delete (e.g., "id = ?").
|
274 |
condition_params (List[Dict[str, Any]]): The parameters for the condition.
|
275 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
276 |
-
|
277 |
Returns:
|
278 |
bool: True if the delete succeeded, False otherwise.
|
279 |
"""
|
280 |
if operation_id is None:
|
281 |
operation_id = f"delete_{int(time.time())}"
|
282 |
-
|
283 |
logger.info(f"[{operation_id}] Deleting record from {table}")
|
284 |
-
|
285 |
# Prepare the SQL
|
286 |
sql = f"DELETE FROM {table} WHERE {condition}"
|
287 |
-
|
288 |
try:
|
289 |
result = execute_query(sql, condition_params, operation_id)
|
290 |
-
|
291 |
# Check for errors
|
292 |
if "results" in result and len(result["results"]) > 0:
|
293 |
if result["results"][0]["type"] == "error":
|
294 |
error_msg = result["results"][0]["error"]["message"]
|
295 |
logger.error(f"[{operation_id}] Delete error: {error_msg}")
|
296 |
return False
|
297 |
-
|
298 |
# Check affected rows
|
299 |
affected_rows = result["results"][0]["response"]["result"]["affected_row_count"]
|
300 |
logger.info(f"[{operation_id}] Deleted {affected_rows} rows")
|
301 |
return affected_rows > 0
|
302 |
-
|
303 |
return False
|
304 |
except Exception as e:
|
305 |
logger.error(f"[{operation_id}] Error deleting record: {str(e)}")
|
306 |
return False
|
307 |
|
308 |
-
def select_records(table: str, columns: List[str] = None, condition: str = None,
|
309 |
-
condition_params: List[Dict[str, Any]] = None, limit: int = None,
|
310 |
offset: int = None, order_by: str = None, operation_id: str = None) -> List[Dict[str, Any]]:
|
311 |
"""
|
312 |
Select records from a table.
|
313 |
-
|
314 |
Args:
|
315 |
table (str): The table to select from.
|
316 |
columns (List[str], optional): The columns to select. Defaults to None (all columns).
|
@@ -320,47 +311,47 @@ def select_records(table: str, columns: List[str] = None, condition: str = None,
|
|
320 |
offset (int, optional): The number of records to skip. Defaults to None.
|
321 |
order_by (str, optional): The order by clause. Defaults to None.
|
322 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
323 |
-
|
324 |
Returns:
|
325 |
List[Dict[str, Any]]: The selected records.
|
326 |
"""
|
327 |
if operation_id is None:
|
328 |
operation_id = f"select_{int(time.time())}"
|
329 |
-
|
330 |
-
logger.
|
331 |
-
|
332 |
# Prepare the SQL
|
333 |
cols = "*" if not columns else ", ".join(columns)
|
334 |
sql = f"SELECT {cols} FROM {table}"
|
335 |
-
|
336 |
if condition:
|
337 |
sql += f" WHERE {condition}"
|
338 |
-
|
339 |
if order_by:
|
340 |
sql += f" ORDER BY {order_by}"
|
341 |
-
|
342 |
if limit:
|
343 |
sql += f" LIMIT {limit}"
|
344 |
-
|
345 |
if offset:
|
346 |
sql += f" OFFSET {offset}"
|
347 |
-
|
348 |
try:
|
349 |
result = execute_query(sql, condition_params, operation_id)
|
350 |
-
|
351 |
# Check for errors
|
352 |
if "results" in result and len(result["results"]) > 0:
|
353 |
if result["results"][0]["type"] == "error":
|
354 |
error_msg = result["results"][0]["error"]["message"]
|
355 |
logger.error(f"[{operation_id}] Select error: {error_msg}")
|
356 |
return []
|
357 |
-
|
358 |
# Extract the records
|
359 |
response = result["results"][0]["response"]
|
360 |
if "result" in response and "rows" in response["result"]:
|
361 |
rows = response["result"]["rows"]
|
362 |
cols = response["result"]["cols"]
|
363 |
-
|
364 |
# Convert rows to dictionaries
|
365 |
records = []
|
366 |
for row in rows:
|
@@ -368,7 +359,7 @@ def select_records(table: str, columns: List[str] = None, condition: str = None,
|
|
368 |
for i, col in enumerate(cols):
|
369 |
col_name = col["name"]
|
370 |
value = row[i]["value"]
|
371 |
-
|
372 |
# Convert value based on type
|
373 |
if row[i]["type"] == "integer":
|
374 |
value = int(value)
|
@@ -376,15 +367,15 @@ def select_records(table: str, columns: List[str] = None, condition: str = None,
|
|
376 |
value = float(value)
|
377 |
elif row[i]["type"] == "null":
|
378 |
value = None
|
379 |
-
|
380 |
record[col_name] = value
|
381 |
-
|
382 |
records.append(record)
|
383 |
-
|
384 |
-
logger.
|
385 |
return records
|
386 |
-
|
387 |
-
logger.
|
388 |
return []
|
389 |
except Exception as e:
|
390 |
logger.error(f"[{operation_id}] Error selecting records: {str(e)}")
|
@@ -393,70 +384,70 @@ def select_records(table: str, columns: List[str] = None, condition: str = None,
|
|
393 |
def get_record_by_id(table: str, id: int, columns: List[str] = None, operation_id: str = None) -> Optional[Dict[str, Any]]:
|
394 |
"""
|
395 |
Get a record by ID.
|
396 |
-
|
397 |
Args:
|
398 |
table (str): The table to select from.
|
399 |
id (int): The ID of the record.
|
400 |
columns (List[str], optional): The columns to select. Defaults to None (all columns).
|
401 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
402 |
-
|
403 |
Returns:
|
404 |
Optional[Dict[str, Any]]: The record, or None if not found.
|
405 |
"""
|
406 |
if operation_id is None:
|
407 |
operation_id = f"get_by_id_{int(time.time())}"
|
408 |
-
|
409 |
condition = "id = ?"
|
410 |
condition_params = [{"type": "integer", "value": str(id)}]
|
411 |
-
|
412 |
records = select_records(table, columns, condition, condition_params, limit=1, operation_id=operation_id)
|
413 |
-
|
414 |
if records:
|
415 |
return records[0]
|
416 |
-
|
417 |
return None
|
418 |
|
419 |
def count_records(table: str, condition: str = None, condition_params: List[Dict[str, Any]] = None, operation_id: str = None) -> int:
|
420 |
"""
|
421 |
Count records in a table.
|
422 |
-
|
423 |
Args:
|
424 |
table (str): The table to count records in.
|
425 |
condition (str, optional): The condition for the count. Defaults to None.
|
426 |
condition_params (List[Dict[str, Any]], optional): The parameters for the condition. Defaults to None.
|
427 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
428 |
-
|
429 |
Returns:
|
430 |
int: The number of records.
|
431 |
"""
|
432 |
if operation_id is None:
|
433 |
operation_id = f"count_{int(time.time())}"
|
434 |
-
|
435 |
logger.info(f"[{operation_id}] Counting records in {table}")
|
436 |
-
|
437 |
# Prepare the SQL
|
438 |
sql = f"SELECT COUNT(*) FROM {table}"
|
439 |
-
|
440 |
if condition:
|
441 |
sql += f" WHERE {condition}"
|
442 |
-
|
443 |
try:
|
444 |
result = execute_query(sql, condition_params, operation_id)
|
445 |
-
|
446 |
# Check for errors
|
447 |
if "results" in result and len(result["results"]) > 0:
|
448 |
if result["results"][0]["type"] == "error":
|
449 |
error_msg = result["results"][0]["error"]["message"]
|
450 |
logger.error(f"[{operation_id}] Count error: {error_msg}")
|
451 |
return 0
|
452 |
-
|
453 |
# Extract the count
|
454 |
response = result["results"][0]["response"]
|
455 |
if "result" in response and "rows" in response["result"] and response["result"]["rows"]:
|
456 |
count = int(response["result"]["rows"][0][0]["value"])
|
457 |
logger.info(f"[{operation_id}] Counted {count} records")
|
458 |
return count
|
459 |
-
|
460 |
logger.warning(f"[{operation_id}] Count failed")
|
461 |
return 0
|
462 |
except Exception as e:
|
@@ -466,13 +457,13 @@ def count_records(table: str, condition: str = None, condition_params: List[Dict
|
|
466 |
def record_exists(table: str, condition: str, condition_params: List[Dict[str, Any]], operation_id: str = None) -> bool:
|
467 |
"""
|
468 |
Check if a record exists in a table.
|
469 |
-
|
470 |
Args:
|
471 |
table (str): The table to check.
|
472 |
condition (str): The condition for the check.
|
473 |
condition_params (List[Dict[str, Any]]): The parameters for the condition.
|
474 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
475 |
-
|
476 |
Returns:
|
477 |
bool: True if the record exists, False otherwise.
|
478 |
"""
|
@@ -482,36 +473,36 @@ def record_exists(table: str, condition: str, condition_params: List[Dict[str, A
|
|
482 |
def create_table_if_not_exists(table: str, schema: str, operation_id: str = None) -> bool:
|
483 |
"""
|
484 |
Create a table if it doesn't exist.
|
485 |
-
|
486 |
Args:
|
487 |
table (str): The table to create.
|
488 |
schema (str): The schema for the table.
|
489 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
490 |
-
|
491 |
Returns:
|
492 |
bool: True if the table was created or already exists, False otherwise.
|
493 |
"""
|
494 |
if operation_id is None:
|
495 |
operation_id = f"create_table_{int(time.time())}"
|
496 |
-
|
497 |
logger.info(f"[{operation_id}] Creating table {table} if not exists")
|
498 |
-
|
499 |
# Prepare the SQL
|
500 |
sql = f"CREATE TABLE IF NOT EXISTS {table} ({schema})"
|
501 |
-
|
502 |
try:
|
503 |
result = execute_query(sql, operation_id=operation_id)
|
504 |
-
|
505 |
# Check for errors
|
506 |
if "results" in result and len(result["results"]) > 0:
|
507 |
if result["results"][0]["type"] == "error":
|
508 |
error_msg = result["results"][0]["error"]["message"]
|
509 |
logger.error(f"[{operation_id}] Create table error: {error_msg}")
|
510 |
return False
|
511 |
-
|
512 |
logger.info(f"[{operation_id}] Table {table} created or already exists")
|
513 |
return True
|
514 |
-
|
515 |
return False
|
516 |
except Exception as e:
|
517 |
logger.error(f"[{operation_id}] Error creating table: {str(e)}")
|
@@ -520,22 +511,22 @@ def create_table_if_not_exists(table: str, schema: str, operation_id: str = None
|
|
520 |
def execute_transaction(queries: List[Tuple[str, List[Dict[str, Any]]]], operation_id: str = None) -> bool:
|
521 |
"""
|
522 |
Execute a transaction with multiple queries.
|
523 |
-
|
524 |
Args:
|
525 |
queries (List[Tuple[str, List[Dict[str, Any]]]]): The queries to execute.
|
526 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
527 |
-
|
528 |
Returns:
|
529 |
bool: True if the transaction succeeded, False otherwise.
|
530 |
"""
|
531 |
if operation_id is None:
|
532 |
operation_id = f"transaction_{int(time.time())}"
|
533 |
-
|
534 |
logger.info(f"[{operation_id}] Executing transaction with {len(queries)} queries")
|
535 |
-
|
536 |
# Prepare the pipeline
|
537 |
requests = [{"type": "execute", "stmt": {"sql": "BEGIN"}}]
|
538 |
-
|
539 |
for sql, params in queries:
|
540 |
requests.append({
|
541 |
"type": "execute",
|
@@ -544,35 +535,35 @@ def execute_transaction(queries: List[Tuple[str, List[Dict[str, Any]]]], operati
|
|
544 |
"args": params or []
|
545 |
}
|
546 |
})
|
547 |
-
|
548 |
requests.append({"type": "execute", "stmt": {"sql": "COMMIT"}})
|
549 |
-
|
550 |
try:
|
551 |
result = execute_pipeline(requests, operation_id)
|
552 |
-
|
553 |
# Check for errors
|
554 |
for i, res in enumerate(result.get("results", [])):
|
555 |
if res.get("type") == "error":
|
556 |
error_msg = res.get("error", {}).get("message", "Unknown error")
|
557 |
logger.error(f"[{operation_id}] Transaction error in query {i}: {error_msg}")
|
558 |
-
|
559 |
# Try to rollback
|
560 |
try:
|
561 |
execute_query("ROLLBACK", operation_id=f"{operation_id}_rollback")
|
562 |
except:
|
563 |
pass
|
564 |
-
|
565 |
return False
|
566 |
-
|
567 |
logger.info(f"[{operation_id}] Transaction executed successfully")
|
568 |
return True
|
569 |
except Exception as e:
|
570 |
logger.error(f"[{operation_id}] Error executing transaction: {str(e)}")
|
571 |
-
|
572 |
# Try to rollback
|
573 |
try:
|
574 |
execute_query("ROLLBACK", operation_id=f"{operation_id}_rollback")
|
575 |
except:
|
576 |
pass
|
577 |
-
|
578 |
return False
|
|
|
5 |
|
6 |
import os
|
7 |
import logging
|
|
|
8 |
import time
|
9 |
+
import requests as req
|
10 |
+
from typing import List, Dict, Any, Optional, Tuple
|
11 |
|
12 |
# Configure logging
|
13 |
logger = logging.getLogger("auth-server")
|
|
|
15 |
def get_http_url() -> Tuple[str, str]:
|
16 |
"""
|
17 |
Get the HTTP URL and auth token for the Turso database.
|
18 |
+
|
19 |
Returns:
|
20 |
Tuple[str, str]: The HTTP URL and auth token.
|
21 |
"""
|
22 |
# Extract the database URL and auth token
|
23 |
db_url = os.getenv("TURSO_DATABASE_URL", "")
|
24 |
auth_token = os.getenv("TURSO_AUTH_TOKEN", "")
|
25 |
+
|
26 |
# Convert URL from libsql:// to https://
|
27 |
if db_url.startswith("libsql://"):
|
28 |
http_url = db_url.replace("libsql://", "https://")
|
29 |
else:
|
30 |
http_url = db_url
|
31 |
+
|
32 |
# Ensure the URL doesn't have a trailing slash
|
33 |
http_url = http_url.rstrip('/')
|
34 |
+
|
35 |
return http_url, auth_token
|
36 |
|
37 |
def get_headers(auth_token: str) -> Dict[str, str]:
|
38 |
"""
|
39 |
Get the headers for the HTTP request.
|
40 |
+
|
41 |
Args:
|
42 |
auth_token (str): The authentication token.
|
43 |
+
|
44 |
Returns:
|
45 |
Dict[str, str]: The headers for the HTTP request.
|
46 |
"""
|
|
|
52 |
def execute_query(sql: str, params: List[Dict[str, Any]] = None, operation_id: str = None) -> Dict[str, Any]:
|
53 |
"""
|
54 |
Execute a SQL query using the HTTP API.
|
55 |
+
|
56 |
Args:
|
57 |
sql (str): The SQL query to execute.
|
58 |
params (List[Dict[str, Any]], optional): The parameters for the query. Defaults to None.
|
59 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
60 |
+
|
61 |
Returns:
|
62 |
Dict[str, Any]: The response from the database.
|
63 |
"""
|
64 |
if operation_id is None:
|
65 |
operation_id = f"query_{int(time.time())}"
|
66 |
+
|
67 |
+
# Only log at debug level for routine operations
|
68 |
+
logger.debug(f"[{operation_id}] Executing query: {sql}")
|
69 |
+
|
70 |
http_url, auth_token = get_http_url()
|
71 |
headers = get_headers(auth_token)
|
72 |
+
|
73 |
# Prepare the query
|
74 |
query = {
|
75 |
"requests": [
|
|
|
83 |
{"type": "close"}
|
84 |
]
|
85 |
}
|
86 |
+
|
87 |
# Send the request
|
88 |
try:
|
89 |
+
response = req.post(f"{http_url}/v2/pipeline", headers=headers, json=query)
|
90 |
response.raise_for_status()
|
91 |
result = response.json()
|
92 |
+
logger.debug(f"[{operation_id}] Query executed successfully")
|
93 |
return result
|
94 |
except Exception as e:
|
95 |
logger.error(f"[{operation_id}] Error executing query: {str(e)}")
|
96 |
raise
|
97 |
|
98 |
+
def execute_pipeline(pipeline_requests: List[Dict[str, Any]], operation_id: str = None) -> Dict[str, Any]:
|
99 |
"""
|
100 |
Execute a pipeline of SQL queries using the HTTP API.
|
101 |
+
|
102 |
Args:
|
103 |
+
pipeline_requests (List[Dict[str, Any]]): The requests to execute.
|
104 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
105 |
+
|
106 |
Returns:
|
107 |
Dict[str, Any]: The response from the database.
|
108 |
"""
|
109 |
if operation_id is None:
|
110 |
operation_id = f"pipeline_{int(time.time())}"
|
111 |
+
|
112 |
+
logger.debug(f"[{operation_id}] Executing pipeline with {len(pipeline_requests)} requests")
|
113 |
+
|
114 |
http_url, auth_token = get_http_url()
|
115 |
headers = get_headers(auth_token)
|
116 |
+
|
117 |
# Prepare the pipeline
|
118 |
pipeline = {
|
119 |
+
"requests": pipeline_requests + [{"type": "close"}]
|
120 |
}
|
121 |
+
|
122 |
# Send the request
|
123 |
try:
|
124 |
+
response = req.post(f"{http_url}/v2/pipeline", headers=headers, json=pipeline)
|
125 |
response.raise_for_status()
|
126 |
result = response.json()
|
127 |
+
logger.debug(f"[{operation_id}] Pipeline executed successfully")
|
128 |
return result
|
129 |
except Exception as e:
|
130 |
logger.error(f"[{operation_id}] Error executing pipeline: {str(e)}")
|
|
|
133 |
def insert_record(table: str, data: Dict[str, Any], operation_id: str = None) -> Optional[int]:
|
134 |
"""
|
135 |
Insert a record into a table.
|
136 |
+
|
137 |
Args:
|
138 |
table (str): The table to insert into.
|
139 |
data (Dict[str, Any]): The data to insert.
|
140 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
141 |
+
|
142 |
Returns:
|
143 |
Optional[int]: The ID of the inserted record, or None if the insert failed.
|
144 |
"""
|
145 |
if operation_id is None:
|
146 |
operation_id = f"insert_{int(time.time())}"
|
147 |
+
|
148 |
+
logger.debug(f"[{operation_id}] Inserting record into {table}")
|
149 |
+
|
150 |
# Prepare the SQL and parameters
|
151 |
columns = list(data.keys())
|
152 |
placeholders = ["?"] * len(columns)
|
153 |
sql = f"INSERT INTO {table} ({', '.join(columns)}) VALUES ({', '.join(placeholders)})"
|
154 |
+
|
155 |
params = []
|
156 |
for key, value in data.items():
|
157 |
if isinstance(value, str):
|
|
|
164 |
params.append({"type": "null", "value": None})
|
165 |
else:
|
166 |
params.append({"type": "text", "value": str(value)})
|
167 |
+
|
168 |
+
# Execute the query directly instead of using pipeline
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
169 |
try:
|
170 |
+
# Direct query execution is more reliable
|
171 |
+
result = execute_query(sql, params, operation_id)
|
172 |
+
|
173 |
+
# Check for errors
|
174 |
if "results" in result and len(result["results"]) > 0:
|
175 |
if result["results"][0]["type"] == "error":
|
176 |
error_msg = result["results"][0]["error"]["message"]
|
177 |
logger.error(f"[{operation_id}] Insert error: {error_msg}")
|
178 |
return None
|
179 |
+
|
180 |
+
# Try to get the last inserted ID with a separate query
|
181 |
+
id_result = execute_query("SELECT last_insert_rowid()", operation_id=f"{operation_id}_get_id")
|
182 |
+
|
183 |
last_id = None
|
184 |
+
if "results" in id_result and len(id_result["results"]) > 0:
|
185 |
+
if id_result["results"][0]["type"] == "ok":
|
186 |
+
rows = id_result["results"][0]["response"]["result"]["rows"]
|
187 |
if rows and len(rows) > 0:
|
188 |
last_id = int(rows[0][0]["value"])
|
189 |
logger.info(f"[{operation_id}] Record inserted with ID: {last_id}")
|
190 |
return last_id
|
191 |
+
|
192 |
+
# If we can't get the ID, try to find it by other means
|
193 |
logger.warning(f"[{operation_id}] Insert succeeded but couldn't get ID")
|
194 |
return None
|
195 |
except Exception as e:
|
|
|
199 |
def update_record(table: str, data: Dict[str, Any], condition: str, condition_params: List[Dict[str, Any]], operation_id: str = None) -> bool:
|
200 |
"""
|
201 |
Update a record in a table.
|
202 |
+
|
203 |
Args:
|
204 |
table (str): The table to update.
|
205 |
data (Dict[str, Any]): The data to update.
|
206 |
condition (str): The condition for the update (e.g., "id = ?").
|
207 |
condition_params (List[Dict[str, Any]]): The parameters for the condition.
|
208 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
209 |
+
|
210 |
Returns:
|
211 |
bool: True if the update succeeded, False otherwise.
|
212 |
"""
|
213 |
if operation_id is None:
|
214 |
operation_id = f"update_{int(time.time())}"
|
215 |
+
|
216 |
+
logger.debug(f"[{operation_id}] Updating record in {table}")
|
217 |
+
|
218 |
# Prepare the SQL and parameters
|
219 |
set_clauses = [f"{key} = ?" for key in data.keys()]
|
220 |
sql = f"UPDATE {table} SET {', '.join(set_clauses)} WHERE {condition}"
|
221 |
+
|
222 |
params = []
|
223 |
for key, value in data.items():
|
224 |
if isinstance(value, str):
|
|
|
231 |
params.append({"type": "null", "value": None})
|
232 |
else:
|
233 |
params.append({"type": "text", "value": str(value)})
|
234 |
+
|
235 |
# Add condition parameters
|
236 |
params.extend(condition_params)
|
237 |
+
|
238 |
try:
|
239 |
result = execute_query(sql, params, operation_id)
|
240 |
+
|
241 |
# Check for errors
|
242 |
if "results" in result and len(result["results"]) > 0:
|
243 |
if result["results"][0]["type"] == "error":
|
244 |
error_msg = result["results"][0]["error"]["message"]
|
245 |
logger.error(f"[{operation_id}] Update error: {error_msg}")
|
246 |
return False
|
247 |
+
|
248 |
# Check affected rows
|
249 |
affected_rows = result["results"][0]["response"]["result"]["affected_row_count"]
|
250 |
+
logger.debug(f"[{operation_id}] Updated {affected_rows} rows")
|
251 |
return affected_rows > 0
|
252 |
+
|
253 |
return False
|
254 |
except Exception as e:
|
255 |
logger.error(f"[{operation_id}] Error updating record: {str(e)}")
|
|
|
258 |
def delete_record(table: str, condition: str, condition_params: List[Dict[str, Any]], operation_id: str = None) -> bool:
|
259 |
"""
|
260 |
Delete a record from a table.
|
261 |
+
|
262 |
Args:
|
263 |
table (str): The table to delete from.
|
264 |
condition (str): The condition for the delete (e.g., "id = ?").
|
265 |
condition_params (List[Dict[str, Any]]): The parameters for the condition.
|
266 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
267 |
+
|
268 |
Returns:
|
269 |
bool: True if the delete succeeded, False otherwise.
|
270 |
"""
|
271 |
if operation_id is None:
|
272 |
operation_id = f"delete_{int(time.time())}"
|
273 |
+
|
274 |
logger.info(f"[{operation_id}] Deleting record from {table}")
|
275 |
+
|
276 |
# Prepare the SQL
|
277 |
sql = f"DELETE FROM {table} WHERE {condition}"
|
278 |
+
|
279 |
try:
|
280 |
result = execute_query(sql, condition_params, operation_id)
|
281 |
+
|
282 |
# Check for errors
|
283 |
if "results" in result and len(result["results"]) > 0:
|
284 |
if result["results"][0]["type"] == "error":
|
285 |
error_msg = result["results"][0]["error"]["message"]
|
286 |
logger.error(f"[{operation_id}] Delete error: {error_msg}")
|
287 |
return False
|
288 |
+
|
289 |
# Check affected rows
|
290 |
affected_rows = result["results"][0]["response"]["result"]["affected_row_count"]
|
291 |
logger.info(f"[{operation_id}] Deleted {affected_rows} rows")
|
292 |
return affected_rows > 0
|
293 |
+
|
294 |
return False
|
295 |
except Exception as e:
|
296 |
logger.error(f"[{operation_id}] Error deleting record: {str(e)}")
|
297 |
return False
|
298 |
|
299 |
+
def select_records(table: str, columns: List[str] = None, condition: str = None,
|
300 |
+
condition_params: List[Dict[str, Any]] = None, limit: int = None,
|
301 |
offset: int = None, order_by: str = None, operation_id: str = None) -> List[Dict[str, Any]]:
|
302 |
"""
|
303 |
Select records from a table.
|
304 |
+
|
305 |
Args:
|
306 |
table (str): The table to select from.
|
307 |
columns (List[str], optional): The columns to select. Defaults to None (all columns).
|
|
|
311 |
offset (int, optional): The number of records to skip. Defaults to None.
|
312 |
order_by (str, optional): The order by clause. Defaults to None.
|
313 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
314 |
+
|
315 |
Returns:
|
316 |
List[Dict[str, Any]]: The selected records.
|
317 |
"""
|
318 |
if operation_id is None:
|
319 |
operation_id = f"select_{int(time.time())}"
|
320 |
+
|
321 |
+
logger.debug(f"[{operation_id}] Selecting records from {table}")
|
322 |
+
|
323 |
# Prepare the SQL
|
324 |
cols = "*" if not columns else ", ".join(columns)
|
325 |
sql = f"SELECT {cols} FROM {table}"
|
326 |
+
|
327 |
if condition:
|
328 |
sql += f" WHERE {condition}"
|
329 |
+
|
330 |
if order_by:
|
331 |
sql += f" ORDER BY {order_by}"
|
332 |
+
|
333 |
if limit:
|
334 |
sql += f" LIMIT {limit}"
|
335 |
+
|
336 |
if offset:
|
337 |
sql += f" OFFSET {offset}"
|
338 |
+
|
339 |
try:
|
340 |
result = execute_query(sql, condition_params, operation_id)
|
341 |
+
|
342 |
# Check for errors
|
343 |
if "results" in result and len(result["results"]) > 0:
|
344 |
if result["results"][0]["type"] == "error":
|
345 |
error_msg = result["results"][0]["error"]["message"]
|
346 |
logger.error(f"[{operation_id}] Select error: {error_msg}")
|
347 |
return []
|
348 |
+
|
349 |
# Extract the records
|
350 |
response = result["results"][0]["response"]
|
351 |
if "result" in response and "rows" in response["result"]:
|
352 |
rows = response["result"]["rows"]
|
353 |
cols = response["result"]["cols"]
|
354 |
+
|
355 |
# Convert rows to dictionaries
|
356 |
records = []
|
357 |
for row in rows:
|
|
|
359 |
for i, col in enumerate(cols):
|
360 |
col_name = col["name"]
|
361 |
value = row[i]["value"]
|
362 |
+
|
363 |
# Convert value based on type
|
364 |
if row[i]["type"] == "integer":
|
365 |
value = int(value)
|
|
|
367 |
value = float(value)
|
368 |
elif row[i]["type"] == "null":
|
369 |
value = None
|
370 |
+
|
371 |
record[col_name] = value
|
372 |
+
|
373 |
records.append(record)
|
374 |
+
|
375 |
+
logger.debug(f"[{operation_id}] Selected {len(records)} records")
|
376 |
return records
|
377 |
+
|
378 |
+
logger.debug(f"[{operation_id}] No records found")
|
379 |
return []
|
380 |
except Exception as e:
|
381 |
logger.error(f"[{operation_id}] Error selecting records: {str(e)}")
|
|
|
384 |
def get_record_by_id(table: str, id: int, columns: List[str] = None, operation_id: str = None) -> Optional[Dict[str, Any]]:
|
385 |
"""
|
386 |
Get a record by ID.
|
387 |
+
|
388 |
Args:
|
389 |
table (str): The table to select from.
|
390 |
id (int): The ID of the record.
|
391 |
columns (List[str], optional): The columns to select. Defaults to None (all columns).
|
392 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
393 |
+
|
394 |
Returns:
|
395 |
Optional[Dict[str, Any]]: The record, or None if not found.
|
396 |
"""
|
397 |
if operation_id is None:
|
398 |
operation_id = f"get_by_id_{int(time.time())}"
|
399 |
+
|
400 |
condition = "id = ?"
|
401 |
condition_params = [{"type": "integer", "value": str(id)}]
|
402 |
+
|
403 |
records = select_records(table, columns, condition, condition_params, limit=1, operation_id=operation_id)
|
404 |
+
|
405 |
if records:
|
406 |
return records[0]
|
407 |
+
|
408 |
return None
|
409 |
|
410 |
def count_records(table: str, condition: str = None, condition_params: List[Dict[str, Any]] = None, operation_id: str = None) -> int:
|
411 |
"""
|
412 |
Count records in a table.
|
413 |
+
|
414 |
Args:
|
415 |
table (str): The table to count records in.
|
416 |
condition (str, optional): The condition for the count. Defaults to None.
|
417 |
condition_params (List[Dict[str, Any]], optional): The parameters for the condition. Defaults to None.
|
418 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
419 |
+
|
420 |
Returns:
|
421 |
int: The number of records.
|
422 |
"""
|
423 |
if operation_id is None:
|
424 |
operation_id = f"count_{int(time.time())}"
|
425 |
+
|
426 |
logger.info(f"[{operation_id}] Counting records in {table}")
|
427 |
+
|
428 |
# Prepare the SQL
|
429 |
sql = f"SELECT COUNT(*) FROM {table}"
|
430 |
+
|
431 |
if condition:
|
432 |
sql += f" WHERE {condition}"
|
433 |
+
|
434 |
try:
|
435 |
result = execute_query(sql, condition_params, operation_id)
|
436 |
+
|
437 |
# Check for errors
|
438 |
if "results" in result and len(result["results"]) > 0:
|
439 |
if result["results"][0]["type"] == "error":
|
440 |
error_msg = result["results"][0]["error"]["message"]
|
441 |
logger.error(f"[{operation_id}] Count error: {error_msg}")
|
442 |
return 0
|
443 |
+
|
444 |
# Extract the count
|
445 |
response = result["results"][0]["response"]
|
446 |
if "result" in response and "rows" in response["result"] and response["result"]["rows"]:
|
447 |
count = int(response["result"]["rows"][0][0]["value"])
|
448 |
logger.info(f"[{operation_id}] Counted {count} records")
|
449 |
return count
|
450 |
+
|
451 |
logger.warning(f"[{operation_id}] Count failed")
|
452 |
return 0
|
453 |
except Exception as e:
|
|
|
457 |
def record_exists(table: str, condition: str, condition_params: List[Dict[str, Any]], operation_id: str = None) -> bool:
|
458 |
"""
|
459 |
Check if a record exists in a table.
|
460 |
+
|
461 |
Args:
|
462 |
table (str): The table to check.
|
463 |
condition (str): The condition for the check.
|
464 |
condition_params (List[Dict[str, Any]]): The parameters for the condition.
|
465 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
466 |
+
|
467 |
Returns:
|
468 |
bool: True if the record exists, False otherwise.
|
469 |
"""
|
|
|
473 |
def create_table_if_not_exists(table: str, schema: str, operation_id: str = None) -> bool:
|
474 |
"""
|
475 |
Create a table if it doesn't exist.
|
476 |
+
|
477 |
Args:
|
478 |
table (str): The table to create.
|
479 |
schema (str): The schema for the table.
|
480 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
481 |
+
|
482 |
Returns:
|
483 |
bool: True if the table was created or already exists, False otherwise.
|
484 |
"""
|
485 |
if operation_id is None:
|
486 |
operation_id = f"create_table_{int(time.time())}"
|
487 |
+
|
488 |
logger.info(f"[{operation_id}] Creating table {table} if not exists")
|
489 |
+
|
490 |
# Prepare the SQL
|
491 |
sql = f"CREATE TABLE IF NOT EXISTS {table} ({schema})"
|
492 |
+
|
493 |
try:
|
494 |
result = execute_query(sql, operation_id=operation_id)
|
495 |
+
|
496 |
# Check for errors
|
497 |
if "results" in result and len(result["results"]) > 0:
|
498 |
if result["results"][0]["type"] == "error":
|
499 |
error_msg = result["results"][0]["error"]["message"]
|
500 |
logger.error(f"[{operation_id}] Create table error: {error_msg}")
|
501 |
return False
|
502 |
+
|
503 |
logger.info(f"[{operation_id}] Table {table} created or already exists")
|
504 |
return True
|
505 |
+
|
506 |
return False
|
507 |
except Exception as e:
|
508 |
logger.error(f"[{operation_id}] Error creating table: {str(e)}")
|
|
|
511 |
def execute_transaction(queries: List[Tuple[str, List[Dict[str, Any]]]], operation_id: str = None) -> bool:
|
512 |
"""
|
513 |
Execute a transaction with multiple queries.
|
514 |
+
|
515 |
Args:
|
516 |
queries (List[Tuple[str, List[Dict[str, Any]]]]): The queries to execute.
|
517 |
operation_id (str, optional): A unique identifier for the operation. Defaults to None.
|
518 |
+
|
519 |
Returns:
|
520 |
bool: True if the transaction succeeded, False otherwise.
|
521 |
"""
|
522 |
if operation_id is None:
|
523 |
operation_id = f"transaction_{int(time.time())}"
|
524 |
+
|
525 |
logger.info(f"[{operation_id}] Executing transaction with {len(queries)} queries")
|
526 |
+
|
527 |
# Prepare the pipeline
|
528 |
requests = [{"type": "execute", "stmt": {"sql": "BEGIN"}}]
|
529 |
+
|
530 |
for sql, params in queries:
|
531 |
requests.append({
|
532 |
"type": "execute",
|
|
|
535 |
"args": params or []
|
536 |
}
|
537 |
})
|
538 |
+
|
539 |
requests.append({"type": "execute", "stmt": {"sql": "COMMIT"}})
|
540 |
+
|
541 |
try:
|
542 |
result = execute_pipeline(requests, operation_id)
|
543 |
+
|
544 |
# Check for errors
|
545 |
for i, res in enumerate(result.get("results", [])):
|
546 |
if res.get("type") == "error":
|
547 |
error_msg = res.get("error", {}).get("message", "Unknown error")
|
548 |
logger.error(f"[{operation_id}] Transaction error in query {i}: {error_msg}")
|
549 |
+
|
550 |
# Try to rollback
|
551 |
try:
|
552 |
execute_query("ROLLBACK", operation_id=f"{operation_id}_rollback")
|
553 |
except:
|
554 |
pass
|
555 |
+
|
556 |
return False
|
557 |
+
|
558 |
logger.info(f"[{operation_id}] Transaction executed successfully")
|
559 |
return True
|
560 |
except Exception as e:
|
561 |
logger.error(f"[{operation_id}] Error executing transaction: {str(e)}")
|
562 |
+
|
563 |
# Try to rollback
|
564 |
try:
|
565 |
execute_query("ROLLBACK", operation_id=f"{operation_id}_rollback")
|
566 |
except:
|
567 |
pass
|
568 |
+
|
569 |
return False
|
main.py
CHANGED
@@ -19,12 +19,16 @@ from app.utils import db_http
|
|
19 |
|
20 |
# Configure logging
|
21 |
logging.basicConfig(
|
22 |
-
level=logging.INFO
|
23 |
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
24 |
handlers=[logging.StreamHandler(sys.stdout)]
|
25 |
)
|
26 |
logger = logging.getLogger("auth-server")
|
27 |
|
|
|
|
|
|
|
|
|
28 |
# Import database libraries
|
29 |
try:
|
30 |
# First try the recommended libsql-experimental package
|
|
|
19 |
|
20 |
# Configure logging
|
21 |
logging.basicConfig(
|
22 |
+
level=logging.WARNING, # Changed from INFO to WARNING to reduce verbosity
|
23 |
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
24 |
handlers=[logging.StreamHandler(sys.stdout)]
|
25 |
)
|
26 |
logger = logging.getLogger("auth-server")
|
27 |
|
28 |
+
# Set specific loggers to different levels as needed
|
29 |
+
logging.getLogger("uvicorn").setLevel(logging.WARNING)
|
30 |
+
logging.getLogger("fastapi").setLevel(logging.WARNING)
|
31 |
+
|
32 |
# Import database libraries
|
33 |
try:
|
34 |
# First try the recommended libsql-experimental package
|