Skip to content

Claude/add auto backup ds6iu - #3139

Merged
amirbiron merged 29 commits into
mainfrom
claude/add-auto-backup-ds6iu
Apr 3, 2026
Merged

Claude/add auto backup ds6iu#3139
amirbiron merged 29 commits into
mainfrom
claude/add-auto-backup-ds6iu

Conversation

@amirbiron

Copy link
Copy Markdown
Owner

תבנית Pull Request

✨ תיאור קצר

  • מה שיניתם ולמה? (2-3 משפטים)

📦 שינויים עיקריים

  • קוד (Backend)
  • בוט טלגרם
  • מסד נתונים/מיגרציות
  • תיעוד (docs/)
  • DevOps/CI/CD

פירוט נקודות (רשימת תבליטים):

🧪 בדיקות

  • איך בדקתם? מה עבר? מה נשאר?
  • Unit
  • Integration
  • Manual

🧪 בדיקות נדרשות ב‑PR

  • 🔍 Code Quality & Security
  • Unit Tests (3.11)
  • Unit Tests (3.12)

📝 סוג שינוי

  • feat: פיצ'ר חדש
  • fix: תיקון באג
  • docs: שינוי תיעוד בלבד
  • refactor: שינוי קוד ללא שינוי התנהגות
  • perf: שיפור ביצועים
  • chore/ci: תשתית/CI
  • breaking change: שינוי שובר תאימות

דוגמאות Conventional Commits

סוג דוגמה להודעה מתי להשתמש
feat feat: הוספת מסך הגדרות פיצ'ר חדש למשתמש
fix fix: תיקון קריסה בעת התחברות תיקון באג מול משתמשים/פרודקשן
chore chore: שדרוג Gradle ל-8.9 תחזוקה, כלי פיתוח, housekeeping
docs docs: עדכון README עם הוראות התקנה שינויי תיעוד בלבד
refactor refactor: חילוץ Repository ל-UseCases שינוי מבני ללא שינוי התנהגות
test test: הוספת בדיקות ל-LoginViewModel הוספת/עדכון בדיקות
build build: הוספת flavor staging ל-CI שינויים בבילד/תלויות/תצורה

