Skip to content

Updates split - #26

Merged
jlooper-cloudinary merged 20 commits into
mainfrom
updates-split
Sep 9, 2026
Merged

jlooper-cloudinary merged 20 commits into
mainfrom
updates-split

Conversation

@Princesso

Copy link
Copy Markdown
Collaborator

No description provided.

Bumps Next.js, React, Radix, Tailwind and related tooling, and updates
the two dynamic route handlers whose `params` now arrive as a Promise
under Next.js 15's async params API. Also fixes UploadButton's prop
typing, which no longer compiles against the updated React 19 types.
Configures Vitest with happy-dom, the @ path alias, and Cloudinary
test env vars so subsequent commits can ship unit tests alongside
their source changes.
Introduces lib/cloudinary-server.ts as the single place that
configures the Cloudinary SDK (previously duplicated across every
route), and adds pagination support (maxResults/nextCursor) to
getResourcesByTag.
Adds isAllowedCloudinaryUrl/validateRemoteUploadUrl to restrict
remote uploads to Cloudinary delivery URLs, and
isReadOnlyMode/isDemoUploadAllowed/assertUploadAllowed to support a
read-only deployment mode with an optional demo-upload carve-out.
Removes the duplicated cloudinary.config() calls in favor of
getCloudinary(), and gates deletes behind isReadOnlyMode().
@vercel

vercel Bot commented Jul 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
photocrate-demo Ready Ready Preview Sep 9, 2026 12:32am UTC
photocrate-web Ready Ready Preview Sep 9, 2026 12:32am UTC

Request Review

Princesso added 13 commits July 19, 2026 14:19
Adds validateParamsToSign to allowlist which upload params clients
may request a signature for, enforcing the expected folder and
required tags before the server signs anything.
Wires validateParamsToSign into the signing endpoint and blocks
signing entirely when the app is in read-only mode.
Applies assertUploadAllowed and validateRemoteUploadUrl to the
server-side upload route so it respects read-only/demo mode and
only accepts Cloudinary-hosted source URLs.
getColorPop used to always send skip-check=true so the demo could
preview the effect; it now only does so when
NEXT_PUBLIC_PHOTOCRATE_DEMO_MODE is enabled, matching the
isDemoUploadAllowed() guard added in the previous commits.
Accepts cursor/limit query params and forwards them to
getResourcesByTag, replacing the previous fixed 400-result fetch.
Adds a small IntersectionObserver-based hook that triggers
onLoadMore when a sentinel element scrolls into view.
Centralizes the width/height/crop/loading configs used for gallery
thumbnails, the media viewer, creation previews, and filter previews,
plus a hashTransformations helper for stable React keys.
useResources now drives a useInfiniteQuery against the paginated
/api/resources endpoint instead of a single fixed-size fetch, and
MediaGallery uses useInfiniteScroll plus getThumbnailPreset/
getCreationPreviewPreset to load further pages and render images.
Replaces inline width/height/crop configs with getViewerPreset/
getFilterPreviewPreset, memoizes the outside-click handler, fixes the
edit sheet's default tab and close-on-click exclusion for the
management navbar, and adds a render test for the Enhance tab.
CldImage now respects explicit loading/priority props instead of
always lazy-loading, and Container forwards arbitrary HTML attributes
(e.g. data-* props) to its wrapping div.
…ured

Avoids calling getCldOgImageUrl (which requires a configured
Cloudinary client) when isCloudinaryConfigured() is false, and marks
decorative sidebar icons aria-hidden.
Runs npm test, lint, and build on pushes and PRs against main with
Cloudinary env vars stubbed out.
Documents the new read-only/demo-mode env vars and asset tag
overrides in .env.example, refreshes README.md, and adds
GITHUB_METADATA.md and VERIFICATION.md.
@Princesso

Copy link
Copy Markdown
Collaborator Author

Hi @eportis-cloudinary, I have split this into 18 commits. You can click on each commit and see the changed files. A number of these commits are tests, updates to outdated libararies, updates to Cloudinary syntaxes, and refactoring code .
One more thing, in the preview environment, deployments use my private Cloudinary API keys and secrets. Can you make the preview environments inherit production environment variables for testing purposes? I do not have the right to do that at the moment.

UploadButton invalidated the ['resources', undefined] query while every
gallery reads from ['resources', <tag>], so new uploads never appeared
without a full reload. Scope it to libraryTag, which is what uploads
are always tagged with.

The tags/update route was missing the isReadOnlyMode() guard that
delete, upload, and sign-cloudinary-params all received in this PR,
allowing tag mutations (favorite/trash/restore) in read-only demo mode.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
validateParamsToSign silently defaulted resource_type to 'image' when
the widget's paramsToSign omitted it, so we signed a string containing
resource_type=image. The widget's actual upload request never sends
resource_type as a signed field (it only selects the API endpoint), so
Cloudinary reconstructs a different string and rejects every signature.

Verified by reproducing the exact signature computation against a live
sign-cloudinary-params call before and after the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@jlooper-cloudinary
jlooper-cloudinary deleted the updates-split branch September 9, 2026 00:36
Princesso added a commit that referenced this pull request Sep 9, 2026
* 01: chore: upgrade Next.js 15, React 19, and dependencies

