feat(api): printer photos - #114
Open
ChristopherHoffman wants to merge 7 commits into
Open
ChristopherHoffman wants to merge 7 commits into
ChristopherHoffman wants to merge 7 commits into
Conversation
…ounting PrinterImageService ports the filament upload/delete/reorder/set-default paths, with ownership on Printer.UserId rather than PrinterImage.CreatedById (which records the uploader). PrinterService gains the subscription cap and the SAS signing helpers. MediaStorageQuotaService becomes the single definition of storage used, adopted by FilamentImageService, FileAttachmentService and SubscriptionService, so the usage a user is shown is the usage they are held to.
Adds POST/GET/DELETE/reorder/set-as-default under /api/Printers/{id}/images, the
authenticated whole-user map at /api/Printers/thumbnails, and Images on the printer
detail response.
The printer list caches an UNSIGNED blob path and signs per request: a signed URL
cached for the entry lifetime would outlive its signature. The paths live on a
separate CachedPrinterSummaryPage rather than behind [JsonIgnore] on the response
DTO, because HybridCache round-trips values through System.Text.Json - a [JsonIgnore]
member does not survive the cache, and keeping the path off the response type makes
leaking it structurally impossible.
Printer deletion and account deletion both remove image rows, File rows and blobs in
that order; the PrinterImage -> File FKs are Restrict, so either would otherwise fail
outright once a printer had photos.
Users -> Printers -> PrinterImages is already a cascade path, so the Cascade the model implies on CreatedById/UpdatedById gives SQL Server a second one and it rejects the table with error 1785. Verified against a real SQL Server 2022: the generated migration fails with 1785, this one applies cleanly. The model keeps Cascade and the migration overrides it, matching AddProjects and AddFilamentImage; account deletion removes these rows explicitly through UserDeletionService rather than by cascade. Integration tests run on SQLite via EnsureCreated and never execute the migration, so nothing catches this before deploy.
Integration tests build their schema with EnsureCreated() on SQLite, so a broken migration passes the whole suite. Notes the two SQL Server rules EF's conventions violate - multiple cascade paths (1785) and correlated aggregates (8124) - and the two-minute check against the SQL Server already in docker-compose.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds printer photos to the API: a
PrinterImageentity, upload/delete/reorder/set-default endpoints, an authenticated per-user thumbnail map, and the deletion cleanup that has to come with them.Pairs with HoffmanEngineering/3d-print-log-ui#176.
What's here
PrinterImageentity, indexes and migration. A filtered unique index enforces exactly one default per printer in the database rather than by convention, so two concurrent "first" uploads cannot both win.PrinterImageService— upload, delete, reorder, set-default, ported from the filament image path. Ownership is alwayspi.Printer.UserId == userId;PrinterImage.CreatedByIdrecords who uploaded, which is not the same thing./api/Printers/{id}/images, plusGET /api/Printers/thumbnails— the whole-user signed map every cross-cutting surface reads. It is authenticated-only, which is what structurally keeps printer photos off public print pages.CachedPrinterSummaryPagerather than behind[JsonIgnore]on the response DTO, because HybridCache round-trips through System.Text.Json — a[JsonIgnore]member does not survive the cache, and keeping the path off the response type makes leaking it structurally impossible.MediaStorageQuotaServicebecomes the single definition of storage used, adopted byFilamentImageService,FileAttachmentServiceandSubscriptionService, so the usage a user is shown is the usage they are held to.MaxImages.PrinterImage → FileFKs areRestrict, so either would otherwise fail outright once a printer had photos.The migration would have broken the deploy
The generated migration gave
CreatedById/UpdatedByIdcascading FKs toUsersby EF convention.Users → Printers → PrinterImagesis already a cascade path, so that second one makes SQL Server reject the table with error 1785. The integration suite never sees it: it runs on SQLite viaEnsureCreated()and never executes migrations at all.Verified against a real SQL Server 2022 — the generated migration fails with 1785, the corrected one applies cleanly:
The model keeps
Cascadeand the migration overrides it toNoAction, matching whatAddProjectsandAddFilamentImagealready had to do — this is the third time this trap has been hit. Account deletion removes those rows explicitly inUserDeletionService, so nothing depended on the cascade.Testing
dotnet test— 1485 passing, 0 failing, 30 skipped (all pre-existing environment skips).PrinterImageServiceTests,PrinterImagesControllerTests,PrinterImageHydrationTests,PrinterImageConcurrencyTests,PrinterImageDeletionTests,MediaStorageQuotaTests.E2ETesting: create a printer with two staged photos, star the second, confirm both upload and that the starred one becomes the list thumbnail.