From 702de680987242141a9b4a7aaeb71e2304ba158e Mon Sep 17 00:00:00 2001 From: Ivo Oskamp Date: Fri, 20 Mar 2026 09:36:17 +0100 Subject: [PATCH] Auto-commit local changes before build (2026-03-20 09:36:17) --- containers/backupchecks/Dockerfile | 2 +- .../src/backend/app/main/routes_settings.py | 133 +++++------------- 2 files changed, 37 insertions(+), 98 deletions(-) diff --git a/containers/backupchecks/Dockerfile b/containers/backupchecks/Dockerfile index cee3009..e831de5 100644 --- a/containers/backupchecks/Dockerfile +++ b/containers/backupchecks/Dockerfile @@ -17,4 +17,4 @@ ENV APP_PORT=8080 EXPOSE 8080 # Use the application factory from backend.app -CMD ["gunicorn", "-b", "0.0.0.0:8080", "backend.app:create_app()"] +CMD ["gunicorn", "-b", "0.0.0.0:8080", "--timeout", "120", "backend.app:create_app()"] diff --git a/containers/backupchecks/src/backend/app/main/routes_settings.py b/containers/backupchecks/src/backend/app/main/routes_settings.py index 1ec3dd8..9153246 100644 --- a/containers/backupchecks/src/backend/app/main/routes_settings.py +++ b/containers/backupchecks/src/backend/app/main/routes_settings.py @@ -8,118 +8,57 @@ from datetime import datetime @roles_required("admin") def settings_jobs_delete_all(): try: - jobs = Job.query.all() + job_count = db.session.execute(text("SELECT COUNT(*) FROM jobs")).scalar() or 0 - if not jobs: + if not job_count: flash("No jobs to delete.", "info") - return redirect(url_for("main.settings", section="general")) + return redirect(url_for("main.settings")) - - - # Collect run ids for FK cleanup in auxiliary tables that may not have ON DELETE CASCADE - run_ids = [] - mail_message_ids = [] - - for job in jobs: - for run in job.runs: - if run.id is not None: - run_ids.append(run.id) - if run.mail_message_id: - mail_message_ids.append(run.mail_message_id) - - # Return related mails back to inbox and unlink from job - if mail_message_ids: - msgs = MailMessage.query.filter(MailMessage.id.in_(mail_message_ids)).all() - for msg in msgs: - if hasattr(msg, "location"): - msg.location = "inbox" - msg.job_id = None - - def _safe_execute(stmt, params): + def _try(stmt): + """Best-effort: skip if table/column doesn't exist in this schema version.""" try: - db.session.execute(stmt, params) - except Exception as cleanup_exc: - # Best-effort cleanup for differing DB schemas - print(f"[settings-jobs] Cleanup skipped: {cleanup_exc}") + db.session.execute(text(stmt)) + except Exception as exc: + print(f"[settings-jobs] Skipped: {exc}") + # All deletions via direct SQL — no ORM loading into Python memory. + # Order: unlink → FK-dependent tables → job_runs → jobs. - # Ensure run_object_links doesn't block job_runs deletion (older schemas may miss ON DELETE CASCADE) - if run_ids: - db.session.execute( - text("DELETE FROM run_object_links WHERE run_id IN :run_ids").bindparams( - bindparam("run_ids", expanding=True) - ), - {"run_ids": run_ids}, - ) + # 1. Unlink staging accounts that have a nullable FK to jobs. + _try("UPDATE cove_accounts SET job_id = NULL WHERE job_id IS NOT NULL") + _try("UPDATE cloud_connect_accounts SET job_id = NULL WHERE job_id IS NOT NULL") + # 2. Return mail messages linked to jobs back to the inbox. + db.session.execute(text( + "UPDATE mail_messages SET job_id = NULL, location = 'inbox' WHERE job_id IS NOT NULL" + )) + # 3. Delete tables that FK → job_runs. + _try("DELETE FROM remark_job_runs") + _try("DELETE FROM ticket_job_runs") + _try("DELETE FROM run_object_links") - # Ensure job_object_links doesn't block jobs deletion (older schemas may miss ON DELETE CASCADE) - job_ids = [j.id for j in jobs] - if job_ids: - db.session.execute( - text("DELETE FROM job_object_links WHERE job_id IN :job_ids").bindparams( - bindparam("job_ids", expanding=True) - ), - {"job_ids": job_ids}, - ) + # 4. Delete tables that FK → jobs. + _try("DELETE FROM job_object_links") + _try("DELETE FROM ticket_scopes WHERE job_id IS NOT NULL") + _try("DELETE FROM remark_scopes WHERE job_id IS NOT NULL OR job_run_id IS NOT NULL") + _try("DELETE FROM overrides WHERE job_id IS NOT NULL") - # Clean up auxiliary FK tables that may reference job_runs/jobs without ON DELETE CASCADE (older schemas) - if run_ids: - _safe_execute( - text("DELETE FROM remark_job_runs WHERE job_run_id IN :run_ids").bindparams( - bindparam("run_ids", expanding=True) - ), - {"run_ids": run_ids}, - ) - _safe_execute( - text("DELETE FROM ticket_job_runs WHERE job_run_id IN :run_ids").bindparams( - bindparam("run_ids", expanding=True) - ), - {"run_ids": run_ids}, - ) - # Some schemas use remark_scopes for per-run remarks - _safe_execute( - text("DELETE FROM remark_scopes WHERE job_run_id IN :run_ids").bindparams( - bindparam("run_ids", expanding=True) - ), - {"run_ids": run_ids}, - ) - - if job_ids: - # ticket_scopes.job_id is a FK without ON DELETE CASCADE in some schemas - _safe_execute( - text("DELETE FROM ticket_scopes WHERE job_id IN :job_ids").bindparams( - bindparam("job_ids", expanding=True) - ), - {"job_ids": job_ids}, - ) - - # Some schemas use remark_scopes for per-job remarks - _safe_execute( - text("DELETE FROM remark_scopes WHERE job_id IN :job_ids").bindparams( - bindparam("job_ids", expanding=True) - ), - {"job_ids": job_ids}, - ) - # Overrides may reference jobs directly - _safe_execute( - text("DELETE FROM overrides WHERE job_id IN :job_ids").bindparams( - bindparam("job_ids", expanding=True) - ), - {"job_ids": job_ids}, - ) - - # Delete all jobs (runs/objects are cascaded via ORM relationships) - for job in jobs: - db.session.delete(job) + # 5. Delete runs, then jobs. + db.session.execute(text("DELETE FROM job_runs")) + db.session.execute(text("DELETE FROM jobs")) db.session.commit() - flash("All jobs deleted. Related mails are returned to the inbox.", "success") + + _log_admin_event( + event_type="jobs_delete_all", + message=f"Deleted all {job_count} job(s) and all related data.", + ) + flash(f"All {job_count} jobs deleted. Related mails are returned to the inbox.", "success") except Exception as exc: db.session.rollback() print(f"[settings-jobs] Failed to delete all jobs: {exc}") - flash("Failed to delete all jobs.", "danger") + flash(f"Failed to delete all jobs: {exc}", "danger") return redirect(url_for("main.settings"))