Skip to content

feat(api): pages and widgets on api/2 - #3133

Open
rubenvdlinde wants to merge 2 commits into
feat/noid/grid-widget-lifecyclefrom
feat/noid/grid-pages-api
Open

rubenvdlinde wants to merge 2 commits into
feat/noid/grid-widget-lifecyclefrom
feat/noid/grid-pages-api

Conversation

@rubenvdlinde

@rubenvdlinde rubenvdlinde commented Oct 22, 2026 •

Copy link
Copy Markdown

Part of #3099, on top of #3132 (widget lifecycle). This adds the five endpoints from #3100. They're routed with #[ApiRoute] on the controller, like the newer public endpoints, so routes.php doesn't change.

Size: 3738 lines: 773 code, 677 tests, and 2288 generated OpenAPI (openapi.json and openapi.ts) in a separate commit you can skip.

Endpoint What it does Who may
GET /api/2/contexts/{contextId}/pages/{pageId} A page with its widgets Read access to the application
POST /api/2/contexts/{contextId}/pages Add a grid page (title, icon, description, technical name) Manage access
PATCH /api/2/contexts/{contextId}/pages/{pageId} Change the page, its widgets, or both Manage access
DELETE /api/2/contexts/{contextId}/pages/{pageId} Delete a grid page Manage access
GET /api/2/widget-types The widget types and their settings schemas Any logged-in user
  • Validation. Every widget's settings are checked against its type's schema. Unknown settings are dropped and missing ones get their default.
  • Data widgets. A data widget may only point at a table or view of its own application; anything else is refused with a 400.
  • Technical names. Leave it out and the page gets page_<id>. An invalid one, or one already used in the application, gives a 400.
  • Icons must be a known icon name; anything else gives a 400.
  • The start page can't be deleted (400).
  • Limits. At most 100 widgets per page. Adding pages is limited to 20 a minute per user, changing a page to 60.
  • Two saves at the same time that collide give a 400 asking to load the page again, not a 500.
  • Someone outside the application gets a 404, as with the other application endpoints.

OpenAPI and the TypeScript types are regenerated, in a separate commit.

A data widget's source may also be given as {nodeType, nodeId} of a table or view that isn't in the application yet. The server then adds it to the application first, after the same permission check as editing the application (403 if you can't manage that table). Like any table added to an application, it also appears on the start page, and it stays in the application when the widget is removed later.

How it was tested

On Nextcloud 34 with PostgreSQL:

  • PageServiceTest, including that someone outside the application gets a 404 before any permission check or write
  • ContextPageControllerTest: 400, 403 and 404, and that every endpoint declares its route
  • changing or deleting a page of another application gives a 404
  • two saves colliding
  • WidgetServiceTest and GridWidgetTypesTest
  • the full unit suite on the top of the stack (feat(contexts): carry the menu through export and import #3138), on PostgreSQL and SQLite: 665 tests
  • composer cs:check, psalm and rector, all clean

By hand on a test instance:

  • The owner can add, change and read a page, including adding a table through a widget.
  • A widget title can be cleared.
  • A table from outside the application gives a 400, and so does deleting the start page.
  • A read-only sharee can read (200) and gets a 403 on every change.
  • Someone outside the application gets a 404 on all four page endpoints.
  • Deleting a page leaves no widget or source rows behind.

🖼️ Screenshots

Backend only, no UI changes. The frontend follows in separate pull requests.

🏁 Checklist

  • ⛑️ Tests (unit and/or integration) are included or not needed
  • 🔙 Backport requests are created or not needed: /backport to stableX.X
  • 📅 Milestone is set
  • 🌸 PR title is meaningful (if it should be in the changelog: is it meaningful to users?)

🤖 AI (if applicable)

  • The content of this PR was partly or fully generated using AI

Comment thread appinfo/routes.php Outdated
Comment on lines +163 to +167
['name' => 'ContextPage#create', 'url' => '/api/2/contexts/{contextId}/pages', 'verb' => 'POST'],
['name' => 'ContextPage#show', 'url' => '/api/2/contexts/{contextId}/pages/{pageId}', 'verb' => 'GET'],
['name' => 'ContextPage#update', 'url' => '/api/2/contexts/{contextId}/pages/{pageId}', 'verb' => 'PATCH'],
['name' => 'ContextPage#destroy', 'url' => '/api/2/contexts/{contextId}/pages/{pageId}', 'verb' => 'DELETE'],
['name' => 'ContextPage#widgetTypes', 'url' => '/api/2/widget-types', 'verb' => 'GET'],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

As far as I see, new routes are now added in the controllers, not in the routes.php.

@rubenvdlinde
rubenvdlinde force-pushed the feat/noid/grid-pages-api branch from daeab7e to a812d77 Compare October 24, 2026 08:09
Read, add, change and delete grid pages with their widgets, and list
the widget types with their settings schemas. Reading needs read
access to the application, changes need manage access. Page creation
is limited to 20 a minute per user.

Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: Ruben van der Linde <ruben@conduction.nl>
Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: Ruben van der Linde <ruben@conduction.nl>
@rubenvdlinde
rubenvdlinde force-pushed the feat/noid/grid-pages-api branch from a812d77 to e241b46 Compare October 26, 2026 09:24
@rubenvdlinde
rubenvdlinde marked this pull request as ready for review October 26, 2026 09:25
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.

3 participants