✅ צ'קליסט

  • הקוד עוקב אחרי הסגנון (Black/isort/flake8/mypy)
  • בדיקות רצות ועוברות
  • תיעוד עודכן (README/Docs)
  • אם נוספו ג'ובים חדשים (Background Jobs) – וודא שהם רשומים ב-services/register_jobs.py (כולל Callback/Trigger להפעלה ידנית — למשל callback_name/trigger_func לפי המבנה) כדי שיופיעו בדשבורד
  • אם נוספו/שונו משתני סביבה – עודכן docs/environment-variables.rst וגם services/config_inspector_service.py
  • אם נוספו/השתנו טוקנים – עודכן גם docs/webapp/theming_and_css.rst + FEATURE_SUGGESTIONS/theme_matrix.md
  • אין סודות/מפתחות בקוד
  • אין מחיקות מסוכנות/פעולות על root (ראו .cursorrules)
  • הודעת הקומיט תואמת Conventional Commits (ע"פ הטבלה)
  • CHANGELOG עודכן אם נדרש
  • כל ה‑Required Checks לעיל ירוקים
  • צילום/וידאו UI מצורף אם רלוונטי

🧩 השפעות/סיכונים

  • השפעה אפשרית על פרודקשן, ביצועים, או אבטחה:

🔗 קישורים

🧯 סיכון / החזרה לאחור (Rollback)

  • תוכנית חזרה לאחור במקרה תקלה:

claude added 26 commits April 3, 2026 09:15
- גיבוי אוטומטי ל-Google Drive עם OAuth redirect flow מהוובאפ
- גיבוי אוטומטי לדיסק (/var/data/repos/backups) עם ניקוי retention
- Scheduler thread ברקע שסורק כל 5 דקות ומריץ גיבויים מתוזמנים
- שחזור אסינכרוני עם progress bar ו-batching (מנות של 20)
- תיקון אינדקס כפול ב-bookmarks (file_bookmarks_user_file_idx)
- UI חדש בהגדרות: סקשנים ל-Drive, דיסק, ותדירות גיבוי

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
…leanup

- Race condition ב-restore_progress: snapshot כל השדות + pop בתוך אותו lock
- Disk backup: עדכון last_backup_at רק בהצלחה (ok=True)
- דליפת זיכרון: cleanup entries ישנים מעל שעה ב-_active_restores

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
מעביר את בדיקת user_id לפני ה-pop מ-_active_restores
כדי שמשתמש לא-מורשה לא ימחק entry של משתמש אחר.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
ה-before_request handler הגביל גודל רק ל-restore הסינכרוני.
עכשיו מכסה גם את restore_backup_async שדרכו עובר כל ה-traffic.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
- drive_auth: כל ערכי error ב-redirects עוברים _url_encode למניעת URL שבור
- backup_api: תוצאת restore לא נמחקת מיד אלא מסומנת fetched_at,
  ונמחקת רק אחרי 5 דקות grace — כך שאם ה-response אבד ברשת
  הלקוח יכול לקרוא שוב בלי לקבל 404

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
_cleanup_stale_restores היה מוחק entries שעבר שעה מאז יצירתם,
בלי לבדוק אם הם עדיין בסטטוס running. עכשיו entries שרצים
לא נמחקים ע"י age check — רק entries שסיימו (done/error).

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
1. backup_api — העברת _active_restores מ-dict בזיכרון ל-collection
   restore_jobs ב-MongoDB. פותר:
   - High: state לא אבד ב-restart או בין workers
   - Low: stuck running entries נמחקים ע"י TTL index (שעה)

2. backup_scheduler — _scan_and_run משתמש ב-find_one_and_update
   לתפיסה אטומית של משתמשים. רק scheduler אחד מריץ גיבוי
   לכל משתמש, גם עם N gunicorn workers.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
אם uid או schedule_key חסרים אחרי ה-claim האטומי, מחזיר את
schedule_next_at לערך המקורי כדי למנוע תקיעה ב-2099.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
כשגיבוי נכשל, schedule_next_at נדחה ב-SCAN_INTERVAL קדימה (5 דקות)
במקום להחזיר את הערך המקורי שעדיין $lte now — מה שגרם ל-while True
לתפוס את אותו משתמש שוב מיד (לולאה אינסופית).

אותו תיקון ב-_reset_drive_schedule ו-_reset_disk_schedule.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
…handling

באג 1 (High): כיבוי schedule ל-Drive ("off") לא עצר את ה-scheduler
כי save_drive_prefs עושה merge ושדות legacy כמו schedule: "daily" נשארים.
תיקון defense-in-depth ב-3 שכבות:
- $unset שדות legacy בכיבוי (drive_backup_api.py)
- $ne "off" + $ne None בשאילתת הסריקה (backup_scheduler.py)
- early return ב-_extract_schedule_key כש-schedule_key=="off"

באג 2 (Low): _complete_restore_job חסר error handling.
אם הקריאה נכשלת ב-success path, היא נופלת ל-except ומדווחת
שחזור מוצלח כנכשל. תיקון:
- try/except ב-_complete_restore_job
- הפרדת תוצאת השחזור מהעדכון ל-DB

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
…sert, null guard)

1. drive_auth.py: _settings_redirect return type -> Response (לא str)
2. backup_api.py: בדיקת result.get("ok") is False לפני סימון "done"
   כדי לא לדווח שחזור כושל כהצלחה
3. drive_backup_api.py: הסרת upsert=True ב-set_disk_schedule
   כדי לא ליצור מסמך user חלקי
4. backup_scheduler.py: הוספת $ne None לשאילתת disk schedule_next_at
   בהתאמה לתיקון שנעשה בשאילתת Drive

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
1. backup_scheduler.py: _extract_schedule_key — val.get("key") or val.get("value")
   גורם ל-short-circuit כש-key הוא string לא-ריק אבל לא תקין,
   ו-value לעולם לא נבדק. הוחלף בלולאה על שני השדות בנפרד.