Bumps Next.js, React, Radix, Tailwind and related tooling, and updates
the two dynamic route handlers whose `params` now arrive as a Promise
under Next.js 15's async params API. Also fixes UploadButton's prop
typing, which no longer compiles against the updated React 19 types.

* 02: test: add Vitest testing infrastructure

Configures Vitest with happy-dom, the @ path alias, and Cloudinary
test env vars so subsequent commits can ship unit tests alongside
their source changes.

* 03: refactor: centralize Cloudinary client configuration

Introduces lib/cloudinary-server.ts as the single place that
configures the Cloudinary SDK (previously duplicated across every
route), and adds pagination support (maxResults/nextCursor) to
getResourcesByTag.

* 04: feat: add upload validation and read-only/demo mode guards

Adds isAllowedCloudinaryUrl/validateRemoteUploadUrl to restrict
remote uploads to Cloudinary delivery URLs, and
isReadOnlyMode/isDemoUploadAllowed/assertUploadAllowed to support a
read-only deployment mode with an optional demo-upload carve-out.

* 05: refactor: use shared Cloudinary client in delete/tags routes

Removes the duplicated cloudinary.config() calls in favor of
getCloudinary(), and gates deletes behind isReadOnlyMode().

* 06: feat: add signed-upload parameter validation

Adds validateParamsToSign to allowlist which upload params clients
may request a signature for, enforcing the expected folder and
required tags before the server signs anything.

* 07: refactor: validate params in sign-cloudinary-params route

Wires validateParamsToSign into the signing endpoint and blocks
signing entirely when the app is in read-only mode.

* 08: refactor: harden upload route with validation guards

Applies assertUploadAllowed and validateRemoteUploadUrl to the
server-side upload route so it respects read-only/demo mode and
only accepts Cloudinary-hosted source URLs.

* 09: fix: gate Color Pop demo skip-check behind demo mode flag

getColorPop used to always send skip-check=true so the demo could
preview the effect; it now only does so when
NEXT_PUBLIC_PHOTOCRATE_DEMO_MODE is enabled, matching the
isDemoUploadAllowed() guard added in the previous commits.

* 10: feat: paginate the resources list API

Accepts cursor/limit query params and forwards them to
getResourcesByTag, replacing the previous fixed 400-result fetch.

* 11: feat: add useInfiniteScroll hook

Adds a small IntersectionObserver-based hook that triggers
onLoadMore when a sentinel element scrolls into view.

* 12: feat: add shared image delivery presets

Centralizes the width/height/crop/loading configs used for gallery
thumbnails, the media viewer, creation previews, and filter previews,
plus a hashTransformations helper for stable React keys.

* 13: refactor: wire infinite scroll and pagination into the gallery

useResources now drives a useInfiniteQuery against the paginated
/api/resources endpoint instead of a single fixed-size fetch, and
MediaGallery uses useInfiniteScroll plus getThumbnailPreset/
getCreationPreviewPreset to load further pages and render images.

* 14: refactor: use delivery presets in MediaViewer

Replaces inline width/height/crop configs with getViewerPreset/
getFilterPreviewPreset, memoizes the outside-click handler, fixes the
edit sheet's default tab and close-on-click exclusion for the
management navbar, and adds a render test for the Enhance tab.

* 15: refactor: small component polish

CldImage now respects explicit loading/priority props instead of
always lazy-loading, and Container forwards arbitrary HTML attributes
(e.g. data-* props) to its wrapping div.

* 16: feat: fall back to a static OG image when Cloudinary isn't configured

Avoids calling getCldOgImageUrl (which requires a configured
Cloudinary client) when isCloudinaryConfigured() is false, and marks
decorative sidebar icons aria-hidden.

* 17: chore: add CI workflow

Runs npm test, lint, and build on pushes and PRs against main with
Cloudinary env vars stubbed out.

* 18: docs: update README, add repo metadata/verification docs

Documents the new read-only/demo-mode env vars and asset tag
overrides in .env.example, refreshes README.md, and adds
GITHUB_METADATA.md and VERIFICATION.md.

* Fix upload cache invalidation and add missing read-only guard

UploadButton invalidated the ['resources', undefined] query while every
gallery reads from ['resources', <tag>], so new uploads never appeared
without a full reload. Scope it to libraryTag, which is what uploads
are always tagged with.

The tags/update route was missing the isReadOnlyMode() guard that
delete, upload, and sign-cloudinary-params all received in this PR,
allowing tag mutations (favorite/trash/restore) in read-only demo mode.

* Fix Invalid Signature error on every upload

validateParamsToSign silently defaulted resource_type to 'image' when
the widget's paramsToSign omitted it, so we signed a string containing
resource_type=image. The widget's actual upload request never sends
resource_type as a signed field (it only selects the API endpoint), so
Cloudinary reconstructs a different string and rejects every signature.

Verified by reproducing the exact signature computation against a live
sign-cloudinary-params call before and after the fix.

---------

Co-authored-by: jlooper-cloudinary <jen.looper@cloudinary.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants