From 015318aa0af8f939cedcfceed05491afbc160e9a Mon Sep 17 00:00:00 2001 From: logikonline Date: Wed, 21 Jan 2026 10:35:56 -0500 Subject: [PATCH] feat(plugins): add PluginRouter interface for route registration Introduce PluginRouter interface to standardize plugin route registration and replace the previous 'any' type approach. Add WebRouterAdapter to wrap web.Router and handle path prefixes correctly. This provides a cleaner, type-safe API for plugins to register routes without needing to know about the underlying router implementation. --- modules/plugins/plugin.go | 8 ++-- modules/plugins/registry.go | 16 ++++++-- modules/plugins/router.go | 81 +++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 modules/plugins/router.go diff --git a/modules/plugins/plugin.go b/modules/plugins/plugin.go index aa6d0d3239..e2dceab830 100644 --- a/modules/plugins/plugin.go +++ b/modules/plugins/plugin.go @@ -61,12 +61,12 @@ type RepoRoutesPlugin interface { Plugin // RegisterRepoWebRoutes adds routes under /{owner}/{repo}/ - // The router is a *web.Router from the gitcaddy server - RegisterRepoWebRoutes(m any) + // The router is a PluginRouter that handles path prefixes correctly + RegisterRepoWebRoutes(r PluginRouter) // RegisterRepoAPIRoutes adds routes under /api/v1/repos/{owner}/{repo}/ - // The router is a *web.Router from the gitcaddy server - RegisterRepoAPIRoutes(m any) + // The router is a PluginRouter that handles path prefixes correctly + RegisterRepoAPIRoutes(r PluginRouter) } // LicensedPlugin is implemented by plugins that require a license diff --git a/modules/plugins/registry.go b/modules/plugins/registry.go index c60956d3e1..7c9cccd58a 100644 --- a/modules/plugins/registry.go +++ b/modules/plugins/registry.go @@ -151,27 +151,35 @@ func RegisterAPIRoutes(m any) { } // RegisterRepoWebRoutes registers per-repository web routes from all plugins -func RegisterRepoWebRoutes(m any) { +// The router parameter should implement WebRouter (e.g., *web.Router) +func RegisterRepoWebRoutes(router WebRouter) { registryLock.RLock() defer registryLock.RUnlock() + // Create adapter with empty prefix (routes are relative to current group) + adapter := NewWebRouterAdapter(router, "") + for name, p := range registry { if rp, ok := p.(RepoRoutesPlugin); ok { log.Debug("Registering repo web routes for plugin: %s", name) - rp.RegisterRepoWebRoutes(m) + rp.RegisterRepoWebRoutes(adapter) } } } // RegisterRepoAPIRoutes registers per-repository API routes from all plugins -func RegisterRepoAPIRoutes(m any) { +// The router parameter should implement WebRouter (e.g., *web.Router) +func RegisterRepoAPIRoutes(router WebRouter) { registryLock.RLock() defer registryLock.RUnlock() + // Create adapter with empty prefix (routes are relative to current group) + adapter := NewWebRouterAdapter(router, "") + for name, p := range registry { if rp, ok := p.(RepoRoutesPlugin); ok { log.Debug("Registering repo API routes for plugin: %s", name) - rp.RegisterRepoAPIRoutes(m) + rp.RegisterRepoAPIRoutes(adapter) } } } diff --git a/modules/plugins/router.go b/modules/plugins/router.go new file mode 100644 index 0000000000..49a01a1147 --- /dev/null +++ b/modules/plugins/router.go @@ -0,0 +1,81 @@ +// Copyright 2026 MarketAlly. All rights reserved. +// SPDX-License-Identifier: MIT + +package plugins + +import ( + "net/http" +) + +// PluginRouter is the interface plugins use to register routes. +// It abstracts away the underlying router implementation and ensures +// routes are registered with the correct path prefixes. +type PluginRouter interface { + // Get registers a GET route + Get(pattern string, handler http.HandlerFunc) + + // Post registers a POST route + Post(pattern string, handler http.HandlerFunc) + + // Put registers a PUT route + Put(pattern string, handler http.HandlerFunc) + + // Delete registers a DELETE route + Delete(pattern string, handler http.HandlerFunc) + + // Group creates a sub-router with a path prefix + Group(pattern string, fn func(PluginRouter)) +} + +// WebRouterAdapter wraps a web.Router to implement PluginRouter. +// This adapter ensures routes are registered with the correct path prefixes. +type WebRouterAdapter struct { + router WebRouter + pathPrefix string +} + +// WebRouter is the interface that web.Router implements +type WebRouter interface { + Get(pattern string, h ...any) + Post(pattern string, h ...any) + Put(pattern string, h ...any) + Delete(pattern string, h ...any) + Group(pattern string, fn func(), middlewares ...any) +} + +// NewWebRouterAdapter creates a new adapter for the given web.Router +func NewWebRouterAdapter(router WebRouter, pathPrefix string) *WebRouterAdapter { + return &WebRouterAdapter{ + router: router, + pathPrefix: pathPrefix, + } +} + +// Get implements PluginRouter.Get +func (a *WebRouterAdapter) Get(pattern string, handler http.HandlerFunc) { + a.router.Get(a.pathPrefix+pattern, handler) +} + +// Post implements PluginRouter.Post +func (a *WebRouterAdapter) Post(pattern string, handler http.HandlerFunc) { + a.router.Post(a.pathPrefix+pattern, handler) +} + +// Put implements PluginRouter.Put +func (a *WebRouterAdapter) Put(pattern string, handler http.HandlerFunc) { + a.router.Put(a.pathPrefix+pattern, handler) +} + +// Delete implements PluginRouter.Delete +func (a *WebRouterAdapter) Delete(pattern string, handler http.HandlerFunc) { + a.router.Delete(a.pathPrefix+pattern, handler) +} + +// Group implements PluginRouter.Group +func (a *WebRouterAdapter) Group(pattern string, fn func(PluginRouter)) { + subAdapter := &WebRouterAdapter{ + router: a.router, + pathPrefix: a.pathPrefix + pattern, + } + fn(subAdapter) +}