2. settings.html: לולאת polling של restore ללא timeout —
   אם ה-job תקוע ב-running, הדפדפן סובב עד שעה.
   הוסף guard של 10 דקות (300 סיבובים × 2 שניות).

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
1. backup_scheduler.py: trigger_drive_backup_now ו-trigger_disk_backup_now
   לא עדכנו last_backup_at — ה-UI הציג תאריך ישן אחרי "גבה עכשיו".
   הוסף עדכון $set אחרי הצלחה.

2. drive_backup_api.py: save_drive_prefs עושה read-modify-write
   שיכול לדרוס sentinel של ה-scheduler.
   הוחלף ב-$set ישיר על שדות ספציפיים (dot notation),
   כמו שכבר עושים ב-set_disk_schedule.
   $unset ל-legacy fields שולב באותו update_one.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
_cleanup_disk_backups השתמש בלוגיקת OR — גיבוי נמחק אם עבר max
*או* אם עבר retention. כך משתמש שלא גיבה 30+ יום איבד את כל
הגיבויים הקיימים (חוץ מהאחרון).

שונה ל: ה-N הכי חדשים (max) נשמרים תמיד. מחיקה חלה רק
על גיבויים מעבר ל-max שגם עברו את ה-retention.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
…erwrite

drive_callback השתמש ב-save_drive_prefs (read-modify-write) לשמירת
drive_email, מה שיכול לדרוס את ה-sentinel של ה-scheduler.
הוחלף ב-$set ישיר על drive_prefs.drive_email — אותו pattern
שכבר תוקן ב-set_drive_schedule.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
1. drive_auth.py: drive_callback השתמש ב-@_require_auth שמחזיר
   JSON 401 — בדפדפן (redirect מ-Google) המשתמש רואה JSON גולמי.
   הוחלף בבדיקת session ידנית עם redirect לדף הגדרות + הודעת שגיאה.

2. backup_api.py: _complete_restore_job איפס progress ל-0 בשגיאה,
   מה שגורם לקפיצה אחורה בפרוגרס בר (65% → 0%).
   בשגיאה, progress לא נדרס — נשמר הערך האחרון.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
1. get_disk_backup_info: total_size חישב רק את 20 הראשונים
   אבל count החזיר את כולם — גודל מטעה ב-UI.
   עכשיו total_size סוכם את כל הקבצים.

2. sentinel orphan recovery: אם ה-process קרס בין claim לעדכון,
   ה-sentinel "2099-01-01" נשאר לנצח והגיבוי תקוע.
   הוסף _recover_orphaned_sentinels שרץ בתחילת כל סריקה —
   מאפס sentinels ל-retry_next_at. הוצא הערך הקשיח לקבוע
   _SENTINEL_VALUE.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
1. sentinel: הוחלף מערך קבוע "2099" ל-now + 30 דקות (SENTINEL_TTL).
   - אם ה-backup מסתיים בזמן — הערך מוחלף כרגיל
   - אם ה-process קרס — אחרי 30 דקות הערך פוקע ($lte now)
     ונתפס מחדש טבעית
   - הוסר _recover_orphaned_sentinels שאיפס גם backups פעילים
     של workers אחרים

2. trigger_drive_backup_now: הוסר עדכון כפול של last_backup_at.
   perform_scheduled_backup כבר מעדכן דרך save_drive_prefs —
   עדכון שני ב-$set גרם ל-race condition.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
_extract_schedule_key בscheduler היה כפילות של extract_schedule_key
מ-handlers/drive/utils.py עם סדר שדות שונה ותמיכה חלקית
(חסר scheduleKey, name). עכשיו משתמש בפונקציה המשותפת
עם ולידציה כנגד SCHEDULE_INTERVALS ובדיקת "off".

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
האינדקס (user_id, file_id) הוחלף מ-user_file_lookup
ל-file_bookmarks_user_file_idx, אבל הישן לא נמחק.
MongoDB לא מוחק אינדקס ישן אוטומטית כשיוצרים חדש
באותם שדות עם שם אחר — נשארים שני אינדקסים.
הוסף drop_index("user_file_lookup") ליד ה-drop הקיים.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
_extract_schedule_key תפס ImportError והחזיר None תמיד,
מה שגרם ל-scheduler לתפוס משתמשים ולאפס מיד (sentinel reset)
בלי להריץ גיבוי — שקט ולנצח.
הוסף fallback מקומי שבודק schedule_key, scheduleKey, schedule
ישירות מ-drive_prefs כשה-import נכשל.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
_extract_schedule_key נקרא מחוץ ל-try/except בלולאת הסריקה.
אם זורק exception (למשל מתוך extract_schedule_key המיובא),
כל הלולאה נעצרת — ה-sentinel לא מאופס והמשתמשים הנותרים נדלגים.

