From 68c06a76c42052b171646c5ea051eacbe02eb2fb Mon Sep 17 00:00:00 2001 From: maziggy Date: Sun, 14 Jun 2026 10:22:21 +0200 Subject: [PATCH 1/3] chore(support): silence bandit B104 on net.info ip redaction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "0.0.0.0" written into the support bundle is a JSON sentinel that scrubs the printer's local IP plus the gateway/peers it sees — not a socket bind address. Annotate inline so bandit stops flagging it. --- backend/app/api/routes/support.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app/api/routes/support.py b/backend/app/api/routes/support.py index 10b64b4bd..5c85ce833 100644 --- a/backend/app/api/routes/support.py +++ b/backend/app/api/routes/support.py @@ -1190,7 +1190,7 @@ def _redact_raw_push_status(raw: dict) -> dict: info_list = net.get("info") if isinstance(info_list, list): net["info"] = [ - ({**entry, "ip": "0.0.0.0"} if isinstance(entry, dict) and "ip" in entry else entry) + ({**entry, "ip": "0.0.0.0"} if isinstance(entry, dict) and "ip" in entry else entry) # nosec B104 - redaction sentinel, not a bind address for entry in info_list ] From 7b43c545e83a5edf6255cd2f53f561e08ba30091 Mon Sep 17 00:00:00 2001 From: maziggy Date: Sun, 14 Jun 2026 11:26:45 +0200 Subject: [PATCH 2/3] Updated README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4acbac15b..1aeb9cad7 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ > — Adam Conway, [XDA-Developers](https://www.xda-developers.com/finally-have-full-control-bambu-lab-printer-ditched-bambu-cloud/)

+ Hackaday XDA-Developers How-To Geek Fabbaloo From b17198068817da93e346967e12ea059675a31468 Mon Sep 17 00:00:00 2001 From: maziggy Date: Sun, 14 Jun 2026 12:21:57 +0200 Subject: [PATCH 3/3] fix(archives): backfill NULL created_at + tolerate NULL in response (#1732) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Older print_archives rows (and rows that landed via the SQLite ↔ Postgres cross-DB restore path) can have created_at = NULL because the column was originally created without a DEFAULT clause — server_default=func.now() only fires at table creation, not for existing rows or raw cross-DB inserts. The list_archives response model required a datetime, so a single NULL row 500'd the whole endpoint via Pydantic ResponseValidationError. - Boot-time backfill: COALESCE(completed_at, started_at, now()) for any row where created_at IS NULL. Dialect-branched (SQLite datetime('now') vs Postgres NOW()). - Schema: created_at is now Optional on ArchiveDuplicate, ArchiveResponse, and ArchiveSlim so a future NULL-leaking path doesn't break the list endpoint again. --- backend/app/core/database.py | 24 ++++++++++++++++++++++++ backend/app/schemas/archive.py | 6 +++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/backend/app/core/database.py b/backend/app/core/database.py index 019aafb79..857baa359 100644 --- a/backend/app/core/database.py +++ b/backend/app/core/database.py @@ -2914,6 +2914,30 @@ async def run_migrations(conn): # file metadata so the FileManager displays the filename, not the title (#1489). await _migrate_drop_library_print_name(conn) + # Backfill NULL print_archives.created_at — older rows (and rows imported + # via the SQLite ↔ Postgres cross-DB restore path) can land with NULL + # because the column was originally created without a DEFAULT clause and + # server_default=func.now() only fires at table creation, not column + # population. The list_archives response model requires a datetime, so a + # single NULL row 500s the whole endpoint (#1732). + async with conn.begin_nested(): + if is_sqlite(): + await conn.execute( + text( + "UPDATE print_archives " + "SET created_at = COALESCE(completed_at, started_at, datetime('now')) " + "WHERE created_at IS NULL" + ) + ) + else: + await conn.execute( + text( + "UPDATE print_archives " + "SET created_at = COALESCE(completed_at, started_at, NOW()) " + "WHERE created_at IS NULL" + ) + ) + async def seed_notification_templates(): """Seed default notification templates if they don't exist.""" diff --git a/backend/app/schemas/archive.py b/backend/app/schemas/archive.py index f1661faa0..7f1ca2cfc 100644 --- a/backend/app/schemas/archive.py +++ b/backend/app/schemas/archive.py @@ -27,7 +27,7 @@ class ArchiveDuplicate(BaseModel): id: int print_name: str | None - created_at: datetime + created_at: datetime | None match_type: str # "exact" (hash match) or "similar" (name match) @@ -94,7 +94,7 @@ class ArchiveResponse(BaseModel): energy_kwh: float | None = None energy_cost: float | None = None - created_at: datetime + created_at: datetime | None # User tracking (Issue #206) created_by_id: int | None = None @@ -137,7 +137,7 @@ class ArchiveSlim(BaseModel): completed_at: datetime | None cost: float | None quantity: int = 1 - created_at: datetime + created_at: datetime | None class Config: from_attributes = True