From 02a7d2fe900cb2dfbca55bef0a83b53ba4dcc8e6 Mon Sep 17 00:00:00 2001 From: fs Date: Thu, 26 Mar 2026 14:02:42 +0100 Subject: [PATCH] feat(api-docs): extract API docs viewer into standalone module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the Swagger UI viewer from core (pages/admin/api-docs/) into modules/api-docs/ as a self-contained module. Create module manifest with routes, permission, sidebar.admin_nav_item slot, asset group, and authorization policy. Remove API docs ability from core OperationsAuthorizationPolicy and UiCapabilityMap. Remove hardcoded sidebar nav item — now contributed via module slot. Add admin-automation group meta to sidebar template for module nav items. Co-Authored-By: Claude Opus 4.6 (1M context) --- config/assets.php | 3 - config/modules.php | 2 +- .../Access/OperationsAuthorizationPolicy.php | 3 - lib/Service/Access/UiCapabilityMap.php | 1 - modules/api-docs/i18n/default_de.json | 5 ++ modules/api-docs/i18n/default_en.json | 5 ++ .../ApiDocs/ApiDocsAuthorizationPolicy.php | 37 +++++++++++ .../ApiDocs/ApiDocsContainerRegistrar.php | 17 +++++ modules/api-docs/module.php | 66 +++++++++++++++++++ .../api-docs/pages}/api-docs/index().php | 3 +- .../pages}/api-docs/index(default).phtml | 2 +- .../api-docs/pages}/api-docs/spec().php | 6 +- .../web}/css/vendor-overrides/swagger-ui.css | 2 +- .../web}/js/pages/admin-api-docs-index.js | 0 templates/partials/app-main-aside.phtml | 15 ++--- 15 files changed, 142 insertions(+), 25 deletions(-) create mode 100644 modules/api-docs/i18n/default_de.json create mode 100644 modules/api-docs/i18n/default_en.json create mode 100644 modules/api-docs/lib/Module/ApiDocs/ApiDocsAuthorizationPolicy.php create mode 100644 modules/api-docs/lib/Module/ApiDocs/ApiDocsContainerRegistrar.php create mode 100644 modules/api-docs/module.php rename {pages/admin => modules/api-docs/pages}/api-docs/index().php (56%) rename {pages/admin => modules/api-docs/pages}/api-docs/index(default).phtml (77%) rename {pages/admin => modules/api-docs/pages}/api-docs/spec().php (66%) rename {web => modules/api-docs/web}/css/vendor-overrides/swagger-ui.css (98%) rename {web => modules/api-docs/web}/js/pages/admin-api-docs-index.js (100%) diff --git a/config/assets.php b/config/assets.php index 5d3d9eb..6d6b865 100644 --- a/config/assets.php +++ b/config/assets.php @@ -58,9 +58,6 @@ return [ 'page-layout' => [ 'css/layout/app-page-layout.css', ], - 'api-docs' => [ - 'css/vendor-overrides/swagger-ui.css', - ], 'docs' => [ 'css/pages/app-docs.css', ], diff --git a/config/modules.php b/config/modules.php index 244f298..ca48f2c 100644 --- a/config/modules.php +++ b/config/modules.php @@ -12,5 +12,5 @@ * Each entry must match a directory name under modules/ containing a module.php manifest. */ return [ - 'enabled_modules' => ['audit', 'addressbook', 'bookmarks', 'notifications'], + 'enabled_modules' => ['audit', 'addressbook', 'bookmarks', 'notifications', 'api-docs'], ]; diff --git a/lib/Service/Access/OperationsAuthorizationPolicy.php b/lib/Service/Access/OperationsAuthorizationPolicy.php index 0a529f7..f69ed54 100644 --- a/lib/Service/Access/OperationsAuthorizationPolicy.php +++ b/lib/Service/Access/OperationsAuthorizationPolicy.php @@ -12,7 +12,6 @@ final class OperationsAuthorizationPolicy implements AuthorizationPolicyInterfac public const ABILITY_ADMIN_MAIL_LOG_VIEW = 'ops.admin.mail_log.view'; public const ABILITY_ADMIN_STATS_VIEW = 'ops.admin.stats.view'; public const ABILITY_ADMIN_SYSTEM_INFO_VIEW = 'ops.admin.system_info.view'; - public const ABILITY_ADMIN_API_DOCS_VIEW = 'ops.admin.api_docs.view'; public const ABILITY_ADMIN_IMPORTS_TYPE_USERS = 'ops.admin.imports.type.users'; public const ABILITY_ADMIN_IMPORTS_TYPE_DEPARTMENTS = 'ops.admin.imports.type.departments'; public const ABILITY_ADMIN_USERS_ASSIGNMENTS_MANAGE = 'ops.admin.users.assignments.manage'; @@ -51,7 +50,6 @@ final class OperationsAuthorizationPolicy implements AuthorizationPolicyInterfac self::ABILITY_ADMIN_MAIL_LOG_VIEW, self::ABILITY_ADMIN_STATS_VIEW, self::ABILITY_ADMIN_SYSTEM_INFO_VIEW, - self::ABILITY_ADMIN_API_DOCS_VIEW, self::ABILITY_ADMIN_IMPORTS_TYPE_USERS, self::ABILITY_ADMIN_IMPORTS_TYPE_DEPARTMENTS, self::ABILITY_ADMIN_USERS_ASSIGNMENTS_MANAGE, @@ -94,7 +92,6 @@ final class OperationsAuthorizationPolicy implements AuthorizationPolicyInterfac self::ABILITY_ADMIN_MAIL_LOG_VIEW => $this->allowIfHas($actorUserId, PermissionService::MAIL_LOG_VIEW), self::ABILITY_ADMIN_STATS_VIEW => $this->allowIfHas($actorUserId, PermissionService::STATS_VIEW), self::ABILITY_ADMIN_SYSTEM_INFO_VIEW => $this->allowIfHas($actorUserId, PermissionService::SYSTEM_INFO_VIEW), - self::ABILITY_ADMIN_API_DOCS_VIEW => $this->allowIfHas($actorUserId, PermissionService::API_DOCS_VIEW), self::ABILITY_ADMIN_IMPORTS_TYPE_USERS => $this->allowIfHas($actorUserId, PermissionService::USERS_IMPORT), self::ABILITY_ADMIN_IMPORTS_TYPE_DEPARTMENTS => $this->allowIfHas($actorUserId, PermissionService::DEPARTMENTS_IMPORT), self::ABILITY_ADMIN_USERS_ASSIGNMENTS_MANAGE => $this->allowIfHas($actorUserId, PermissionService::USERS_UPDATE_ASSIGNMENTS), diff --git a/lib/Service/Access/UiCapabilityMap.php b/lib/Service/Access/UiCapabilityMap.php index e2b33e3..72f9cb3 100644 --- a/lib/Service/Access/UiCapabilityMap.php +++ b/lib/Service/Access/UiCapabilityMap.php @@ -21,7 +21,6 @@ final class UiCapabilityMap 'can_view_settings' => SettingsAuthorizationPolicy::ABILITY_ADMIN_SETTINGS_VIEW, 'can_view_imports' => OperationsAuthorizationPolicy::ABILITY_ADMIN_IMPORTS_VIEW, 'can_view_jobs' => OperationsAuthorizationPolicy::ABILITY_ADMIN_JOBS_VIEW, - 'can_view_api_docs' => OperationsAuthorizationPolicy::ABILITY_ADMIN_API_DOCS_VIEW, 'can_view_docs' => OperationsAuthorizationPolicy::ABILITY_ADMIN_DOCS_VIEW, 'can_view_mail_log' => OperationsAuthorizationPolicy::ABILITY_ADMIN_MAIL_LOG_VIEW, 'can_view_stats' => OperationsAuthorizationPolicy::ABILITY_ADMIN_STATS_VIEW, diff --git a/modules/api-docs/i18n/default_de.json b/modules/api-docs/i18n/default_de.json new file mode 100644 index 0000000..93f9153 --- /dev/null +++ b/modules/api-docs/i18n/default_de.json @@ -0,0 +1,5 @@ +{ + "API docs": "API-Dokumentation", + "API documentation": "API-Dokumentation", + "Can view API documentation": "Kann API-Dokumentation anzeigen" +} diff --git a/modules/api-docs/i18n/default_en.json b/modules/api-docs/i18n/default_en.json new file mode 100644 index 0000000..6c19b67 --- /dev/null +++ b/modules/api-docs/i18n/default_en.json @@ -0,0 +1,5 @@ +{ + "API docs": "API docs", + "API documentation": "API documentation", + "Can view API documentation": "Can view API documentation" +} diff --git a/modules/api-docs/lib/Module/ApiDocs/ApiDocsAuthorizationPolicy.php b/modules/api-docs/lib/Module/ApiDocs/ApiDocsAuthorizationPolicy.php new file mode 100644 index 0000000..99d9347 --- /dev/null +++ b/modules/api-docs/lib/Module/ApiDocs/ApiDocsAuthorizationPolicy.php @@ -0,0 +1,37 @@ +permissionService->userHas($actorUserId, self::PERMISSION_KEY)) { + return AuthorizationDecision::deny(403, 'forbidden'); + } + + return AuthorizationDecision::allow(); + } +} diff --git a/modules/api-docs/lib/Module/ApiDocs/ApiDocsContainerRegistrar.php b/modules/api-docs/lib/Module/ApiDocs/ApiDocsContainerRegistrar.php new file mode 100644 index 0000000..bf16c2d --- /dev/null +++ b/modules/api-docs/lib/Module/ApiDocs/ApiDocsContainerRegistrar.php @@ -0,0 +1,17 @@ +set(ApiDocsAuthorizationPolicy::class, static fn (AppContainer $c): ApiDocsAuthorizationPolicy => new ApiDocsAuthorizationPolicy( + $c->get(PermissionService::class) + )); + } +} diff --git a/modules/api-docs/module.php b/modules/api-docs/module.php new file mode 100644 index 0000000..d655e9c --- /dev/null +++ b/modules/api-docs/module.php @@ -0,0 +1,66 @@ + 'api-docs', + 'version' => '1.0.0', + 'enabled_by_default' => false, + 'load_order' => 20, + 'requires' => [], + + 'routes' => [ + ['path' => 'admin/api-docs', 'target' => 'api-docs'], + ['path' => 'admin/api-docs/spec', 'target' => 'api-docs/spec'], + ], + 'public_paths' => [], + 'container_registrars' => [ + \MintyPHP\Module\ApiDocs\ApiDocsContainerRegistrar::class, + ], + + 'ui_slots' => [ + 'sidebar.admin_nav_item' => [ + [ + 'key' => 'api-docs', + 'group' => 'admin-automation', + 'label' => 'API docs', + 'path' => 'admin/api-docs', + 'permission' => 'api_docs.view', + 'order' => 300, + ], + ], + ], + + 'authorization_policies' => [ + \MintyPHP\Module\ApiDocs\ApiDocsAuthorizationPolicy::class, + ], + + 'search_resources' => [], + + 'asset_groups' => [ + 'api-docs' => [ + 'modules/api-docs/css/vendor-overrides/swagger-ui.css', + ], + ], + + 'scheduler_jobs' => [], + 'layout_context_providers' => [], + 'session_providers' => [], + + 'permissions' => [ + [ + 'key' => 'api_docs.view', + 'description' => 'Can view API documentation', + 'active' => true, + 'is_system' => true, + ], + ], + + 'event_listeners' => [], + 'deactivation_handler' => null, + 'migrations_path' => null, + 'i18n_path' => 'i18n', +]; diff --git a/pages/admin/api-docs/index().php b/modules/api-docs/pages/api-docs/index().php similarity index 56% rename from pages/admin/api-docs/index().php rename to modules/api-docs/pages/api-docs/index().php index da36ce5..578d086 100644 --- a/pages/admin/api-docs/index().php +++ b/modules/api-docs/pages/api-docs/index().php @@ -1,10 +1,11 @@ - + diff --git a/pages/admin/api-docs/spec().php b/modules/api-docs/pages/api-docs/spec().php similarity index 66% rename from pages/admin/api-docs/spec().php rename to modules/api-docs/pages/api-docs/spec().php index babcc5e..51f9335 100644 --- a/pages/admin/api-docs/spec().php +++ b/modules/api-docs/pages/api-docs/spec().php @@ -1,14 +1,14 @@ $canViewJobs, 'withTenant' => false, ], - [ - 'label' => t('API docs'), - 'path' => 'admin/api-docs', - 'active' => navActive('admin/api-docs', true), - 'visible' => $canViewApiDocs, - 'withTenant' => false, - ], ], ], [ @@ -133,7 +125,7 @@ $adminNavGroups = [ 'visible' => $hasLogsSection, 'items' => [ [ - 'label' => t('Mail logs'), + 'label' => t('nav.logs.mail'), 'path' => 'admin/mail-log', 'active' => navActive('admin/mail-log', true), 'visible' => $canViewMailLog, @@ -199,6 +191,7 @@ if ($moduleNavItemSlots) { } // Known group metadata — modules declare their group key, we map to label + icon. $moduleGroupMeta = [ + 'admin-automation' => ['label' => t('Automation & integrations'), 'icon' => 'bi-gear-wide-connected'], 'admin-logs' => ['label' => t('Audit & logs'), 'icon' => 'bi-journal-text'], ]; foreach ($moduleGroupsByKey as $groupKey => $items) { @@ -209,7 +202,7 @@ if ($moduleNavItemSlots) { // Also add mail log to the logs group if it exists as a core item if ($groupKey === 'admin-logs' && $canViewMailLog) { array_unshift($items, [ - 'label' => t('Mail logs'), + 'label' => t('nav.logs.mail'), 'path' => 'admin/mail-log', 'active' => navActive('admin/mail-log', true), 'visible' => $canViewMailLog,