Spaces:
Sleeping
Sleeping
Upload 5 files
Browse files- flask_app_hf.py +84 -85
flask_app_hf.py
CHANGED
|
@@ -377,7 +377,8 @@ def system_status():
|
|
| 377 |
'groq': groq_client is not None,
|
| 378 |
'gemini': genai_client is not None
|
| 379 |
},
|
| 380 |
-
'storage': '
|
|
|
|
| 381 |
'auth': 'disabled'
|
| 382 |
})
|
| 383 |
|
|
@@ -388,30 +389,26 @@ def admin_system_health():
|
|
| 388 |
"""Return simple system health info using file-based storage (no external deps)."""
|
| 389 |
try:
|
| 390 |
import shutil
|
| 391 |
-
|
| 392 |
schema = {}
|
| 393 |
total_rows = 0
|
| 394 |
db_size = 0
|
| 395 |
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
db_size += size
|
| 412 |
-
schema[folder] = {'row_count': row_count, 'size_bytes': size}
|
| 413 |
-
|
| 414 |
-
# disk usage for current filesystem
|
| 415 |
try:
|
| 416 |
du = shutil.disk_usage('.')
|
| 417 |
disk = {
|
|
@@ -424,10 +421,10 @@ def admin_system_health():
|
|
| 424 |
disk = {}
|
| 425 |
|
| 426 |
health = {
|
| 427 |
-
'memory':
|
| 428 |
'disk': disk,
|
| 429 |
'database': {
|
| 430 |
-
'
|
| 431 |
'total_rows': total_rows,
|
| 432 |
'estimated_size_bytes': db_size,
|
| 433 |
'tables': schema
|
|
@@ -445,36 +442,33 @@ def admin_system_health():
|
|
| 445 |
def admin_database_schema():
|
| 446 |
"""Return a simple schema overview derived from hf_data folders."""
|
| 447 |
try:
|
| 448 |
-
|
| 449 |
schema = {}
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 456 |
cols = []
|
| 457 |
-
if files:
|
| 458 |
-
sample_path = os.path.join(folder_path, files[0])
|
| 459 |
-
try:
|
| 460 |
-
with open(sample_path, 'r', encoding='utf-8') as fh:
|
| 461 |
-
data = json.load(fh)
|
| 462 |
-
if isinstance(data, list) and data:
|
| 463 |
-
sample = data[0]
|
| 464 |
-
elif isinstance(data, dict):
|
| 465 |
-
sample = data
|
| 466 |
-
else:
|
| 467 |
-
sample = {}
|
| 468 |
-
cols = [{'name': k, 'type': type(v).__name__} for k, v in (sample or {}).items()]
|
| 469 |
-
except Exception:
|
| 470 |
-
cols = []
|
| 471 |
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
else:
|
| 477 |
-
schema = {}
|
| 478 |
|
| 479 |
return jsonify({'success': True, 'schema': schema})
|
| 480 |
except Exception as e:
|
|
@@ -502,21 +496,12 @@ def admin_list_users():
|
|
| 502 |
flashcards = len(load_user_data(uid, 'flashcards'))
|
| 503 |
conversations = len(load_user_data(uid, 'conversations'))
|
| 504 |
analytics = len(load_user_data(uid, 'analytics'))
|
| 505 |
-
|
| 506 |
-
# estimate created_at by file mtime if available
|
| 507 |
created_at = None
|
| 508 |
try:
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
os.path.join(DATA_ROOT, 'analytics', f"{uid}.json")
|
| 513 |
-
]
|
| 514 |
-
mtimes = []
|
| 515 |
-
for p in path_candidates:
|
| 516 |
-
if os.path.exists(p):
|
| 517 |
-
mtimes.append(os.path.getmtime(p))
|
| 518 |
-
if mtimes:
|
| 519 |
-
created_at = datetime.fromtimestamp(min(mtimes)).isoformat()
|
| 520 |
except Exception:
|
| 521 |
created_at = None
|
| 522 |
|
|
@@ -540,16 +525,22 @@ def admin_user_detail(user_id):
|
|
| 540 |
"""Get detailed info for a user or delete their data (file-based)."""
|
| 541 |
try:
|
| 542 |
if request.method == 'DELETE':
|
| 543 |
-
# remove
|
| 544 |
removed = []
|
| 545 |
-
for folder in ['flashcards', 'conversations', 'analytics']:
|
| 546 |
-
p = os.path.join(DATA_ROOT, folder, f"{user_id}.json")
|
| 547 |
try:
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
|
|
|
|
| 551 |
except Exception as ex:
|
| 552 |
-
print(f"Failed deleting {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 553 |
return jsonify({'success': True, 'deleted': removed})
|
| 554 |
|
| 555 |
# GET -> return analytics, flashcards, conversations
|
|
@@ -648,22 +639,30 @@ def admin_export_all():
|
|
| 648 |
"""Package the entire DATA_ROOT into a zip and send for download."""
|
| 649 |
try:
|
| 650 |
import zipfile
|
| 651 |
-
import tempfile
|
| 652 |
-
|
| 653 |
-
if not os.path.exists(DATA_ROOT):
|
| 654 |
-
return jsonify({'success': False, 'error': 'No data directory found'}), 404
|
| 655 |
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 667 |
except Exception as e:
|
| 668 |
print(f"export all error: {e}")
|
| 669 |
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
| 377 |
'groq': groq_client is not None,
|
| 378 |
'gemini': genai_client is not None
|
| 379 |
},
|
| 380 |
+
'storage': 'in_memory',
|
| 381 |
+
'demo_mode': True,
|
| 382 |
'auth': 'disabled'
|
| 383 |
})
|
| 384 |
|
|
|
|
| 389 |
"""Return simple system health info using file-based storage (no external deps)."""
|
| 390 |
try:
|
| 391 |
import shutil
|
| 392 |
+
# compute simple stats from IN_MEMORY
|
| 393 |
schema = {}
|
| 394 |
total_rows = 0
|
| 395 |
db_size = 0
|
| 396 |
|
| 397 |
+
for table, table_data in IN_MEMORY.items():
|
| 398 |
+
if isinstance(table_data, dict):
|
| 399 |
+
row_count = sum(1 for _ in table_data.keys())
|
| 400 |
+
# approximate size by serializing entries
|
| 401 |
+
size = 0
|
| 402 |
+
for k, v in table_data.items():
|
| 403 |
+
try:
|
| 404 |
+
size += len(json.dumps(v, ensure_ascii=False).encode('utf-8'))
|
| 405 |
+
except Exception:
|
| 406 |
+
pass
|
| 407 |
+
schema[table] = {'row_count': row_count, 'size_bytes': size}
|
| 408 |
+
total_rows += row_count
|
| 409 |
+
db_size += size
|
| 410 |
+
|
| 411 |
+
# disk usage for current filesystem (informational)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 412 |
try:
|
| 413 |
du = shutil.disk_usage('.')
|
| 414 |
disk = {
|
|
|
|
| 421 |
disk = {}
|
| 422 |
|
| 423 |
health = {
|
| 424 |
+
'memory': True,
|
| 425 |
'disk': disk,
|
| 426 |
'database': {
|
| 427 |
+
'storage': 'in_memory',
|
| 428 |
'total_rows': total_rows,
|
| 429 |
'estimated_size_bytes': db_size,
|
| 430 |
'tables': schema
|
|
|
|
| 442 |
def admin_database_schema():
|
| 443 |
"""Return a simple schema overview derived from hf_data folders."""
|
| 444 |
try:
|
| 445 |
+
# derive schema from IN_MEMORY
|
| 446 |
schema = {}
|
| 447 |
+
for table, table_data in IN_MEMORY.items():
|
| 448 |
+
cols = []
|
| 449 |
+
row_count = 0
|
| 450 |
+
if isinstance(table_data, dict):
|
| 451 |
+
row_count = sum(1 for _ in table_data.keys())
|
| 452 |
+
# infer columns/types from first value
|
| 453 |
+
try:
|
| 454 |
+
first_val = None
|
| 455 |
+
for v in table_data.values():
|
| 456 |
+
first_val = v
|
| 457 |
+
break
|
| 458 |
+
sample = None
|
| 459 |
+
if isinstance(first_val, list) and first_val:
|
| 460 |
+
sample = first_val[0]
|
| 461 |
+
elif isinstance(first_val, dict):
|
| 462 |
+
sample = first_val
|
| 463 |
+
if isinstance(sample, dict):
|
| 464 |
+
cols = [{'name': k, 'type': type(v).__name__} for k, v in sample.items()]
|
| 465 |
+
except Exception:
|
| 466 |
cols = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 467 |
|
| 468 |
+
schema[table] = {
|
| 469 |
+
'row_count': row_count,
|
| 470 |
+
'columns': cols
|
| 471 |
+
}
|
|
|
|
|
|
|
| 472 |
|
| 473 |
return jsonify({'success': True, 'schema': schema})
|
| 474 |
except Exception as e:
|
|
|
|
| 496 |
flashcards = len(load_user_data(uid, 'flashcards'))
|
| 497 |
conversations = len(load_user_data(uid, 'conversations'))
|
| 498 |
analytics = len(load_user_data(uid, 'analytics'))
|
| 499 |
+
# created_at from user record if available
|
|
|
|
| 500 |
created_at = None
|
| 501 |
try:
|
| 502 |
+
urec = IN_MEMORY.get('users', {}).get(uid)
|
| 503 |
+
if urec and isinstance(urec, dict):
|
| 504 |
+
created_at = urec.get('created_at')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 505 |
except Exception:
|
| 506 |
created_at = None
|
| 507 |
|
|
|
|
| 525 |
"""Get detailed info for a user or delete their data (file-based)."""
|
| 526 |
try:
|
| 527 |
if request.method == 'DELETE':
|
| 528 |
+
# remove in-memory records across tables
|
| 529 |
removed = []
|
| 530 |
+
for folder in ['flashcards', 'conversations', 'analytics', 'study_plans']:
|
|
|
|
| 531 |
try:
|
| 532 |
+
tbl = IN_MEMORY.get(folder, {})
|
| 533 |
+
if user_id in tbl:
|
| 534 |
+
del tbl[user_id]
|
| 535 |
+
removed.append(f"{folder}/{user_id}")
|
| 536 |
except Exception as ex:
|
| 537 |
+
print(f"Failed deleting in-memory {folder}/{user_id}: {ex}")
|
| 538 |
+
# remove user record
|
| 539 |
+
try:
|
| 540 |
+
if user_id in IN_MEMORY.get('users', {}):
|
| 541 |
+
del IN_MEMORY['users'][user_id]
|
| 542 |
+
except Exception:
|
| 543 |
+
pass
|
| 544 |
return jsonify({'success': True, 'deleted': removed})
|
| 545 |
|
| 546 |
# GET -> return analytics, flashcards, conversations
|
|
|
|
| 639 |
"""Package the entire DATA_ROOT into a zip and send for download."""
|
| 640 |
try:
|
| 641 |
import zipfile
|
|
|
|
|
|
|
|
|
|
|
|
|
| 642 |
|
| 643 |
+
mem_zip = io.BytesIO()
|
| 644 |
+
with zipfile.ZipFile(mem_zip, 'w', compression=zipfile.ZIP_DEFLATED) as zf:
|
| 645 |
+
# dump each table as files
|
| 646 |
+
for table, table_data in IN_MEMORY.items():
|
| 647 |
+
if isinstance(table_data, dict):
|
| 648 |
+
for uid, val in table_data.items():
|
| 649 |
+
try:
|
| 650 |
+
payload = json.dumps(val, ensure_ascii=False, indent=2)
|
| 651 |
+
except Exception:
|
| 652 |
+
payload = str(val)
|
| 653 |
+
arcname = os.path.join(table, f"{uid}.json")
|
| 654 |
+
zf.writestr(arcname, payload)
|
| 655 |
+
else:
|
| 656 |
+
# serialize whole object
|
| 657 |
+
try:
|
| 658 |
+
payload = json.dumps(table_data, ensure_ascii=False, indent=2)
|
| 659 |
+
except Exception:
|
| 660 |
+
payload = str(table_data)
|
| 661 |
+
arcname = f"{table}.json"
|
| 662 |
+
zf.writestr(arcname, payload)
|
| 663 |
+
|
| 664 |
+
mem_zip.seek(0)
|
| 665 |
+
return send_file(mem_zip, mimetype='application/zip', as_attachment=True, download_name='hf_data_export.zip')
|
| 666 |
except Exception as e:
|
| 667 |
print(f"export all error: {e}")
|
| 668 |
return jsonify({'success': False, 'error': str(e)}), 500
|