2
0

Plugin entry

This commit is contained in:
2026-01-16 22:38:01 -05:00
parent d724d1eb64
commit 0d36b892eb
2 changed files with 303 additions and 0 deletions

88
modules/plugins/plugin.go Normal file
View File

@@ -0,0 +1,88 @@
// Copyright 2026 MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT
package plugins
import (
"context"
"github.com/go-chi/chi/v5"
"xorm.io/xorm"
)
// Plugin defines the interface that all GitCaddy plugins must implement
type Plugin interface {
// Name returns the unique identifier for this plugin
Name() string
// Version returns the plugin version
Version() string
// Description returns a human-readable description
Description() string
// Init is called when the plugin is loaded
Init(ctx context.Context) error
// Shutdown is called when the server is shutting down
Shutdown(ctx context.Context) error
}
// DatabasePlugin is implemented by plugins that need database tables
type DatabasePlugin interface {
Plugin
// RegisterModels returns the models to be registered with the database
RegisterModels() []any
// Migrate runs any database migrations for this plugin
Migrate(ctx context.Context, x *xorm.Engine) error
}
// WebRoutesPlugin is implemented by plugins that add web UI routes
type WebRoutesPlugin interface {
Plugin
// RegisterWebRoutes adds routes to the web UI router
RegisterWebRoutes(m chi.Router)
}
// APIRoutesPlugin is implemented by plugins that add API routes
type APIRoutesPlugin interface {
Plugin
// RegisterAPIRoutes adds routes to the API router
RegisterAPIRoutes(m chi.Router)
}
// RepoRoutesPlugin is implemented by plugins that add per-repository routes
type RepoRoutesPlugin interface {
Plugin
// RegisterRepoWebRoutes adds routes under /{owner}/{repo}/
RegisterRepoWebRoutes(m chi.Router)
// RegisterRepoAPIRoutes adds routes under /api/v1/repos/{owner}/{repo}/
RegisterRepoAPIRoutes(m chi.Router)
}
// LicensedPlugin is implemented by plugins that require a license
type LicensedPlugin interface {
Plugin
// ValidateLicense checks if the plugin is properly licensed
ValidateLicense(ctx context.Context) error
// LicenseInfo returns information about the current license
LicenseInfo() *LicenseInfo
}
// LicenseInfo contains license details
type LicenseInfo struct {
Valid bool
Tier string // e.g., "solo", "pro", "team", "enterprise"
CustomerID string
ExpiresAt int64
GracePeriod bool // true if in grace period after expiry
}

215
modules/plugins/registry.go Normal file
View File

@@ -0,0 +1,215 @@
// Copyright 2026 MarketAlly. All rights reserved.
// SPDX-License-Identifier: MIT
package plugins
import (
"context"
"sync"
"code.gitea.io/gitea/modules/log"
"github.com/go-chi/chi/v5"
"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 chi.Router) {
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 chi.Router) {
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
func RegisterRepoWebRoutes(m chi.Router) {
registryLock.RLock()
defer registryLock.RUnlock()
for name, p := range registry {
if rp, ok := p.(RepoRoutesPlugin); ok {
log.Debug("Registering repo web routes for plugin: %s", name)
rp.RegisterRepoWebRoutes(m)
}
}
}
// RegisterRepoAPIRoutes registers per-repository API routes from all plugins
func RegisterRepoAPIRoutes(m chi.Router) {
registryLock.RLock()
defer registryLock.RUnlock()
for name, p := range registry {
if rp, ok := p.(RepoRoutesPlugin); ok {
log.Debug("Registering repo API routes for plugin: %s", name)
rp.RegisterRepoAPIRoutes(m)
}
}
}
// IsLicensed checks if a plugin is properly licensed
func IsLicensed(name string) bool {
registryLock.RLock()
defer registryLock.RUnlock()
p, exists := registry[name]
if !exists {
return false
}
lp, ok := p.(LicensedPlugin)
if !ok {
return true // Non-licensed plugins are always "licensed"
}
info := lp.LicenseInfo()
return info != nil && info.Valid
}
// GetLicenseInfo returns license info for a plugin
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
}
return lp.LicenseInfo()
}