Skip to content

feat(pages): add the page and widget endpoints for applications - #36

Open
rubenvdlinde wants to merge 3 commits into
stack/p1-page-modelfrom
stack/p2-page-api
Open

rubenvdlinde wants to merge 3 commits into
stack/p1-page-modelfrom
stack/p2-page-api

Conversation

@rubenvdlinde

@rubenvdlinde rubenvdlinde commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Second step of the page rework from nextcloud#3075 (reply: pages live on the application page model). Stacked on #35.

What changes

OCS api/2, new ContextPageController + PageService:

  • GET /api/2/contexts/{contextId}/pages, GET .../pages/{pageId}: pages with their widgets, read through PageContentHelper (existing content shows as full-width data widgets in order). Read access to the application (RequirePermission READ on the context).
  • POST .../pages (title, technicalName), PATCH .../pages/{pageId} (null keeps a value, an empty technical name removes it), DELETE .../pages/{pageId} (the start page gives 400). PUT .../pages/{pageId} stays the existing order endpoint, so retitling uses PATCH.
  • PUT .../pages/{pageId}/widgets: replaces the page's content rows in one transaction. Every widget needs a type and gridX/gridY/gridWidth/gridHeight within 12 columns; configuration and content are checked and sanitised by GridWidgetTypes. A data widget names nodeRelId of this application, or node: {nodeType, nodeId}; a new table or view is added as a node with read permission after the same canManageNodeById check ContextService::update uses. A foreign nodeRelId is 400, a node the user cannot manage is 403 (as in the application update). order is filled from the positions. Saving the start page works the same and makes it positioned.
  • GET /api/2/widget-types: the schemas, from GridWidgetTypes (header, text, data). The data widget has no content schema: its node is the row's node_rel_id.
  • Writes carry @CanManageContext and check canManageContextById again in the service before any write.
  • openapi.json and src/types/openapi/openapi.ts regenerated.

Technical names required: a page created without a technical name gets one from its title; an invalid or taken one is a 400 on create and rename, a rename can no longer remove it, and every page output carries technical_name.

Tests

Nextcloud 34 container, PostgreSQL 16, one file per call:

  • tests/unit/Service/PageServiceTest.php: OK (28 tests, 80 assertions), including non-manager writes, invalid titles and technical names, invalid widgets, a foreign nodeRelId and a node the user cannot manage.
  • tests/unit/Controller/ContextPageControllerTest.php: OK (7 tests, 15 assertions): 403 for a non-manager, 400 for a foreign nodeRelId and for deleting the start page, 404 for another application's page.
  • tests/unit/Service/GridWidgetTypesTest.php: OK (12 tests, 36 assertions)
  • tests/unit/Db/ContextMapperTest.php: OK (8 tests, 37 assertions); ContextService, PageContentHelper, PermissionsService tests OK.
  • cs:check clean, psalm No errors found!.
  • Manual on the instance: created a grid page, duplicate technical name 400, saved a header + a data widget for a table not in the application (became node 20) + an existing node, foreign nodeRelId 400, start page delete 400; a sharee with read access lists pages (200) and gets 403 on create and save.

Inherited: openapi-extractor warns that list endpoints have no limit parameter, as for every existing list endpoint.

Prepared in the Conduction fork for porting upstream by @remko48. Prepared with Claude Code.

api/2 endpoints to list and read the pages of an application, create,
retitle and delete grid pages, and save the widgets of a page with their
layout in one go. The start page cannot be deleted.

Widgets are checked against the schemas of their type. A data widget
names a node of the application, or a table or view that is added as a
node after the same manage check the application update uses. Saving
fills the content order from the positions. The widget types are served
from /api/2/widget-types.

Assisted-by: ClaudeCode:claude-opus-5-5
A page created without a technical name gets one made from its title,
unique within the application. An invalid or taken name is a 400, also when
another request takes it first, and a rename can no longer remove it.
Every page output carries its technical name.

Assisted-by: ClaudeCode:claude-opus-5-5
@rubenvdlinde

Copy link
Copy Markdown
Collaborator Author

Superseded by #47 (rebuilt on the nextcloud#3100 model, see #13). Kept open for reference until Remko has ported.

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.

1 participant