Skip to content

feat(media): add flat folder API foundation - #2584

Open
khoinguyenpham04 wants to merge 3 commits into
feat/media-pagination-uifrom
feat/media-folders-api
Open

feat(media): add flat folder API foundation#2584
khoinguyenpham04 wants to merge 3 commits into
feat/media-pagination-uifrom
feat/media-folders-api

Conversation

@khoinguyenpham04

@khoinguyenpham04 khoinguyenpham04 commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Adds the backend and typed-client foundation for flat, shared Media Library folders. Local media can belong to one folder or the Main library, list requests can select All media, Main library media, or one folder, and deleting a folder returns its media to the Main library without changing media IDs, storage keys, URLs, or usage records.

This is PR1 of the folders sequence and intentionally has no admin UI, upload-to-folder behavior, nesting, bulk moves, provider changes, CLI commands, or MCP commands. PR2 will add the focused Media Library UI on top of this branch.

This PR is stacked on #2582 so reviewers see only the folder foundation. It will ultimately merge into main after the pagination dependency lands.

Related media roadmap Discussions:

A folder-specific maintainer-approved Discussion was not found. Code review can proceed, but this feature must not merge until the folder scope is approved.

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • Typecheck passes for the changed emdash package
  • Type-aware lint passes with 0 diagnostics
  • The full default core suite passes: 6,044 tests
  • All changed TypeScript and documentation files pass their formatters
  • I have added/updated tests for my changes
  • User-visible strings in the admin UI are not applicable; PR1 adds no admin UI and includes no messages.po changes
  • I have added and reviewed an emdash minor changeset
  • New features link to an approved Discussion: folder-specific approval is still needed

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Codex GPT-5.6, with GPT-5.6 Terra Thinking X High second-opinion reviews

Screenshots / test output

PR1 has no visual changes.

  • Full default core suite: 489 files passed, 6,044 tests passed.
  • Focused migration, repository, REST, authorization, OpenAPI, and client suite: 223 tests passed.
  • Real Astro server client flow passed for folder create/list/rename/delete, assignment, filtering, and safe folder deletion.
  • Type-aware lint: 0 diagnostics.
  • Core typecheck, core build, OpenAPI validation, changeset validation, and documentation build passed.
  • SQLite migration replay and concurrent-migrator coverage passed. PostgreSQL was not configured locally, so dialect-parametric PostgreSQL cases remain for CI.
  • Iterative Terra Thinking X High adversarial review converged with no findings.

@changeset-bot

changeset-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8858dd3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
Name Type
emdash Minor
@emdash-cms/cloudflare Minor
@emdash-cms/sandbox-workerd Patch
@emdash-cms/plugin-mcp-smoke Major
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
@emdash-cms/admin Minor
@emdash-cms/auth Minor
@emdash-cms/blocks Minor
@emdash-cms/gutenberg-to-portable-text Minor
@emdash-cms/x402 Minor
create-emdash Minor
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

Copy link
Copy Markdown
Contributor

Scope check

This PR changes 1,578 lines across 33 files. Large PRs are harder to review and more likely to be closed without review.

If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs.

See CONTRIBUTING.md for contribution guidelines.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 20, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
docs 8858dd3 Aug 26 2026, 05:11 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 20, 2026

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://feat-media-folders-api.try.emdashcms.com, https://feat-media-folders-api-emdash-playground.emdash-cms.workers.dev (commit 8858dd3)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://a5055fcf.try.emdashcms.com, https://a5055fcf-emdash-playground.emdash-cms.workers.dev 8858dd3 2026-08-26T21:06:37.264Z Visit the dashboard ↗
  • Build: Failed ❌

View logs ↗
d77704e 2026-08-26T16:06:49.884Z View logs ↗
  • Build: Failed ❌

View logs ↗
edfd4e3 2026-08-26T14:51:59.991Z View logs ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://1df02b78.try.emdashcms.com, https://1df02b78-emdash-playground.emdash-cms.workers.dev 151d46b 2026-08-25T15:48:57.571Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://da7e75ba.try.emdashcms.com, https://da7e75ba-emdash-playground.emdash-cms.workers.dev cad314c 2026-08-25T12:21:48.670Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://75dace68.try.emdashcms.com, https://75dace68-emdash-playground.emdash-cms.workers.dev 3a3f8c8 2026-08-25T11:54:23.345Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://a0aed6f6.try.emdashcms.com, https://a0aed6f6-emdash-playground.emdash-cms.workers.dev cc6eae9 2026-08-25T11:31:42.936Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://e878468e.try.emdashcms.com, https://e878468e-emdash-playground.emdash-cms.workers.dev b5b2821 2026-08-20T23:34:59.312Z Visit the dashboard ↗

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 20, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-cache 8858dd3 Aug 26 2026, 05:06 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 20, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-do 8858dd3 Aug 26 2026, 05:09 PM

@github-actions

Copy link
Copy Markdown
Contributor

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This PR adds a coherent flat media-folders foundation: a migration, repository, handlers, Astro routes, client methods, OpenAPI additions, and REST docs. The implementation closely follows existing EmDash patterns—cursor pagination, ApiResult handlers, requirePerm/requireOwnerPerm, FK/cascade behavior, and a D1-compatible migration. Test coverage is broad (migrations, repository integration, handler/unit, route authorization, OpenAPI, and client serialization).

The main process concern is the one the PR already flags: AGENTS.md requires a maintainer-approved Discussion for a feature, and no folder-specific Discussion exists yet. The code should not merge until that approval is in place; as a draft PR the author is aware, so this raises the approach risk without making it a line finding.

