feat(views): no-code REST endpoints and public embeds over document collections - #1060
Open
mmcintosh wants to merge 2 commits into
Open
feat(views): no-code REST endpoints and public embeds over document collections#1060mmcintosh wants to merge 2 commits into
mmcintosh wants to merge 2 commits into
Conversation
…over document collections Lets an admin turn any document-model collection into a filtered, sorted, paginated REST endpoint without writing code: pick a collection, build filter rules through a visual builder (with a raw-JSON escape hatch for anything the builder can't express), choose columns, and get a live GET /api/views/:name JSON/CSV endpoint. A view can also be published as a chromeless public page at /v/:path, or shared via an unguessable one-off token. Storage: two plugin-owned tables (views, view_displays), provisioned idempotently from the plugin's onBoot — no new numbered migration, matching the app's collections-are-code-only convention. No FK into core tables. Query engine: views compile down to the same document-model substrate every other read in the app goes through (DocumentQueryProvider -> DocumentRepository), so a view's read path gets the same ACL enforcement, indexed field lookups, and keyset pagination as any other document read. Filter field names are strictly allowlisted against the collection's actual schema — never string-concatenated into SQL. Security: - Every view and every published display is tenant-scoped end to end; two different tenants can use the same view name or the same embed path without colliding. - Public exposure is opt-in and double-gated: the public JSON/CSV endpoint and the public embed page both require the specific view/display to be marked public AND the backing collection to grant public read. Drafts never leak — both public paths force published-only regardless of what a caller claims. - Anonymous public reads honor per-document deny ACL overrides via the app's existing permissions service, rather than the plugin reimplementing access control. - Share tokens are 128-bit cryptographically random and rate-limited (60 req/min/IP) on both public read paths. Testing: 66 real-SQLite tests (tenant isolation, public-exposure gating, cache-key isolation), tsc clean, two E2E specs (108-views-tenant-scoping, 109-views-plugin) covering admin builder flows, public API invariants, and cross-tenant isolation end to end.
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
Views lets an admin turn any document-model collection into a filtered, sorted, paginated REST endpoint — no code, no migration, no deploy. Pick a collection, build filter rules through a visual builder (with an OR-group escape hatch for anything the builder can't express), choose columns, and the result is a live
GET /api/views/:nameJSON/CSV endpoint. Optionally, a view can also be published as a chromeless public page at/v/:path, or shared via an unguessable one-off token — a stakeholder-facing table or dashboard slice with zero admin exposure.Today, exposing a filtered slice of content as an API or a public page means writing a route by hand — new endpoint, new query logic, new caching, new access-control wiring, each one a fresh place to get tenant-scoping or public-exposure gating wrong. Views collapses that into a form: build the filter once, and you have (a) a governed JSON/CSV API for internal tooling or partner integrations, and (b) an optional, cacheable public page for anything that used to mean "ask an engineer for a one-off report."
Changes
views,view_displays), provisioned idempotently from the plugin'sonBoot— no new numbered migration, matching this repo's collections-are-code-only convention. No FK into core tables.DocumentQueryProvider→DocumentRepository), so a view's read path gets the same ACL enforcement, indexed field lookups, and keyset pagination as any other document read.denyoverrides via the app's existing ACL resolver, rather than the plugin re-implementing access control./admin/plugins/:id) had an About paragraph and an unused Routes-reference extension point. Added a second, also-reusable section — How to Use, a step-by-step walkthrough — and populated Views' own manifest with both a full route reference and a 6-step usage guide (screenshots below).Testing
Unit Tests
E2E Tests
108-views-tenant-scoping.spec.ts(two tenants creating same-named views, cross-tenant edit rejection, two tenants publishing at the identical path both resolving correctly) and109-views-plugin.spec.ts(admin builder, public API invariants, date-filter regression).Security review: verified tenant isolation across all 12 admin routes, both public read endpoints (draft/private/non-public-collection leak checks, share-token unguessability), and SQL-injection safety in the filter engine. No vulnerabilities found.
Screenshots/Videos
Building a view — pick a collection, get a live preview immediately:

A saved view, with its live API preview:

The Views list:

The actual public REST response —

GET /api/views/published-posts, unauthenticated:Publishing a view as a public embed — pick display columns, publish, get a share link:

The live public embed page —

/v/published-posts, zero admin chrome:The filter builder — plain-English operator labels, live-filtering

price < 50against 100 seeded products:In-plugin "How to Use" guide (Plugins → Views → Information):

In-plugin Routes reference (same tab, right below):

Checklist