2
0
Files
gitcaddy-server/modules/plugins/registry.go
logikonline 015318aa0a
Some checks failed
Build and Release / Build Binaries (arm64, darwin, macos) (push) Has been cancelled
Build and Release / Build Binaries (amd64, windows, windows-latest) (push) Has been cancelled
Build and Release / Build Binary (linux/arm64) (push) Has been cancelled
Build and Release / Lint (push) Has been cancelled
Build and Release / Unit Tests (push) Has been cancelled
Build and Release / Build Binaries (amd64, linux, linux-latest) (push) Has been cancelled
Build and Release / Build Binaries (amd64, darwin, macos) (push) Has been cancelled
Build and Release / Create Release (push) Has been cancelled
Build and Release / Integration Tests (PostgreSQL) (push) Has been cancelled
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.
2026-01-21 10:35:56 -05:00

242 lines
5.7 KiB
Go

// Copyright 2026 MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT
package plugins
import (
"context"
"sync"
"code.gitcaddy.com/server/v3/modules/log"
"xorm.io/xorm"
)
var (
registry = make(map[string]Plugin)
registryLock sync.RWMutex
)
// Register adds a plugin to the registry
func Register(p Plugin) {
registryLock.Lock()
defer registryLock.Unlock()
name := p.Name()
if _, exists := registry[name]; exists {
log.Warn("Plugin %s is already registered, skipping", name)
return
}
registry[name] = p
log.Info("Plugin registered: %s v%s", name, p.Version())
}
// Get returns a plugin by name
func Get(name string) Plugin {
registryLock.RLock()
defer registryLock.RUnlock()
return registry[name]
}
// All returns all registered plugins
func All() []Plugin {
registryLock.RLock()
defer registryLock.RUnlock()
plugins := make([]Plugin, 0, len(registry))
for _, p := range registry {
plugins = append(plugins, p)
}
return plugins
}
// InitAll initializes all registered plugins
func InitAll(ctx context.Context) error {
registryLock.RLock()
defer registryLock.RUnlock()
for name, p := range registry {
log.Info("Initializing plugin: %s", name)
// Check license for licensed plugins
if lp, ok := p.(LicensedPlugin); ok {
if err := lp.ValidateLicense(ctx); err != nil {
log.Warn("Plugin %s license validation failed: %v", name, err)
// Continue but features may be limited
} else {
info := lp.LicenseInfo()
if info != nil && info.Valid {
log.Info("Plugin %s licensed: tier=%s", name, info.Tier)
}
}
}
if err := p.Init(ctx); err != nil {
log.Error("Failed to initialize plugin %s: %v", name, err)
return err
}
}
return nil
}
// ShutdownAll shuts down all registered plugins
func ShutdownAll(ctx context.Context) {
registryLock.RLock()
defer registryLock.RUnlock()
for name, p := range registry {
log.Info("Shutting down plugin: %s", name)
if err := p.Shutdown(ctx); err != nil {
log.Error("Failed to shutdown plugin %s: %v", name, err)
}
}
}
// RegisterModels registers database models from all plugins
func RegisterModels() []any {
registryLock.RLock()
defer registryLock.RUnlock()
var models []any
for _, p := range registry {
if dp, ok := p.(DatabasePlugin); ok {
models = append(models, dp.RegisterModels()...)
}
}
return models
}
// MigrateAll runs migrations for all database plugins
func MigrateAll(ctx context.Context, x *xorm.Engine) error {
registryLock.RLock()
defer registryLock.RUnlock()
for name, p := range registry {
if dp, ok := p.(DatabasePlugin); ok {
log.Info("Running migrations for plugin: %s", name)
if err := dp.Migrate(ctx, x); err != nil {
log.Error("Migration failed for plugin %s: %v", name, err)
return err
}
}
}
return nil
}
// RegisterWebRoutes registers web UI routes from all plugins
func RegisterWebRoutes(m any) {
registryLock.RLock()
defer registryLock.RUnlock()
for name, p := range registry {
if wp, ok := p.(WebRoutesPlugin); ok {
log.Debug("Registering web routes for plugin: %s", name)
wp.RegisterWebRoutes(m)
}
}
}
// RegisterAPIRoutes registers API routes from all plugins
func RegisterAPIRoutes(m any) {
registryLock.RLock()
defer registryLock.RUnlock()
for name, p := range registry {
if ap, ok := p.(APIRoutesPlugin); ok {
log.Debug("Registering API routes for plugin: %s", name)
ap.RegisterAPIRoutes(m)
}
}
}
// RegisterRepoWebRoutes registers per-repository web routes from all plugins
// 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(adapter)
}
}
}
// RegisterRepoAPIRoutes registers per-repository API routes from all plugins
// 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(adapter)
}
}
}
// IsLicensed checks if a plugin is properly licensed
// For licensed plugins without a valid license, this returns true because
// we default to Solo tier (free) when no license is present
func IsLicensed(name string) bool {
registryLock.RLock()
defer registryLock.RUnlock()
p, exists := registry[name]
if !exists {
return false
}
_, ok := p.(LicensedPlugin)
if !ok {
return true // Non-licensed plugins are always "licensed"
}
// Licensed plugins default to Solo tier (free) even without a license file
// So they are always considered "licensed" if registered
return true
}
// GetLicenseInfo returns license info for a plugin
// If the plugin doesn't have a license set, returns the default Solo license
func GetLicenseInfo(name string) *LicenseInfo {
registryLock.RLock()
defer registryLock.RUnlock()
p, exists := registry[name]
if !exists {
return nil
}
lp, ok := p.(LicensedPlugin)
if !ok {
return nil
}
info := lp.LicenseInfo()
if info == nil || !info.Valid {
// Default to Solo tier when no valid license
return DefaultSoloLicense()
}
return info
}
// GetLicenseLimits returns the license limits for a plugin
func GetLicenseLimits(name string) *LicenseLimits {
info := GetLicenseInfo(name)
if info == nil {
return nil
}
return GetLimitsForTier(info.Tier)
}