Code-level issues are minor and mostly on the public surface: the changeset is too terse for a minor feature, the typed client introduces a duplicate local-media shape next to an already-inaccurate MediaItem, and the update-body schema accepts the query-only unfiled sentinel. None of these are blockers, but they should be cleaned up before the feature ships.

Comment thread .changeset/flat-media-folders.md Outdated
Comment thread packages/core/src/api/schemas/media.ts Outdated
Comment thread packages/core/src/client/index.ts Outdated
@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond review/needs-rereview Author pushed changes since the last review and removed review/needs-review No maintainer or bot review yet review/awaiting-author Reviewed; waiting on the author to respond labels Aug 25, 2026
@pkg-pr-new

pkg-pr-new Bot commented Aug 25, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/admin@2584

@emdash-cms/auth

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/auth@2584

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/auth-atproto@2584

@emdash-cms/blocks

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/blocks@2584

@emdash-cms/cloudflare

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/cloudflare@2584

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/contentful-to-portable-text@2584

emdash

npm i https://pkg.pr.new/emdash-cms/emdash@2584

create-emdash

npm i https://pkg.pr.new/emdash-cms/emdash/create-emdash@2584

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/gutenberg-to-portable-text@2584

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-cli@2584

@emdash-cms/plugin-types

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-types@2584

@emdash-cms/registry-client

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-client@2584

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-lexicons@2584

@emdash-cms/registry-moderation

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-moderation@2584

@emdash-cms/registry-verification

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-verification@2584

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/sandbox-workerd@2584

@emdash-cms/x402

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/x402@2584

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-ai-moderation@2584

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-atproto@2584

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-audit-log@2584

@emdash-cms/plugin-color

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-color@2584

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-embeds@2584

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-field-kit@2584

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-forms@2584

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-webhook-notifier@2584

commit: 8858dd3

@khoinguyenpham04 khoinguyenpham04 added the bot:review Trigger an emdashbot code review on this PR label Aug 26, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This remains a coherent, scope-disciplined PR1 for flat media folders. The backend architecture follows existing EmDash conventions: cursor pagination for folder lists, ApiResult handlers, requirePerm/requireOwnerPerm authorization, FK onDelete("set null") for safe folder deletion, a D1-compatible migration (072_media_folders) with rollback coverage, and broad tests across migrations, repositories, handlers, routes, OpenAPI, and the typed client. The folder routes are wired correctly before /media/[id] so static /media/folders paths take precedence.

Re-review status

  • The expanded changeset now meets the .changeset/README.md standard: it leads with the released behavior, names the affected surface (REST and typed client), explains usage, permissions, defaults, and safe-deletion semantics.
  • The prior note about accepting the query-only unfiled sentinel in PUT /media/:id update bodies is now documented as explicit behavior in the REST reference, so I’m not treating it as an unaddressed bug.
  • The prior typed-client concern is still open: the client-side MediaItem interface is structurally wrong for the API responses returned by mediaList, mediaGet, and mediaUpload. This is the only remaining code-level issue.

What I checked

  • Diff files, full source of changed handlers/routes/repositories/schemas/client.
  • rbac.ts permission mapping (media:read, media:edit_own, media:edit_any, media:upload, token scopes).
  • Migration registration in runner.ts and the migration’s up/down/Idempotency handling.
  • database/types.ts for MediaFolderTable and folder_id on MediaTable.
  • Route injection order in astro/integration/routes.ts.
  • openapi/document.ts for folder paths and response schemas.
  • Updated REST docs (rest-api.mdx) and media-library guide.
  • Unit and integration tests for folders, pagination, routes, and client serialization.

Headline conclusion

No security, data-loss, or authorization blockers. The foundation is clean and well-tested. Before the feature ships, the typed-client MediaItem shape needs to be reconciled with the actual REST MediaItem schemas.

One process caveat from the prior review carries into this round: AGENTS.md requires a maintainer-approved Discussion for a feature. The PR description correctly notes that folder-specific approval is still needed and that this must not merge until it exists. I have not made that a line finding because the author already flags it.


Findings

  • [needs fixing] packages/core/src/client/index.ts:187-191

    The client-side MediaItem interface does not match the REST API’s mediaItemSchema and mediaListReadItemSchema, which this PR’s new folder-aware methods (mediaList, mediaGet, mediaSetFolder, mediaUpload) all return or re-use. Specifically:

    • The API returns storageKey, but this type uses key.
    • The API returns size as nullable, but this type types it as number.
    • The API always returns status, authorId, contentHash, blurhash, and dominantColor, which are absent here.
    • folderId is optional (?) even though every media response now includes it as string | null.
    • updatedAt is present here but is not part of the API media schema.

    This makes the typed client misleading for consumers trying to use the new folder fields or any other media metadata. The type should be aligned with the canonical API shape (with url and usage kept optional because they’re added by list/get envelopes).

    /** Media item */
    export interface MediaItem {
    	id: string;
    	filename: string;
    	mimeType: string;
    	size: number | null;
    	width: number | null;
    	height: number | null;
    	alt: string | null;
    	caption: string | null;
    	storageKey: string;
    	status: "pending" | "ready" | "failed";
    	contentHash: string | null;
    	blurhash: string | null;
    	dominantColor: string | null;
    	createdAt: string;
    	authorId: string | null;
    	folderId: string | null;
    	url?: string;
    	usage?: MediaUsageSummary;
    }
    

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Aug 26, 2026
@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-rereview Author pushed changes since the last review labels Aug 26, 2026
Comment thread .changeset/flat-media-folders.md
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Aug 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants