Repository navigation
Feature: upload a wallpaper, and fit it to the screen - #135
Merged
Merged
Conversation
The page's content policy allows images only from this origin, Unsplash and the icon CDN, so the Image URL background could never load a link to anywhere else: the browser refused it and the dashboard fell back to the base colour. Store the image instead of pointing at it. An upload is sniffed by its own bytes, written under the icons mount in its own directory, and served from this origin; a pasted link is downloaded once through the guarded fetch and stored the same way, so the field keeps its meaning without loading anything cross-origin. One wallpaper is kept at a time. Add a fill or fit choice for how the image sits on the screen, with the label contrast measuring the fitted edges as the colour that actually shows there.
The site-wide request body limit is 3 MB, well under a 4K photo, so nginx refused the upload with an HTML error page before the route's own limit applied. The admin read every failure as JSON, so what reached the user was the browser complaining about the response rather than a word about the file. Give the two wallpaper endpoints their own limits: 16 MB of body on the upload address, and 60s at the proxy for the link fetch, which now waits 30s for the download rather than the 8s a widget poll gets. Read a failed response as text first, so a body that is not JSON still produces a message, and say plainly when an image is too large. Error notices now stay until they are dismissed, and hovering or focusing any notice holds it open, so a message can be read and copied.
An upload deleted every other stored wallpaper at once, so an upload that was never saved took the wallpaper still on screen with it and left the dashboard on its base colour. Store the new file and delete nothing. A config write prunes instead, keeping the file the saved settings name; a config that names none keeps the newest, so switching the source and back does not throw the image away. Steady state is one file either way. Cover the fit setting end to end: the dropdown writes the field, the save stores it, and the dashboard's wallpaper layer reports contain rather than cover.
The settings pages draw the same wallpaper as the dashboard, but set only the image, the colour and the brightness, so a fitted wallpaper was still cropped to cover there. Set the size alongside the rest, and cover both pages in the fit spec.
Name a stored file with a random suffix as well as the timestamp, so two uploads in the same millisecond cannot land on one name. Report an over-size body as 400 with the limit in it, rather than a 500 whose message the error layer withholds. Fold the demo, origin and rate-limit checks of both endpoints into one guard, and read both bodies through one capped reader. Log a wallpaper that could not be removed instead of discarding the reason. A link that fails to download now restores the wallpaper already saved, rather than clearing the field and offering to save nothing. Cut the comments back to the constraints: the two limits nginx has to agree with, the reason the upload deletes nothing, and the two-seed colour parse.
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.
Summary
POST /api/wallpaper/upload: multipart, 8 MB cap, origin-checked and rate-limited on the existinguploadbucket, stored under$ICONS_PATH/wallpaper/with a generated name.POST /api/wallpaper/fetch: downloads a pasted link throughfetchChecked, so the SSRF guard and the 4 MB response cap apply, and stores the result identically.api/src/image-sniff.js: PNG, JPEG, GIF, WebP (RIFF brand) and AVIF (ISOftypbrand) by signature; the submitted name decides nothing, and the stored extension follows the bytes.storeWallpaperremoves every other file in the directory after a successful write. The directory sits a level below the one/api/icons/locallists, so a wallpaper never appears in the icon picker.GET /api/wallpaperout ofroutes/icons.jsinto the newroutes/wallpaper.js.fitto the background settings:--bg-sizebecomescoverorcontain, andhtml::beforenow paints--bg-colorwithbackground-repeat:no-repeat, so a fitted image has a defined backdrop.contain:drawPlanreturns source and destination rects, and the sampling canvas is filled with the backdrop colour before the image is drawn, so a label on a letterboxed edge is measured against what is really there.docs/security.md.Testing
All green, including the Docker build. Added 12 backend tests for the sniffer, the storage rules and the two endpoints, and 4 frontend tests for the fit geometry.
Checklist