Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
de099a8
feat: גיבוי אוטומטי ל-Drive ולדיסק מהוובאפ + שיפור שחזור
claude Apr 3, 2026
718219a
fix: שימוש ב-WEBAPP_URL הקיים במקום WEBAPP_BASE_URL ב-drive_auth
claude Apr 3, 2026
68e495d
fix: תיקון 3 באגים מביקורת — race condition, disk backup timestamp, c…
claude Apr 3, 2026
5d05fb1
fix: בדיקת בעלות לפני מחיקת entry ב-restore_progress
claude Apr 3, 2026
fb2b0d6
fix: הוספת upload size limit ל-restore-async endpoint
claude Apr 3, 2026
a2e780a
fix: URL-encode OAuth errors + grace period לתוצאות restore
claude Apr 3, 2026
e981b0f
fix: מניעת מחיקת restore entries שעדיין רצים
claude Apr 3, 2026
82498d6
fix: restore state ב-MongoDB + scheduler atomic claiming
claude Apr 3, 2026
5331473
fix: reset sentinel when validation fails after atomic claim
claude Apr 3, 2026
b2ca1a8
fix: prevent infinite loop in scheduler when backup fails
claude Apr 3, 2026
e32187f
fix: prevent disabled Drive schedule from triggering & restore error …
claude Apr 3, 2026
6cc040a
fix: address 4 review findings (type annotation, restore ok check, up…
claude Apr 3, 2026
8897320
fix: short-circuit bug in schedule key extraction & polling timeout
claude Apr 3, 2026
8512e03
fix: update last_backup_at in manual triggers & atomic drive schedule
claude Apr 3, 2026
4a7a570
fix: preserve recent backups regardless of age in cleanup
claude Apr 3, 2026
550f055
fix: use atomic $set for drive_email in callback to avoid sentinel ov…
claude Apr 3, 2026
eae7283
fix: redirect-based auth in OAuth callback & preserve progress on error
claude Apr 3, 2026
d5168be
fix: total_size counts all backups & recover orphaned sentinels on crash
claude Apr 3, 2026
05e5256
fix: self-expiring sentinel & remove duplicate last_backup_at update
claude Apr 3, 2026
aa7393b
fix: deduplicate schedule key extraction using shared utility
claude Apr 3, 2026
3f06428
fix: drop old user_file_lookup index before creating renamed version
claude Apr 3, 2026
859b3c8
fix: add local fallback when schedule key utility import fails
claude Apr 3, 2026
24351d7
fix: protect drive scan loop from schedule key extraction errors
claude Apr 3, 2026
e742437
fix: guard concurrent restores & cap backups per scan cycle
claude Apr 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion database/bookmarks_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ def _ensure_indexes(self):
self.collection.drop_index("unique_user_file_anchor")
except Exception:
pass
# מחיקת אינדקס ישן שהוחלף בשם חדש
try:
self.collection.drop_index("user_file_lookup")
except Exception:
pass

indexes = [
# אינדקס ייחודי למניעת כפילויות בשורה
Expand All @@ -85,7 +90,7 @@ def _ensure_indexes(self):
# אינדקס לחיפוש מהיר לפי משתמש וקובץ
IndexModel(
[("user_id", ASCENDING), ("file_id", ASCENDING)],
name="user_file_lookup",
name="file_bookmarks_user_file_idx",
Comment thread
amirbiron marked this conversation as resolved.
),
# אינדקס לחיפוש לפי משתמש בלבד
IndexModel(
Expand Down
7 changes: 1 addition & 6 deletions database/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -1629,12 +1629,7 @@ def safe_create_index(*args: Any, **kwargs: Any) -> None:
name="announcements_is_active_idx",
)

# file_bookmarks - אינדקס משולב user_id+file_id לצמצום חיפושים לפי משתמש+קובץ
safe_create_index(
"file_bookmarks",
[("user_id", ASCENDING), ("file_id", ASCENDING)],
name="file_bookmarks_user_file_idx",
)
# file_bookmarks — האינדקס user_id+file_id מנוהל ב-BookmarksManager._ensure_indexes()

# recent_opens - אינדקס משולב user_id+file_name לשליפה מהירה של "נפתח לאחרונה"
safe_create_index(
Expand Down
60 changes: 55 additions & 5 deletions services/personal_backup_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
"""
import json
import logging
import time
import zipfile
from datetime import datetime, timezone
from io import BytesIO
from typing import Any, Dict, List, Optional
from typing import Any, Callable, Dict, List, Optional

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -482,6 +483,7 @@ def restore_user_data(
zip_bytes: bytes,
*,
overwrite: bool = False,
progress_cb: Optional[Callable[[int, str], None]] = None,
) -> Dict[str, Any]:
"""
משחזר נתוני משתמש מקובץ ZIP.
Expand All @@ -490,6 +492,7 @@ def restore_user_data(
user_id: מזהה המשתמש המשחזר
zip_bytes: תוכן קובץ ה-ZIP
overwrite: אם True, יוצר גרסה חדשה לקבצים קיימים (ההיסטוריה נשמרת)
progress_cb: callback אופציונלי (percent: int, step: str) לדיווח התקדמות

Returns:
dict עם סיכום: {"ok": bool, "restored": {...}, "errors": [...]}
Expand Down Expand Up @@ -567,24 +570,44 @@ def restore_user_data(
errors.append("שגיאה בקריאת backup_info")
backup_info = {}

def _report(pct: int, step: str):
if progress_cb:
try:
progress_cb(pct, step)
except Exception:
pass

_report(5, "קורא מטאדאטה...")

# 1) שחזור קבצים רגילים
files_meta = self._read_json_from_zip(zf, "metadata/files.json", errors, budget=budget)
regular_meta = (
files_meta.get("regular_files", []) if isinstance(files_meta, dict) else []
)
_report(10, f"משחזר קבצים (0/{len(regular_meta)})...")
restored["files"] = self._restore_regular_files(
zf, user_id, regular_meta, overwrite, errors, budget=budget
zf, user_id, regular_meta, overwrite, errors, budget=budget,
progress_cb=lambda done, total: _report(
10 + int(40 * done / max(total, 1)),
f"משחזר קבצים ({done}/{total})...",
),
)

# 2) שחזור קבצים גדולים
large_meta = (
files_meta.get("large_files", []) if isinstance(files_meta, dict) else []
)
_report(50, f"משחזר קבצים גדולים (0/{len(large_meta)})...")
restored["large_files"] = self._restore_large_files(
zf, user_id, large_meta, overwrite, errors, budget=budget
zf, user_id, large_meta, overwrite, errors, budget=budget,
progress_cb=lambda done, total: _report(
50 + int(15 * done / max(total, 1)),
f"משחזר קבצים גדולים ({done}/{total})...",
),
)

# 3) שחזור אוספים
_report(65, "משחזר אוספים...")
collections_data = self._read_json_from_zip(
zf, "metadata/collections.json", errors, budget=budget
)
Expand All @@ -594,11 +617,13 @@ def restore_user_data(
restored["collection_items"] = ci

# 4) שחזור סימניות
_report(75, "משחזר סימניות...")
bookmarks_data = self._read_json_from_zip(zf, "metadata/bookmarks.json", errors, budget=budget)
if isinstance(bookmarks_data, list):
restored["bookmarks"] = self._restore_bookmarks(user_id, bookmarks_data, errors)

# 5) שחזור פתקיות
_report(85, "משחזר פתקיות...")
notes_data = self._read_json_from_zip(
zf, "metadata/sticky_notes.json", errors, budget=budget
)
Expand All @@ -608,17 +633,21 @@ def restore_user_data(
)

# 6) שחזור העדפות
_report(92, "משחזר העדפות...")
prefs_data = self._read_json_from_zip(
zf, "metadata/preferences.json", errors, budget=budget
)
if isinstance(prefs_data, dict) and prefs_data:
restored["preferences"] = self._restore_preferences(user_id, prefs_data, errors)

# 7) שחזור העדפות Drive
_report(96, "משחזר העדפות Drive...")
drive_data = self._read_json_from_zip(zf, "metadata/drive_prefs.json", errors, budget=budget)
if isinstance(drive_data, dict) and drive_data:
restored["drive_prefs"] = self._restore_drive_prefs(user_id, drive_data, errors)

_report(100, "השחזור הושלם")

emit_event(
"personal_backup_restore",
user_id=user_id,
Expand All @@ -638,6 +667,8 @@ def _restore_regular_files(
errors: List[str],
*,
budget: "_ZipReadBudget",
progress_cb: Optional[Callable[[int, int], None]] = None,
batch_size: int = 20,
) -> int:
"""משחזר קבצי קוד רגילים.

Expand All @@ -650,7 +681,16 @@ def _restore_regular_files(
from database.models import CodeSnippet

count = 0
for meta in meta_list:
total = len(meta_list)
for idx, meta in enumerate(meta_list):
# דיווח התקדמות + הפוגה בין מנות לתת ל-DB לנשום
if progress_cb and idx % batch_size == 0:
try:
progress_cb(idx, total)
except Exception:
pass
if idx > 0 and idx % batch_size == 0:
time.sleep(0.05)
file_name = meta.get("file_name", "")
if not file_name:
continue
Expand Down Expand Up @@ -773,6 +813,8 @@ def _restore_large_files(
errors: List[str],
*,
budget: "_ZipReadBudget",
progress_cb: Optional[Callable[[int, int], None]] = None,
batch_size: int = 20,
) -> int:
"""משחזר קבצים גדולים.

Expand All @@ -783,7 +825,15 @@ def _restore_large_files(
from database.models import LargeFile

count = 0
for meta in meta_list:
total = len(meta_list)
for idx, meta in enumerate(meta_list):
if progress_cb and idx % batch_size == 0:
try:
progress_cb(idx, total)
except Exception:
pass
if idx > 0 and idx % batch_size == 0:
time.sleep(0.05)
file_name = meta.get("file_name", "")
if not file_name:
continue
Expand Down
30 changes: 30 additions & 0 deletions webapp/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,22 @@ def _stop_background_observability_warmup() -> None:
start_background_observability_warmup()


# --- Backup restore jobs TTL index ---
try:
from webapp.backup_api import _ensure_restore_indexes # noqa: E402
_ensure_restore_indexes()
except Exception:
pass

# --- Backup Scheduler (auto-backup to Drive & Disk) ---
try:
if str(os.getenv("DISABLE_BACKUP_SCHEDULER", "")).lower() not in {"1", "true", "yes"}:
from webapp.backup_scheduler import start_backup_scheduler # noqa: E402
start_backup_scheduler()
except Exception:
pass
Comment thread
amirbiron marked this conversation as resolved.


# --- Observability: Alert Tags indexes warmup (best-effort) ---
def start_background_alert_tags_indexes() -> None:
"""מנסה להבטיח אינדקסים ל-alert_tags ברקע (לא חוסם את השרת)."""
Expand Down Expand Up @@ -1106,6 +1122,20 @@ def _job():
# אל תפיל את השרת אם ה-Blueprint אינו זמין (למשל בסביבת דוקס/CI)
pass

# Google Drive Auth (OAuth redirect flow)
try:
from webapp.drive_auth import drive_auth_bp # noqa: E402
app.register_blueprint(drive_auth_bp)
except Exception:
pass

# Drive & Disk Backup API (schedule, trigger, status)
try:
from webapp.drive_backup_api import drive_backup_bp # noqa: E402
app.register_blueprint(drive_backup_bp)
except Exception:
pass

# GitHub Webhooks (Repo Sync Engine) - לפי המדריך
try:
from webapp.routes.webhooks import webhooks_bp # noqa: E402
Expand Down
Loading
Loading