/app and JSON endpoints under /api. Both run inside a workspace, so every read and write has to be scoped to it.
A page under /app
src/RequestHandlers/NotesView.php
AppView prefixes /app, requires an authenticated user, enforces the email verification policy and renders ViewResponse through Twig. The example above is served at GET /app/notes.
The template
templates/notes.twig
See Frontend integration for the Alpine and asset side.
A workspace-scoped JSON endpoint
src/RequestHandlers/Api/ListNotesRequestHandler.php
src/RequestHandlers/Api/NotesApi.php
/api/notes, and they accept the same authentication as the rest of the User API: a session token from the app, or an API key. See the REST API overview.
The user and the workspace
UserMiddleware attaches both entities to the request:
- The workspace is the caller’s current workspace, or the one named by the
X-Workspace-Idheader when the caller is a member of it. - The workspace attribute is set only when the user belongs to it, so treat a missing workspace as an error rather than a reason to fall back.
Access control
Scope every query by workspace, and verify ownership whenever an ID comes from the request.src/AccessControls/NoteAccessControl.php
Presentation\AccessControls\*, with Presentation\AccessControls\Permission for finer-grained checks. Two conventions worth copying:
- A resource the caller may not see is a
404, not a403. - A resource that exists but is withheld by the user’s plan is a
403.
Feature flags and plan limits
Gate your feature behind an option so administrators can turn it off:Write endpoints
- Validate with
Presentation\Validation\Validator::validateRequest(). - Return
StatusCode::CREATEDwith the created resource, orEmptyResponsewithStatusCode::NO_CONTENTfor deletes. - Changes are flushed at the end of the request, so persist through a repository and return.