Plugin entry
This commit is contained in:
88
modules/plugins/plugin.go
Normal file
88
modules/plugins/plugin.go
Normal 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
215
modules/plugins/registry.go
Normal 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()
|
||||
}
|
||||
Reference in New Issue
Block a user