Add tenant-scoped folder organization to Media (#307)#1147
Open
rustynwac wants to merge 1 commit into
Open
Conversation
Enable Payload's native folder support on the Media collection so editors can organize assets into folders in the admin. Payload auto-generates a single shared folder collection with no notion of tenancy, so enabling the feature naively would let every avalanche center see and reassign every other center's folders. Fold the generated folder collection into the app's home-grown multi-tenancy via the (experimental) root `folders.collectionOverrides` hook: a required `tenant` relationship, the `filterByTenant` base list filter, and tenant-aware RBAC keyed on the `media` permission. Register the folder slug with the tenant deprovision hook so folders cascade-delete with their tenant (the folder tenant FK is NOT NULL, so orphaning would otherwise FK-error on delete). - Media: `folders: true` - New `src/folders` module: slug + tenant-scoping overrides - Deprovision: add folder slug to TENANT_SCOPED_COLLECTIONS - Seed upsert: exclude the folder slug (no contentHash field) - Migration add_media_folders + regenerated types Admin-only; no frontend or public-read change. Folders are not enabled on any other collection and existing media is not auto-sorted. Fixes #307 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016s7MJjeP8Mr3KRiRA22WwJ
Contributor
Migration Safety CheckFound 5 potential issues: 20260709_144026_add_media_folders.ts Warning (line 10): DELETE keyword detected - review for data loss FOREIGN KEY (\`parent_id\`) REFERENCES \`payload_folders\`(\`id\`) ON UPDATE no action ON DELETE cascadeWarning (line 26): DELETE keyword detected - review for data loss FOREIGN KEY (\`folder_id\`) REFERENCES \`payload_folders\`(\`id\`) ON UPDATE no action ON DELETE set null,Warning (line 27): DELETE keyword detected - review for data loss FOREIGN KEY (\`tenant_id\`) REFERENCES \`tenants\`(\`id\`) ON UPDATE no action ON DELETE set nullWarning (line 43): ALTER keyword detected - review for data loss await db.run(sql`ALTER TABLE \`media\` ADD \`folder_id\` integer REFERENCES payload_folders(id);`)Warning (line 46): ALTER keyword detected - review for data loss sql`ALTER TABLE \`payload_locked_documents_rels\` ADD \`payload_folders_id\` integer REFERENCES payload_folders(id);`,Review these patterns and add backup/restore logic if needed. See |
Contributor
|
Preview deployment: https://claudexmedia-folders-307.preview.avy-fx.org |
Contributor
Author
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.



Description
Enables Payload's native folder support on the Media collection so editors can organize assets into folders in the admin (the SAC use case in #307:
Bios/,BlogImages/, etc.).The catch is tenant isolation. Payload auto-generates a single shared folder collection (
payload-folders) that has no notion of tenancy and isn't covered by our tenant list filter — so simply flipping the feature on would let every avalanche center see and reassign every other center's folders. This PR folds that generated collection into the app's existing home-grown multi-tenancy so folders are strictly per-tenant.Related Issues
Fixes #307
Key Changes
src/collections/Media/index.ts—folders: true.src/folders/index.ts(new) — the rootfoldersconfig. Uses the (experimental)collectionOverrideshook to inject into the generated folder collection:tenantrelationship (via the sharedtenantField()),filterByTenantbase list filter (the folder-browsing view appliesbaseListFilter, so this scopes folder browsing/selection to the selected tenant),accessByTenantRole('media')— folders exist to organize media, so media permissions govern them (replaces Payload's permissive default of "any logged-in user").src/collections/Tenants/hooks/deprovisionBeforeDelete.ts— add the folder slug toTENANT_SCOPED_COLLECTIONS. The folder→tenant FK isNOT NULLwithON DELETE set null, so without this, deleting a tenant would FK-error; deleting the folders first avoids it.src/endpoints/seed/upsert.ts— excludepayload-foldersfrom the seed upsert helper's collection union (folders have nocontentHashfield and are never seeded through it).add_media_folders+ regeneratedpayload-types.ts.The root
foldersconfig is marked experimental upstream ("may change in minor versions") — worth noting for future Payload upgrades.How to test
pnpm seedthen openlocalhost:3000/admin, Media → Browse by Folder.Automated:
pnpm test— 528 passing, incl. new__tests__/server/folders.server.test.ts(verifies the override injects the tenant field, base list filter, and RBAC, and that the folder slug is registered for deprovisioning).pnpm tsc,pnpm lint,pnpm fallow:audit— clean.pnpm seed:standalonecompletes.Migration Explanation
20260709_144026_add_media_foldersis additive: it creates thepayload_folders(+payload_folders_folder_type) tables and adds nullablefolder_idcolumns tomediaandpayload_locked_documents_rels. No data is dropped or rewritten inup().pnpm migrate:checkflags only false positives —ON DELETEclauses inside newFOREIGN KEYdefinitions and additiveALTER TABLE ADD COLUMN(same keyword-heuristic warnings as prior accepted migrations likeadd_galleries). Thedown()uses Payload's standard SQLite table-recreation pattern to reverse the added columns.Future enhancements / Questions
🤖 Generated with Claude Code
https://claude.ai/code/session_016s7MJjeP8Mr3KRiRA22WwJ
Generated by Claude Code
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.