תיקון:
- עטיפה ב-try/except סביב _extract_schedule_key בלולאת הסריקה
- שינוי except ImportError ל-except Exception בפונקציה עצמה
  כך שגם שגיאות מתוך extract_schedule_key נתפסות

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
1. backup_api.py: restore_backup_async לא בדק אם כבר רץ restore
   לאותו user. שתי קריאות מקבילות גורמות לכתיבות כפולות ל-DB.
   הוסף בדיקת status: "running" לפני יצירת job חדש (409).

2. backup_scheduler.py: _scan_drive_backups הוא while True
   שרץ לפני _scan_disk_backups. אחרי השבתה ארוכה, הרבה
   drive backups בתור גורמים ל-disk להירעב שעות.
   הוסף MAX_BACKUPS_PER_SCAN (10) — כל סוג מעבד עד 10
   בסריקה, והנותרים ייתפסו בסריקה הבאה.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
הסרת width: 100% מכפתורי הגיבוי ומה-select בסקשנים של Drive ודיסק,
והחלפת flex: 1 ב-div פשוט כך שהאלמנטים מתאימים לטקסט שבתוכם.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
@github-actions

github-actions Bot commented Apr 3, 2026

Copy link
Copy Markdown

🧯 Dangerous deletes guard report

Policy: see .cursorrules — dangerous deletions are blocked unless wrapped safely.

Summary:

  • Flagged findings (blocking): 0
    0
  • Excluded matches (not blocking): 15
  • Total matches (all files): 129

Flagged findings (file:line:snippet):
(none)

