Files
Pacerino b4509d12e7 [Backend][Frontend] Add host plugins with module config and schema-driven UI
Introduce per-host plugin support: Caddy module discovery, module
config endpoints, plugin schemas (basicauth, cloudflare) and the
host plugins API. On the frontend, add the Plugins page, plugin/host
plugin dialogs and schema-driven form fields.
2026-06-15 14:17:34 +02:00

106 lines
3.0 KiB
Go

package database
import (
"database/sql/driver"
"encoding/json"
"errors"
"gorm.io/gorm"
)
type Host struct {
gorm.Model
Domains string `json:"domains" validate:"required,domains"`
Matcher string `json:"matcher"`
Upstreams []Upstream
Plugins []HostPlugin
}
// HostPlugin is a per-host plugin configuration. Handler holds the rendered
// Caddy route handler JSON (secrets already hashed), which is injected into
// the host's route. ModuleID is the dotted Caddy module name.
type HostPlugin struct {
gorm.Model
HostID uint `json:"hostId" gorm:"index"`
ModuleID string `json:"moduleId" validate:"required"`
// Handler is the rendered route handler object applied to the route.
Handler JSON `json:"handler"`
Enabled bool `json:"enabled" gorm:"default:true"`
}
type Upstream struct {
gorm.Model
HostID uint `json:"hostId"`
Backend string `json:"backend" validate:"required,hostname_port"`
}
// JSON is a json.RawMessage that persists as TEXT in SQLite via the
// Valuer/Scanner interfaces.
type JSON json.RawMessage
// Value implements driver.Valuer.
func (j JSON) Value() (driver.Value, error) {
if len(j) == 0 {
return "null", nil
}
return string(j), nil
}
// Scan implements sql.Scanner.
func (j *JSON) Scan(value any) error {
if value == nil {
*j = JSON("null")
return nil
}
switch v := value.(type) {
case []byte:
*j = append((*j)[0:0], v...)
case string:
*j = append((*j)[0:0], v...)
default:
return errors.New("unsupported type for JSON column")
}
return nil
}
// MarshalJSON makes JSON serialise as raw JSON rather than a byte array.
func (j JSON) MarshalJSON() ([]byte, error) {
if len(j) == 0 {
return []byte("null"), nil
}
return j, nil
}
// UnmarshalJSON captures raw JSON as-is.
func (j *JSON) UnmarshalJSON(data []byte) error {
*j = append((*j)[0:0], data...)
return nil
}
// ModuleConfig stores a configuration fragment for a single Caddy module
// (plugin). Config is applied to the live Caddy config at Path via the admin
// API. ModuleID is the dotted Caddy module name, e.g. "dns.providers.cloudflare".
type ModuleConfig struct {
gorm.Model
// ModuleID is the dotted Caddy module name this fragment configures.
ModuleID string `json:"moduleId" gorm:"uniqueIndex" validate:"required"`
// Path is the admin API config path the fragment is patched into,
// e.g. "apps/tls/automation/policies". Relative to /config/.
Path string `json:"path" validate:"required"`
// Config is the JSON fragment patched into Path.
Config JSON `json:"config" validate:"required"`
// Enabled controls whether the fragment is applied on reconcile.
Enabled bool `json:"enabled" gorm:"default:true"`
}
type User struct {
gorm.Model
Name string `validate:"required"`
Email string `gorm:"unique" validate:"required,email"`
Secret string `json:"secret,omitempty"`
// Provider is the auth source for this user: "local" or "oidc".
Provider string `json:"provider" gorm:"default:local"`
// Subject is the OIDC subject identifier (sub claim), empty for local users.
Subject string `json:"subject,omitempty" gorm:"index"`
}