121 lines
3.6 KiB
Go
121 lines
3.6 KiB
Go
// Package moduleregistry implementiert Core API-02: die Registry, in der
|
|
// sich Fachmodule (DMS, Mail, weitere) mit Metadaten eintragen, gekoppelt an
|
|
// die Aktivierungspruefung aus LIC-02 (Feature-Flags). Zusaetzlich
|
|
// authentifiziert die Registry Modul-Instanzen selbst ueber ein bei
|
|
// Provisionierung ausgestelltes Service-Credential.
|
|
package moduleregistry
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"gitea.perlbach24.de/scripte/nexarch/internal/flag"
|
|
)
|
|
|
|
var (
|
|
ErrMissingName = errors.New("moduleregistry: name darf nicht leer sein")
|
|
ErrMissingVersion = errors.New("moduleregistry: version darf nicht leer sein")
|
|
ErrModuleNotFound = errors.New("moduleregistry: modul nicht registriert")
|
|
)
|
|
|
|
type Module struct {
|
|
Name string
|
|
Version string
|
|
RequiredFlags []string
|
|
}
|
|
|
|
type Registry struct {
|
|
pool *pgxpool.Pool
|
|
flags *flag.Service
|
|
}
|
|
|
|
func NewRegistry(pool *pgxpool.Pool, flags *flag.Service) *Registry {
|
|
return &Registry{pool: pool, flags: flags}
|
|
}
|
|
|
|
// Register traegt ein Modul mit Name, Version und benoetigten Feature-Flags
|
|
// ein (Akzeptanzkriterium 1). Fehlende Pflichtangaben werden abgewiesen
|
|
// (Akzeptanzkriterium 1 / Pruefung 2). Erneutes Register desselben Namens
|
|
// aktualisiert Version/Flags (Redeploy-Fall).
|
|
func (r *Registry) Register(ctx context.Context, name, version string, requiredFlags []string) (Module, error) {
|
|
if name == "" {
|
|
return Module{}, ErrMissingName
|
|
}
|
|
if version == "" {
|
|
return Module{}, ErrMissingVersion
|
|
}
|
|
if requiredFlags == nil {
|
|
requiredFlags = []string{}
|
|
}
|
|
|
|
_, err := r.pool.Exec(ctx, `
|
|
INSERT INTO modules (name, version, required_flags, registered_at)
|
|
VALUES ($1, $2, $3, now())
|
|
ON CONFLICT (name) DO UPDATE SET version = $2, required_flags = $3, registered_at = now()
|
|
`, name, version, requiredFlags)
|
|
if err != nil {
|
|
return Module{}, fmt.Errorf("modul registrieren: %w", err)
|
|
}
|
|
return Module{Name: name, Version: version, RequiredFlags: requiredFlags}, nil
|
|
}
|
|
|
|
func (r *Registry) Get(ctx context.Context, name string) (Module, error) {
|
|
var m Module
|
|
m.Name = name
|
|
err := r.pool.QueryRow(ctx, `
|
|
SELECT version, required_flags FROM modules WHERE name = $1
|
|
`, name).Scan(&m.Version, &m.RequiredFlags)
|
|
if err != nil {
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return Module{}, ErrModuleNotFound
|
|
}
|
|
return Module{}, fmt.Errorf("modul lesen: %w", err)
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
// List liefert alle registrierten Module (Akzeptanzkriterium 3: ueber API
|
|
// abfragbar, z.B. fuer Statusseite/Lizenzoberflaeche).
|
|
func (r *Registry) List(ctx context.Context) ([]Module, error) {
|
|
rows, err := r.pool.Query(ctx, `SELECT name, version, required_flags FROM modules ORDER BY name`)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("module auflisten: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var out []Module
|
|
for rows.Next() {
|
|
var m Module
|
|
if err := rows.Scan(&m.Name, &m.Version, &m.RequiredFlags); err != nil {
|
|
return nil, fmt.Errorf("modul lesen: %w", err)
|
|
}
|
|
out = append(out, m)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
// IsActive prueft, ob ein registriertes Modul fuer einen Tenant aktiviert
|
|
// ist: registriert UND alle benoetigten Feature-Flags sind fuer diesen
|
|
// Tenant aktiv (Akzeptanzkriterium 2). Ein nicht registriertes Modul gilt
|
|
// immer als nicht aktiv.
|
|
func (r *Registry) IsActive(ctx context.Context, tenantSlug, moduleName string) (bool, error) {
|
|
m, err := r.Get(ctx, moduleName)
|
|
if err != nil {
|
|
if errors.Is(err, ErrModuleNotFound) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
for _, flagKey := range m.RequiredFlags {
|
|
if !r.flags.IsEnabled(ctx, tenantSlug, flagKey) {
|
|
return false, nil
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|