Excluded matches (by path pattern)
./Dockerfile:42:    rm -rf /var/lib/apt/lists/*
./Dockerfile:121:    rm -rf /var/lib/apt/lists/*
./node_modules/katex/src/fonts/Makefile:139:	rm -rf pfa ff otf ttf woff woff2
./node_modules/katex/package.json:153:    "build": "rimraf dist/ && mkdirp dist && cp README.md dist && rollup -c --failAfterWarnings && webpack && node update-sri.js package dist/README.md",
./node_modules/mermaid/dist/mermaid.min.js:1524:`,"getStyles"),c1e=RQe});var h1e={};dr(h1e,{diagram:()=>NQe});var NQe,f1e=N(()=>{"use strict";$ge();a1e();l1e();u1e();NQe={parser:Fge,db:n1e,renderer:o1e,styles:c1e}});var m1e,g1e=N(()=>{"use  … [truncated]
./node_modules/mermaid/dist/mermaid.min.js.map:4:  "sourcesContent": ["/**\n* Default values for dimensions\n*/\nconst defaultIconDimensions = Object.freeze({\n\tleft: 0,\n\ttop: 0,\n\twidth: 16,\n\theight: 16\n});\n/**\n* Default values fo … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.esm/chunk-2M32CCKP.mjs.map:4:  "sourcesContent": ["{\n  \"name\": \"mermaid\",\n  \"version\": \"11.12.0\",\n  \"description\": \"Markdown-ish syntax for generating flowcharts, mindmaps, sequence d … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.esm.min/chunk-4HFYJGYH.mjs:1:var r={name:"mermaid",version:"11.12.0",description:"Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graph … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.esm.min/chunk-4HFYJGYH.mjs.map:4:  "sourcesContent": ["{\n  \"name\": \"mermaid\",\n  \"version\": \"11.12.0\",\n  \"description\": \"Markdown-ish syntax for generating flowcharts, mindmaps, sequen … [truncated]
./node_modules/mermaid/dist/chunks/mermaid.core/chunk-KS23V3DP.mjs.map:4:  "sourcesContent": ["{\n  \"name\": \"mermaid\",\n  \"version\": \"11.12.0\",\n  \"description\": \"Markdown-ish syntax for generating flowcharts, mindmaps, sequence  … [truncated]
./node_modules/mermaid/dist/mermaid.js.map:4:  "sourcesContent": ["/**\n* Default values for dimensions\n*/\nconst defaultIconDimensions = Object.freeze({\n\tleft: 0,\n\ttop: 0,\n\twidth: 16,\n\theight: 16\n});\n/**\n* Default values for tr … [truncated]
./README.md:842:find . -name "__pycache__" -exec rm -rf {} +
./docs/DOCUMENTATION_GUIDE.md:453:rm -rf _build
./docs/Makefile:24:	rm -rf $(BUILDDIR)
./webapp/static/js/md_preview.bundle.js.map:4:  "sourcesContent": ["// Markdown-it plugin to render GitHub-style task lists; see\n//\n// https://github.com/blog/1375-task-lists-in-gfm-issues-pulls-comments\n// https://github.com/blog/1825-t … [truncated]

@github-actions

github-actions Bot commented Apr 3, 2026

Copy link
Copy Markdown

⏱️ Performance report

(No performance test durations collected. Mark tests with @pytest.mark.performance.)

@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

existing = db.db.restore_jobs.find_one(
{"user_id": int(user_id), "status": "running"},
projection={"_id": 1},
)
if existing:

P1 Badge Serialize restore job claim per user

The async restore guard is a non-atomic check-then-insert: this find_one runs before _create_restore_job, so two concurrent /api/backup/restore-async requests from the same user can both pass the check and start parallel restores. That breaks the endpoint’s single-restore invariant and can lead to overlapping writes during restore. Use an atomic claim (e.g., unique partial index for status="running" per user or a single find_one_and_update/upsert-based lock).


ts = _now_utc().strftime("%Y%m%d_%H%M%S")
filename = f"webapp_backup_{user_id}_{ts}.zip"
filepath = backup_dir / filename
filepath.write_bytes(zip_bytes)

P2 Badge Generate unique disk backup filenames

Disk backup filenames are only second-granular (%Y%m%d_%H%M%S), so two backups for the same user in the same second (for example, manual trigger plus scheduler, or rapid retries) resolve to the same path and write_bytes overwrites the earlier archive. This can silently lose a backup copy; include sub-second precision or a random suffix in the filename.


# מחכים עד 60 שניות
result = future.result(timeout=60)
return jsonify(result)

P2 Badge Return asynchronous status for long Drive backups

This endpoint waits only 60 seconds for future.result(), but backup uploads can legitimately exceed that; on timeout it returns an error while the worker thread continues running. Users then see a failed operation and may retrigger, causing duplicate/overlapping Drive backups. Expose a job status (or longer-lived async response) instead of converting slow-but-running work into an immediate failure.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@sentry

sentry Bot commented Apr 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

1. P1 — restore job claim: unique partial index (status=running, per user)
   replaces non-atomic check-then-insert with DuplicateKeyError catch
2. P2 — disk backup filename: add microseconds (%f) to prevent overwrites
3. P2 — Drive backup-now: returns immediately with status=running,
   background thread updates DB on completion, UI polls backup-status

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
@github-actions

github-actions Bot commented Apr 3, 2026

Copy link
Copy Markdown

📖 Documentation Preview

The documentation has been built successfully!

To view locally:

  1. Download the artifacts
  2. Extract the zip file
  3. Open index.html in your browser

Comment thread webapp/drive_backup_api.py
The backend now returns immediately with status=running, so the JS
must poll /api/drive/backup-status for manual_backup_status instead
of treating ok=true as completion. Polls every 1s for up to 2 minutes.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment thread webapp/backup_api.py Outdated
Prevents TTL index failure from silently skipping the critical
unique partial index that guards concurrent restores.

https://claude.ai/code/session_019psqKqV6MhkHQRkqt8Szz5
@amirbiron
amirbiron merged commit f0dda5d into main Apr 3, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants