From 28b5353a0f1ee034da2e06d0fbd0ed06263c9b29 Mon Sep 17 00:00:00 2001 From: Boaz Poolman Date: Sat, 20 Dec 2025 15:53:40 +0100 Subject: [PATCH 1/2] feat: new configuration option called 'router_use_controllers' --- .changeset/short-rivers-fly.md | 6 ++ packages/core/server/config.ts | 2 + packages/core/server/controllers/core.ts | 60 ++++++++++++++++++- .../configuration/router-use-controllers.md | 22 +++++++ packages/docs/sidebars.ts | 1 + 5 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 .changeset/short-rivers-fly.md create mode 100644 packages/docs/docs/configuration/router-use-controllers.md diff --git a/.changeset/short-rivers-fly.md b/.changeset/short-rivers-fly.md new file mode 100644 index 00000000..09b1e37e --- /dev/null +++ b/.changeset/short-rivers-fly.md @@ -0,0 +1,6 @@ +--- +"strapi-plugin-webtools": minor +"docs": minor +--- + +feat: new configuration option called 'router_use_controllers' diff --git a/packages/core/server/config.ts b/packages/core/server/config.ts index f98761aa..d2fb660e 100644 --- a/packages/core/server/config.ts +++ b/packages/core/server/config.ts @@ -6,6 +6,7 @@ export interface Config { website_url: string; default_pattern: string, unique_per_locale: boolean, + router_use_controllers: boolean, slugify: (fieldValue: string) => string, } @@ -14,6 +15,7 @@ const config: { validator: () => void } = { default: { + router_use_controllers: false, website_url: null, default_pattern: '/[pluralName]/[documentId]', slugify: (fieldValue) => kebabCase(deburr(toLower(fieldValue))), diff --git a/packages/core/server/controllers/core.ts b/packages/core/server/controllers/core.ts index e92472aa..150c2c61 100644 --- a/packages/core/server/controllers/core.ts +++ b/packages/core/server/controllers/core.ts @@ -5,6 +5,56 @@ import { Schema } from '@strapi/strapi'; import { getPluginService } from '../util/getPluginService'; import { sanitizeOutput } from '../util/sanitizeOutput'; +type EntityResponse = { data: {}, meta: {} }; + +const routerWithControllers = async (ctx: Context) => { + const { path, ...searchQuery } = ctx.query; + + // Find related entity by path. + const { entity, contentType } = await getPluginService('url-alias').findRelatedEntity(path as string, { + ...searchQuery, + fields: ['documentId'], + }); + + const isSingleType = strapi.contentTypes[contentType].kind === 'singleType'; + let controllerEntity: EntityResponse = null; + + // Query the full entity using the content type controller. + if (isSingleType) { + controllerEntity = await strapi.controllers[contentType].find(ctx, async () => {}) as + EntityResponse; + } else { + controllerEntity = await strapi.controllers[contentType].findOne({ + ...ctx, + query: { + ...ctx.query, + }, + params: { + ...ctx.params as {}, + id: entity.documentId, + }, + }, async () => {}) as EntityResponse; + } + + if (!controllerEntity) { + ctx.notFound(); + return null; + } + + // Add content type to response. + const responseEntity = { + data: { + ...controllerEntity.data, + contentType, + }, + meta: { + ...controllerEntity.meta, + }, + }; + + return responseEntity; +}; + /** * Router controller */ @@ -14,13 +64,17 @@ export default { const { path, ...searchQuery } = ctx.query; const { auth } = ctx.state; - const { entity, contentType } = await getPluginService('url-alias').findRelatedEntity(path as string, searchQuery); + const routerUseControllers = strapi.config.get('plugin::webtools.router_use_controllers', false); - if (!entity) { - ctx.notFound(); + if (routerUseControllers) { + const entity = await routerWithControllers(ctx); + ctx.body = entity; return; } + // Find related entity by path. + const { entity, contentType } = await getPluginService('url-alias').findRelatedEntity(path as string, searchQuery); + // Check 'find' permissions for the content type we're querying. // eslint-disable-next-line @typescript-eslint/no-unsafe-argument await strapi.auth.verify(auth, { scope: [`${contentType}.find`] }); diff --git a/packages/docs/docs/configuration/router-use-controllers.md b/packages/docs/docs/configuration/router-use-controllers.md new file mode 100644 index 00000000..96525d30 --- /dev/null +++ b/packages/docs/docs/configuration/router-use-controllers.md @@ -0,0 +1,22 @@ +--- +sidebar_label: 'Router use controllers' +displayed_sidebar: webtoolsSidebar +slug: /configuration/router-use-controllers +--- + +# Router use controllers + +The [Webtools Router](/api/rest#router) endpoint has an option to make use of the core controllers of your content types. That means that you can extend your controllers as you're used to and the result will be returned by the Router endpoint by of Webtools. + +:::note +To make use of this feature you will need to enable the `findOne` permission of the specific content type. +::: + +In the future this might become the default behavior but that will cause a breaking change in the current behavior. + +| Name | Details | +| ---- | ------- | +| Key | `router_use_controllers` | +| Required | false | +| Type | boolean | +| Default | false | diff --git a/packages/docs/sidebars.ts b/packages/docs/sidebars.ts index 5cd725e5..23814748 100644 --- a/packages/docs/sidebars.ts +++ b/packages/docs/sidebars.ts @@ -80,6 +80,7 @@ const sidebars = { items: [ "configuration/introduction", "configuration/default-pattern", + "configuration/router-use-controllers", "configuration/website-url", "configuration/slugify", "configuration/unique-per-locale", From 342f269949e114a9435f93221982d6b8bf86f3d5 Mon Sep 17 00:00:00 2001 From: Boaz Poolman Date: Sat, 20 Dec 2025 15:56:35 +0100 Subject: [PATCH 2/2] fix: add back the default 404 response from the router endpoint --- packages/core/server/controllers/core.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/core/server/controllers/core.ts b/packages/core/server/controllers/core.ts index 150c2c61..68f34419 100644 --- a/packages/core/server/controllers/core.ts +++ b/packages/core/server/controllers/core.ts @@ -75,6 +75,11 @@ export default { // Find related entity by path. const { entity, contentType } = await getPluginService('url-alias').findRelatedEntity(path as string, searchQuery); + if (!entity) { + ctx.notFound(); + return; + } + // Check 'find' permissions for the content type we're querying. // eslint-disable-next-line @typescript-eslint/no-unsafe-argument await strapi.auth.verify(auth, { scope: [`${contentType}.find`] });