1521 Commits
Author SHA1 Message Date
maziggy 5f13d5770c fix(inventory): filament of a print spanning a restart is no longer counted twice 2026-10-16 15:21:25 +02:00
maziggy b34ae7ca7b fix(billing): a failed dispatch no longer leaves its cost centre for the next print 2026-10-16 15:13:40 +02:00
maziggy 12422a7c7e fix(archive): runs of another plate are logged and charged for that plate 2026-10-16 13:29:29 +02:00
maziggy a8270b8fa2 fix(archive): reprints start clean, deleted archives are archived anew
A reprint kept the previous run's failure reason after it completed; a
queued job whose archive was deleted printed with no archive,
notification or tracking; and a timelapse deleted or replaced during its
MP4 conversion came back when the conversion finished.
2026-10-16 13:09:17 +02:00
maziggy e250281c17 feat(queue): a grouping that ended in failures is marked failed
A grouping whose jobs all failed (or failed and were cancelled) with
nothing printed stayed active for good, even through the start-up
clean-up. It is now marked failed, with its own filter on the Batches
tab, and opens again when a job is added. Orders are unchanged.
2026-10-16 12:51:25 +02:00
maziggy 19212d066d fix(print): Print Anyway per printer, variant plate, nozzle per plate, sliced .3mf drop
Print Anyway now only covers the printers whose warning it answered; an
untouched variant plate dropdown sends the plate it shows; the nozzle per
filament comes from the chosen plate; and a sliced 3MF named without
.gcode can be dropped on a printer card or uploaded for print.
2026-10-16 12:40:33 +02:00
maziggy a3ba9994fe fix(queue): orders and groupings show as finished however their last job ended
They were only re-checked when a print completed or an item was edited,
so a grouping whose last job was cancelled, stopped, deleted, failed at
dispatch or cancelled by a library delete, and an order whose last run
was closed by the stuck-print cleanup, stayed active until a restart.
They are now re-checked on every way a job ends, best-effort after the
change is committed.
2026-10-16 12:23:13 +02:00
maziggy 010361f378 fix(queue): a job cancelled while it is being sent stays cancelled
A failed upload overwrote a cancel made during it with "failed", sent a
failure notification and blocked the next job that needs the previous
print to have succeeded. Failures are now only recorded for a job still
waiting or printing, the toast of a cancelled job closes, and a job
stopped just as its printer turned busy is no longer queued again.
2026-10-16 11:45:34 +02:00
maziggy 5c575b5bae fix(queue): "only if the previous print succeeded" looks at the right printer
For a job queued for any printer of a model, the check ran before a
printer was chosen and nearly always let the job go. It now checks the
printer about to be used, takes another of the model when that one's last
print failed, and waits (with Resume for the model) when every free one
failed. On PostgreSQL a cancel without an end time no longer hides a
failure before it.
2026-10-16 11:30:21 +02:00
maziggy 2699bebc37 fix(queue): a job moved to another printer drops the old printer's settings
The AMS mapping, Print Anyway and nozzle choices are worked out for one
printer. Bulk edit, single edit and order "dispatch remaining" carried
them to another printer, where they mapped the wrong colour, skipped the
new printer's filament checks or pinned every copy to one printer. They
now stay with the old printer and are worked out again for the new one.
2026-10-16 10:48:10 +02:00
maziggy 4c15689267 fix(settings): pages work for users without the settings permission
Pages read install settings from GET /settings, which needs settings:read;
for anyone else they fell back to defaults. With billing on, the print
dialog hid the cost centre picker and the print was refused. The UI
settings these pages need now come from /settings/ui-flags, which any
signed-in user may read, through a useUiSettings hook. G-code snippets are
reported only as set or not.
2026-10-16 10:25:48 +02:00
maziggy 7616b65463 fix(spoolbuddy): write the Spoolman spool to the tag in Spoolman mode
Built-in spools are kept after switching to Spoolman. One with the same id
as the Spoolman spool was written to the tag and linked to it instead. The
tag write now takes the spool only from the inventory in use.
2026-10-16 09:56:02 +02:00
maziggy e965d22f38 A queued print that never starts no longer marks the printer's next print as started by Bambuddy. 2026-10-16 09:35:29 +02:00
maziggy d1f50973c6 Security hardening (maziggy/bambuddy-security #37) 2026-10-16 08:53:30 +02:00
maziggy e4866937c3 Security hardening (maziggy/bambuddy-security #36) 2026-10-16 08:38:04 +02:00
maziggy df3d016b53 Security hardening (maziggy/bambuddy-security #35) 2026-10-16 08:18:54 +02:00
maziggy 8a6faab0d2 Security hardening (maziggy/bambuddy-security #34) 2026-10-16 00:01:53 +02:00
maziggy 978d70370d Security hardening (maziggy/bambuddy-security #33) 2026-10-15 20:20:46 +02:00
maziggy 2a03aa612d Security hardening (maziggy/bambuddy-security #32) 2026-10-15 19:45:02 +02:00
maziggy 7b27d2a654 Security hardening (maziggy/bambuddy-security #31) 2026-10-15 19:28:22 +02:00
maziggy fa8d670691 Security hardening (maziggy/bambuddy-security #30) 2026-10-15 18:42:13 +02:00
maziggy f43be8098e Security hardening (maziggy/bambuddy-security #29) 2026-10-15 18:11:15 +02:00
maziggy dd6696470f Security hardening (maziggy/bambuddy-security #28) 2026-10-15 17:11:36 +02:00
maziggy 369ce6e1b5 fix(settings): restoring an old SQLite backup onto PostgreSQL no longer fails on project values
Older versions stored any project priority or status. Restoring a backup of
such an install onto PostgreSQL copied the rows as they were, and a value
longer than the column made PostgreSQL refuse the row and the whole restore
fail ("value too long for type character varying(20)"). The import now gives
an unknown priority "normal" and an unknown status "active" while copying,
the fallbacks project import and the start-up repair use. Checked end to end
against a throwaway PostgreSQL, with and without the fix.
2026-10-15 16:34:36 +02:00
maziggy 7ece330c53 fix(finance): release budget held by prints that ended
Before a print without a user (authentication off, or its user deleted while
it printed) was charged to its cost centre (b2ed37b6c), billing stopped
before using up or releasing the print's budget reservation, so it held that
much of the cost centre's budget for good.

A start-up repair releases active print reservations whose queue entry has
finished or is gone: nothing can charge them any more. Waiting, printing and
skipped entries keep theirs, and the ledger is not touched. Not gated: it
only matches dead reservations, so a second start changes nothing.
2026-10-15 16:26:02 +02:00
maziggy 176161f0d2 fix(pipelines): a pipeline run remembers how each copy ended
A dispatched pipeline job kept status 'queued' and showed its outcome only
through its queue entry. Once the entry was deleted (queue page, archive or
user delete) the job lost its link, the run stayed 'dispatching' forever,
and retry could not tell a printed copy from one that never ran.

Session hooks now store a job's final status when its queue entry ends:
on every flush (status changes, deletes of a pending entry = cancelled) and
for bulk statements on print_queue run through a session. A start-up
backfill copies the final status of entries that ended before. Jobs whose
entry was already gone read as 'unknown': never retried, counted as finished
by the run, and left alone by cancelling the run. A guard test refuses raw
SQL that ends or deletes queue entries past the hooks.
2026-10-15 16:08:43 +02:00
maziggy e1714b5115 fix(inventory): deleting a spool keeps its usage history; spool ids are never reused
On PostgreSQL, deleting a spool cascaded to its usage history, so
recalculating the cost of its prints fell back to an estimate. On SQLite the
history stayed with the old spool id, which the next spool could get and
then show the deleted spool's prints as its own.

spool_usage_history.spool_id is now nullable with ON DELETE SET NULL: the
model, a PostgreSQL migration, an edit of the stored table definition on
SQLite (both historical shapes), and the SQLite rule list, whose start-up
sweep also clears ids left by earlier deletes. The spool table joins
AUTOINCREMENT on SQLite. Readers handle usage without a spool: the usage
list (was a 500), the forecast panel and the stock forecast skip it, Git
restore keeps it.
2026-10-15 15:30:10 +02:00
maziggy 9613d65e0d fix(archives): archives a spool was used for can be deleted on PostgreSQL
spool_usage_history.archive_id referenced the archive without a delete rule,
so on PostgreSQL every hard delete of an archive with spool usage failed on
the foreign key with a server error: deleting with purge from statistics,
the automatic purge with that option, and deleting a printer with its
archives. The usage record is the spool's consumption log and now outlives
the archive: ON DELETE SET NULL in the model, a PostgreSQL migration, and
the SQLite rule list, whose start-up sweep also clears links left dangling
by earlier deletes.

Recalculate costs no longer charges usage without an archive link to
archives by print name. With deleted archives' usage now unlinked, that
fallback would have charged one print's spool cost to every archive of the
same name; such archives get the filament-price estimate instead.
2026-10-15 15:05:58 +02:00
maziggy 72dfa7b168 Apply the reprint checks to re-slicing an archive with the built-in slicer. 2026-10-15 14:13:00 +02:00
maziggy d1100f5a33 Security hardening (maziggy/bambuddy-security #26)
-----------
fix(queue): every way of printing an archive again applies the same checks

Deleted archives read as missing when queueing, batching or running a
pipeline; a batch's archive or library file passes the same checks as
POST /queue/; running a pipeline on an archive needs the right to reprint
it, and the Run with pipeline dialog shows why a run is refused.
2026-10-15 13:56:56 +02:00
maziggy cfc6512599 Security hardening (maziggy/bambuddy-security #25) 2026-10-15 13:39:18 +02:00
maziggy 0c571bc2c4 fix(queue): an id of 0 no longer slips past the checks when queueing, batching or creating a project
Queue create, batch create and project create tested archive_id,
library_file_id and parent_id for truthiness: 0 skipped the existence check
and was then stored as the id. On PostgreSQL that failed the foreign key
with a server error; on SQLite it saved a link to nothing. Batch create did
not check its archive or library file at all.

0 now means none on all three, as it already did when editing a project's
parent. Batch create also checks that a given archive exists and a given
library file is active, and refuses an unknown one with 400.
2026-10-15 13:10:45 +02:00
maziggy a399ef0361 fix(pending-uploads): check the project before archiving an upload into it
Archiving a pending upload stored the requested project id on the archive
and the pending row without checking it, and only after the file had been
archived. An unknown project failed with a foreign key error (500) on
PostgreSQL with the archive files already written, and was stored as a
broken link on SQLite. 0 skipped the archive but was stored on the pending
row.

The project is now checked first: an unknown one is refused with 400
"Project not found" and the upload stays pending. 0 means no project, and
the archive and the pending row get the same id.
2026-10-15 12:58:44 +02:00
maziggy 9c9278e673 fix(projects): a project's BOM no longer shows archives from printers outside the user's groups
The BOM routes stored any archive id on a line and answered with that
archive's print name, looked up by id alone. A member of a group limited to
some printers could read the name of any archive (404 when asked for
directly) by linking a BOM line to it, and every reader of the project saw
the names of archives from printers they have no access to. A link to an
archive that does not exist was saved too: a dangling id on SQLite, a
foreign key error (500) on PostgreSQL.

A line now links only to a live archive on the caller's printers (400
"Archive not found" otherwise, the same for hidden and missing), and shows
its archive's id and name only to callers who can see it, as the project's
archive list does. Every non-zero id is checked on edit, also an unchanged
one, so resending a hidden link's id answers like a missing archive. 0 means
no archive on create too. The list looks the names up in one query.
2026-10-15 12:42:38 +02:00
maziggy fbd506557a fix(projects): adding archives or queue jobs to a project keeps to the user's printers and prints
add-archives, remove-archives and add-queue loaded each archive or job by
id alone and needed only projects:update. A member of a group limited to
some printers could link another team's archives and jobs to a project or
unlink them, and the "Added N" count told them which hidden ids exist. Any
project editor could also relink other users' prints, which the archive and
queue edits refuse.

The routes now change only rows the caller could change directly: on their
printers, and only their own unless they hold the update_all permission.
Other rows are skipped like missing ones. An API key acts with its owner's
rights. A project key still needs no archive or queue flag (#1893), but it
never reaches a row its owner could not change; a legacy key without an owner
keeps every row of its printers. The responses carry the number changed as
"count", and Assign to project reports it instead of the number selected.
2026-10-15 11:52:34 +02:00
maziggy e0e2799f40 fix(archives): an archive edit cannot move it to a printer outside the user's groups
The archive edit checked only the printer the archive was on, then wrote the
new printer_id and project_id unchecked. A member of a group limited to
printer A could move an archive of A to printer B, into the history of a team
they have no access to (measured: the other team's member then listed it),
although uploading to such a printer was already refused. Any caller could
also link an archive to a printer or project that does not exist: stored
dangling on SQLite, a foreign key error (500) on PostgreSQL.

A changed printer now has to be in the caller's printer scope (404 "Printer
not found", as for any hidden printer) and has to exist (400), and a changed
project has to exist (400 "Project not found"). Null still clears either; a
printer-less archive is in every scope, as one uploaded without a printer.
Values the edit resends unchanged are not re-checked, so an archive whose
stored printer or project is dangling can still be edited.
2026-10-15 11:10:16 +02:00
maziggy 26a8a09937 fix(projects): refuse an unknown priority on create, keep imported statuses valid, repair stored ones
Creating a project stored any priority string, and the JSON and file imports
stored any status. The project editor sends both back on every save and
refuses such values, so a project holding one could not be edited at all,
not even its name. A value longer than 20 characters failed with a server
error on PostgreSQL.

Create now answers 400 "Invalid priority" for a priority outside
low/normal/high/urgent, as PATCH already did. Both imports keep a status of
active/completed/archived and fall back to "active", as they already did for
the priority. PROJECT_PRIORITIES and PROJECT_STATUSES now live in
models/project.py.

A startup repair gives existing projects with an unknown or missing priority
"normal" and an unknown status "active". It is not gated behind a flag: it
only matches values nothing accepts, so a second run changes nothing, and a
restored older database is repaired on its next start.

------------------

fix(smart-plugs): check printer id 0 like any other printer id

Smart plug create and edit, and virtual printer create, tested the printer id
for truthiness, so 0 skipped the "Printer not found" check. On PostgreSQL
the foreign key then failed with a server error; on SQLite the plug or
virtual printer was saved linked to a printer that does not exist.

The checks now use "is not None", as queue create and virtual printer update
already did, so 0 is refused with "Printer not found".
2026-10-15 10:47:32 +02:00
maziggy ae3c27f3df fix(firmware): log an unreachable Bambu Lab site once, not on every firmware check
Without an internet connection, every firmware check of every printer logged
a full ERROR for each Bambu Lab page it fetched, plus a WARNING per model
(measured: 12 ERROR and 9 WARNING lines for three printers checked three
times), all saying the same thing.

FirmwareCheckService now tracks unreachable hosts separately for the wiki and
bambulab.com, since either can fail on its own. The first transport failure
for a host is logged once as a WARNING. Later failures during that outage go
to debug, and the first successful fetch logs "<host> reachable again" at
INFO. The per-model "Could not fetch firmware info" warning also drops to
debug while an outage is known. A wiki fetch that does reach the host but
gets no usable answer, such as a redirect loop or a broken encoding, is
logged at debug and doesn't count as an outage.
2026-10-15 10:11:47 +02:00
maziggy bdd3111547 fix(cloud): report an unreachable Bambu Cloud as 502, not as a server error or wrong credentials 2026-10-15 09:26:23 +02:00
maziggy 47fbd23ff8 fix(smart-plugs): an edit can no longer save a plug that cannot be read back 2026-10-15 08:50:33 +02:00
maziggy d3827e646a Security hardening (maziggy/bambuddy-security #23)
Security hardening (maziggy/bambuddy-security #24)
2026-10-15 08:06:14 +02:00
maziggy ed0ab1cd42 Security hardening (maziggy/bambuddy-security #22) 2026-10-14 17:40:30 +02:00
maziggy 8701e2b6b3 Security hardening (maziggy/bambuddy-security #18) 2026-10-14 16:43:08 +02:00
maziggy fe2bc43720 test: run the backend suite on PostgreSQL too; fix the project import priority
The suite can now run against a throwaway PostgreSQL server
(TEST_POSTGRES_URL=postgresql+asyncpg://...@127.0.0.1:port/postgres):
each worker gets its own databases, the app takes its PostgreSQL code
paths, tests of SQLite-only behaviour are marked sqlite_only. Test data
fixed where it relied on SQLite ignoring foreign keys, types and lengths.

Found by it: importing a project file without a priority failed on
2026-10-14 16:03:43 +02:00
maziggy 930a6661d7 Security hardening (maziggy/bambuddy-security #21) 2026-10-14 14:01:36 +02:00
maziggy b6847f246e Security hardening (maziggy/bambuddy-security #21) 2026-10-14 13:21:25 +02:00
maziggy 047fe0df49 Security hardening (maziggy/bambuddy-security #20) 2026-10-14 12:40:20 +02:00
maziggy a51283ad76 Security hardening (maziggy/bambuddy-security #19) 2026-10-14 11:18:19 +02:00
maziggy b1be388a7d Security hardening (maziggy/bambuddy-security #18) 2026-10-14 10:08:09 +02:00
maziggy 929e89d6ce Security hardening (maziggy/bambuddy-security #17) 2026-10-13 20:20:09 +02:00