Skip to content

feat(uploads): add web page screenshot capabilities - #448

Merged
stakach merged 2 commits into
masterfrom
feat/uploads-screenshot
Sep 30, 2026
Merged

stakach merged 2 commits into
masterfrom
feat/uploads-screenshot

Conversation

@stakach

@stakach stakach commented Sep 30, 2026

Copy link
Copy Markdown
Member

Summary

Adds POST /api/engine/v2/uploads/screenshot. It renders a web page in headless Chromium (browserless), saves the image to the authority's default storage, and returns the completed Upload (201).

Request body:

  • url (required)
  • width / height (default 1920×1080)
  • scale
  • format (png | jpeg | webp)
  • full_page
  • settle (ms, default 1000)
  • file_name, public, tags

Width, height, scale, settle and tags are validated. The storage's allowed file types and mime types apply, and a screenshot tag is always added.

Page readiness

We send a single request to browserless's /chromium/screenshot. It waits in this order:

  1. networkidle2
  2. document.fonts.ready
  3. two requestAnimationFrames
  4. settle ms
  5. one more frame

Steps 2–5 are a single async waitForFunction, because browserless runs waitForTimeout before waitForFunction.

Internal service protection

  • Only https URLs are accepted (400 otherwise).
  • The browser is sent rejectRequestPattern: ["^http:"], which blocks every plain-http load, including redirects and subresources. Internal services only speak http.

Errors

  • 400 for invalid input.
  • Browser failure or busy → 502; browser timeout → 504. These use the new generic Error::BadGateway / Error::GatewayTimeout.
  • Storage rejected or unreachable → 502. The Upload row is removed.

Refactor

  • The server-side "sign PUT, send bytes, mark complete" logic moved out of ImageGen::Store.put into a shared ObjectStore.put. ImageGen::Store.put now delegates to it and keeps its ImageGen::Vendor error.
  • Rollback on failure now uses delete rather than destroy. destroy's before_destroy storage cleanup could fail against the same broken storage and leave an orphaned, incomplete row. Raised IO/socket errors now roll back too.

docker-compose

  • New browser service: ghcr.io/browserless/chromium, amd64 + arm64, token auth. It's configured through BROWSER_URI, BROWSER_TOKEN and SCREENSHOT_TIMEOUT.
  • minio now uses pgsty/minio / pgsty/mc, because the upstream quay.io / docker.io minio/* images return 401. testbucket now runs mc alias set, since mc config host add has been removed.

Deployment

Deployments need a browserless instance reachable at BROWSER_URI (default http://browser:3000). Ideally it's network-isolated from internal services as defence in depth.

Testing

  • New spec/controllers/uploads_screenshot_spec.cr (9 examples) covers:
    • the happy path and the body sent to the browser;
    • format and file_name handling;
    • every validation 400;
    • browser 500 / 408;
    • storage 403 and a raised connection error (no row left behind);
  • ./test spec/controllers/uploads_screenshot_spec.cr spec/controllers/uploads_spec.cr spec/controllers/signage/signage_ai_spec.cr: 54 examples, 0 failures, run on the plain compose file.
  • OPENAPI_DOC.yml regenerated.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@github-actions github-actions Bot added the type: enhancement new feature or request label Sep 30, 2026
BROWSER_TOKEN falls back to PLACE_SERVER_SECRET / SERVER_SECRET (as dispatch
does) and is sent as an Authorization: Bearer header rather than ?token=, so
the long secret stays out of browserless access logs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot removed the type: enhancement new feature or request label Sep 30, 2026

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@github-actions github-actions Bot added the type: enhancement new feature or request label Sep 30, 2026
@stakach
stakach merged commit 526a7fb into master Sep 30, 2026
11 checks passed
@stakach
stakach deleted the feat/uploads-screenshot branch September 30, 2026 06:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: enhancement new feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant