upload all

This commit is contained in:
2025-11-20 13:29:13 +01:00
parent daea26583b
commit c0e2df2430
35 changed files with 10016 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
{
"enabled": false,
"settings": {
"password": "test",
"username": "test"
}
}

View File

@@ -0,0 +1,4 @@
{
"enabled": true,
"settings": {}
}

View File

@@ -0,0 +1,4 @@
{
"enabled": false,
"settings": {}
}

9
backend/go.mod Normal file
View File

@@ -0,0 +1,9 @@
module certigo-addon-backend
go 1.21
require (
github.com/google/uuid v1.5.0
github.com/gorilla/mux v1.8.1
github.com/mattn/go-sqlite3 v1.14.18
)

6
backend/go.sum Normal file
View File

@@ -0,0 +1,6 @@
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=

2686
backend/main.go Normal file

File diff suppressed because it is too large Load Diff

BIN
backend/myapp Executable file

Binary file not shown.

586
backend/openapi.yaml Normal file
View File

@@ -0,0 +1,586 @@
openapi: 3.0.3
info:
title: Certigo Addon API
description: API für die Verwaltung von Spaces, FQDNs und Certificate Signing Requests (CSRs)
version: 1.0.0
contact:
name: Certigo Addon
servers:
- url: http://localhost:8080/api
description: Local development server
paths:
/health:
get:
summary: System Health Check
description: Prüft den Systemstatus des Backends
tags:
- System
responses:
'200':
description: System ist erreichbar
content:
application/json:
schema:
$ref: '#/components/schemas/HealthResponse'
/stats:
get:
summary: Statistiken abrufen
description: Ruft Statistiken über die Anzahl der Spaces, FQDNs und CSRs ab
tags:
- System
responses:
'200':
description: Statistiken erfolgreich abgerufen
content:
application/json:
schema:
$ref: '#/components/schemas/StatsResponse'
/spaces:
get:
summary: Alle Spaces abrufen
description: Ruft eine Liste aller Spaces ab
tags:
- Spaces
responses:
'200':
description: Liste der Spaces
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Space'
post:
summary: Space erstellen
description: Erstellt einen neuen Space
tags:
- Spaces
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateSpaceRequest'
responses:
'201':
description: Space erfolgreich erstellt
content:
application/json:
schema:
$ref: '#/components/schemas/Space'
'400':
description: Ungültige Anfrage
/spaces/{id}:
delete:
summary: Space löschen
description: Löscht einen Space. Wenn der Space FQDNs enthält, muss der Parameter deleteFqdns=true gesetzt werden.
tags:
- Spaces
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
- name: deleteFqdns
in: query
required: false
schema:
type: boolean
default: false
description: Wenn true, werden alle FQDNs des Spaces mitgelöscht
responses:
'200':
description: Space erfolgreich gelöscht
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'404':
description: Space nicht gefunden
'409':
description: Space enthält noch FQDNs
/spaces/{id}/fqdns/count:
get:
summary: FQDN-Anzahl abrufen
description: Ruft die Anzahl der FQDNs für einen Space ab
tags:
- FQDNs
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Anzahl der FQDNs
content:
application/json:
schema:
$ref: '#/components/schemas/CountResponse'
/spaces/{id}/fqdns:
get:
summary: Alle FQDNs eines Spaces abrufen
description: Ruft alle FQDNs für einen Space ab
tags:
- FQDNs
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Liste der FQDNs
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/FQDN'
'404':
description: Space nicht gefunden
post:
summary: FQDN erstellen
description: Erstellt einen neuen FQDN innerhalb eines Spaces
tags:
- FQDNs
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateFQDNRequest'
responses:
'201':
description: FQDN erfolgreich erstellt
content:
application/json:
schema:
$ref: '#/components/schemas/FQDN'
'400':
description: Ungültige Anfrage
'404':
description: Space nicht gefunden
'409':
description: FQDN existiert bereits in diesem Space
delete:
summary: Alle FQDNs eines Spaces löschen
description: Löscht alle FQDNs eines Spaces
tags:
- FQDNs
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Alle FQDNs erfolgreich gelöscht
content:
application/json:
schema:
$ref: '#/components/schemas/DeleteResponse'
/spaces/{id}/fqdns/{fqdnId}:
delete:
summary: FQDN löschen
description: Löscht einen einzelnen FQDN
tags:
- FQDNs
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
- name: fqdnId
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: FQDN erfolgreich gelöscht
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResponse'
'404':
description: FQDN nicht gefunden
/fqdns:
delete:
summary: Alle FQDNs global löschen
description: Löscht alle FQDNs aus allen Spaces. Erfordert confirm=true Query-Parameter.
tags:
- FQDNs
parameters:
- name: confirm
in: query
required: true
schema:
type: boolean
description: Muss true sein, um die Operation auszuführen
responses:
'200':
description: Alle FQDNs erfolgreich gelöscht
content:
application/json:
schema:
$ref: '#/components/schemas/DeleteResponse'
'400':
description: Bestätigung erforderlich
/csrs:
delete:
summary: Alle CSRs global löschen
description: Löscht alle CSRs aus allen Spaces. Erfordert confirm=true Query-Parameter.
tags:
- CSRs
parameters:
- name: confirm
in: query
required: true
schema:
type: string
description: Muss "true" sein, um die Operation auszuführen
example: "true"
responses:
'200':
description: Alle CSRs erfolgreich gelöscht
content:
application/json:
schema:
$ref: '#/components/schemas/DeleteResponse'
'400':
description: Bestätigung erforderlich
/spaces/{spaceId}/fqdns/{fqdnId}/csr:
post:
summary: CSR hochladen
description: Lädt einen CSR (Certificate Signing Request) im PEM-Format hoch
tags:
- CSRs
parameters:
- name: spaceId
in: path
required: true
schema:
type: string
format: uuid
- name: fqdnId
in: path
required: true
schema:
type: string
format: uuid
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required:
- csr
- spaceId
- fqdn
properties:
csr:
type: string
format: binary
description: CSR-Datei im PEM-Format
spaceId:
type: string
description: ID des Spaces
fqdn:
type: string
description: Name des FQDNs
responses:
'201':
description: CSR erfolgreich hochgeladen
content:
application/json:
schema:
$ref: '#/components/schemas/CSR'
'400':
description: Ungültige Anfrage oder ungültiges CSR-Format
'404':
description: Space oder FQDN nicht gefunden
get:
summary: CSR(s) abrufen
description: Ruft CSR(s) für einen FQDN ab. Mit latest=true wird nur der neueste CSR zurückgegeben.
tags:
- CSRs
parameters:
- name: spaceId
in: path
required: true
schema:
type: string
format: uuid
- name: fqdnId
in: path
required: true
schema:
type: string
format: uuid
- name: latest
in: query
required: false
schema:
type: boolean
default: false
description: Wenn true, wird nur der neueste CSR zurückgegeben
responses:
'200':
description: CSR(s) erfolgreich abgerufen
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/CSR'
- type: array
items:
$ref: '#/components/schemas/CSR'
'404':
description: FQDN nicht gefunden
components:
schemas:
HealthResponse:
type: object
properties:
status:
type: string
example: "ok"
message:
type: string
example: "Backend ist erreichbar"
time:
type: string
format: date-time
example: "2024-01-15T10:30:00Z"
StatsResponse:
type: object
properties:
spaces:
type: integer
example: 5
fqdns:
type: integer
example: 12
csrs:
type: integer
example: 7
Space:
type: object
properties:
id:
type: string
format: uuid
example: "550e8400-e29b-41d4-a716-446655440000"
name:
type: string
example: "Mein Space"
description:
type: string
example: "Beschreibung des Spaces"
createdAt:
type: string
format: date-time
example: "2024-01-15T10:30:00Z"
CreateSpaceRequest:
type: object
required:
- name
properties:
name:
type: string
example: "Mein Space"
description:
type: string
example: "Beschreibung des Spaces"
FQDN:
type: object
properties:
id:
type: string
format: uuid
example: "660e8400-e29b-41d4-a716-446655440000"
spaceId:
type: string
format: uuid
example: "550e8400-e29b-41d4-a716-446655440000"
fqdn:
type: string
example: "example.com"
description:
type: string
example: "Beschreibung des FQDN"
createdAt:
type: string
format: date-time
example: "2024-01-15T10:30:00Z"
CreateFQDNRequest:
type: object
required:
- fqdn
properties:
fqdn:
type: string
example: "example.com"
description:
type: string
example: "Beschreibung des FQDN"
Extension:
type: object
properties:
id:
type: string
example: "2.5.29.37"
oid:
type: string
example: "2.5.29.37"
name:
type: string
example: "X509v3 Extended Key Usage"
critical:
type: boolean
example: false
value:
type: string
example: "301406082b0601050507030106082b06010505070302"
description:
type: string
example: "TLS Web Server Authentication\n TLS Web Client Authentication"
purposes:
type: array
items:
type: string
example: ["TLS Web Server Authentication", "TLS Web Client Authentication"]
CSR:
type: object
properties:
id:
type: string
format: uuid
example: "770e8400-e29b-41d4-a716-446655440000"
fqdnId:
type: string
format: uuid
example: "660e8400-e29b-41d4-a716-446655440000"
spaceId:
type: string
format: uuid
example: "550e8400-e29b-41d4-a716-446655440000"
fqdn:
type: string
example: "example.com"
csrPem:
type: string
example: "-----BEGIN CERTIFICATE REQUEST-----\n...\n-----END CERTIFICATE REQUEST-----"
subject:
type: string
example: "CN=example.com"
publicKeyAlgorithm:
type: string
example: "RSA"
signatureAlgorithm:
type: string
example: "SHA256-RSA"
keySize:
type: integer
example: 2048
dnsNames:
type: array
items:
type: string
example: ["example.com", "www.example.com"]
emailAddresses:
type: array
items:
type: string
example: ["admin@example.com"]
ipAddresses:
type: array
items:
type: string
example: ["192.168.1.1"]
uris:
type: array
items:
type: string
example: ["https://example.com"]
extensions:
type: array
items:
$ref: '#/components/schemas/Extension'
createdAt:
type: string
format: date-time
example: "2024-01-15T10:30:00Z"
MessageResponse:
type: object
properties:
message:
type: string
example: "Operation erfolgreich"
CountResponse:
type: object
properties:
count:
type: integer
example: 5
DeleteResponse:
type: object
properties:
message:
type: string
example: "Alle FQDNs erfolgreich gelöscht"
deletedCount:
type: integer
example: 5
securitySchemes:
{}:
type: http
scheme: none

View File

@@ -0,0 +1,76 @@
package providers
import (
"fmt"
"strings"
)
// AutoDNSProvider ist der Provider für AutoDNS
type AutoDNSProvider struct{}
func NewAutoDNSProvider() *AutoDNSProvider {
return &AutoDNSProvider{}
}
func (p *AutoDNSProvider) GetName() string {
return "autodns"
}
func (p *AutoDNSProvider) GetDisplayName() string {
return "AutoDNS"
}
func (p *AutoDNSProvider) GetDescription() string {
return "AutoDNS SSL Certificate Provider"
}
func (p *AutoDNSProvider) ValidateConfig(settings map[string]interface{}) error {
username, ok := settings["username"].(string)
if !ok || strings.TrimSpace(username) == "" {
return fmt.Errorf("username ist erforderlich")
}
password, ok := settings["password"].(string)
if !ok || strings.TrimSpace(password) == "" {
return fmt.Errorf("password ist erforderlich")
}
return nil
}
func (p *AutoDNSProvider) TestConnection(settings map[string]interface{}) error {
// Hier würde die tatsächliche Verbindung zu AutoDNS getestet werden
// Für jetzt nur Validierung
return p.ValidateConfig(settings)
}
// GetRequiredSettings gibt die erforderlichen Einstellungen zurück
func (p *AutoDNSProvider) GetRequiredSettings() []SettingField {
return []SettingField{
{
Name: "username",
Label: "Benutzername",
Type: "text",
Required: true,
Description: "AutoDNS Benutzername",
},
{
Name: "password",
Label: "Passwort",
Type: "password",
Required: true,
Description: "AutoDNS Passwort",
},
}
}
// SignCSR signiert einen CSR (noch nicht implementiert)
func (p *AutoDNSProvider) SignCSR(csrPEM string, settings map[string]interface{}) (*SignCSRResult, error) {
return nil, fmt.Errorf("AutoDNS CSR-Signierung noch nicht implementiert")
}
// GetCertificate ruft ein Zertifikat ab (noch nicht implementiert)
func (p *AutoDNSProvider) GetCertificate(certificateID string, settings map[string]interface{}) (string, error) {
return "", fmt.Errorf("AutoDNS Zertifikat-Abruf noch nicht implementiert")
}

205
backend/providers/dummy.go Normal file
View File

@@ -0,0 +1,205 @@
package providers
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)
// DummyCAProvider ist ein Dummy-Provider für Tests
type DummyCAProvider struct {
baseURL string
}
func NewDummyCAProvider() *DummyCAProvider {
return &DummyCAProvider{
baseURL: "http://localhost:8088",
}
}
func (p *DummyCAProvider) GetName() string {
return "dummy-ca"
}
func (p *DummyCAProvider) GetDisplayName() string {
return "Dummy CA"
}
func (p *DummyCAProvider) GetDescription() string {
return "Externe Dummy CA für Tests und Entwicklung (http://localhost:8088)"
}
func (p *DummyCAProvider) ValidateConfig(settings map[string]interface{}) error {
// Dummy-Provider benötigt keine Konfiguration
return nil
}
func (p *DummyCAProvider) TestConnection(settings map[string]interface{}) error {
// Teste Verbindung zur externen CA über Health Check
url := fmt.Sprintf("%s/health", p.baseURL)
resp, err := http.Get(url)
if err != nil {
return fmt.Errorf("CA-Server nicht erreichbar: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("CA-Server antwortet mit Status %d", resp.StatusCode)
}
// Prüfe Response Body
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("fehler beim Lesen der Health-Check-Response: %v", err)
}
var healthResponse struct {
Status string `json:"status"`
}
if err := json.Unmarshal(body, &healthResponse); err != nil {
return fmt.Errorf("ungültige Health-Check-Response: %v", err)
}
if healthResponse.Status != "ok" {
return fmt.Errorf("CA-Server meldet Status: %s", healthResponse.Status)
}
return nil
}
// GetRequiredSettings gibt die erforderlichen Einstellungen zurück
func (p *DummyCAProvider) GetRequiredSettings() []SettingField {
return []SettingField{}
}
// SignCSR signiert einen CSR über die externe Dummy CA API
func (p *DummyCAProvider) SignCSR(csrPEM string, settings map[string]interface{}) (*SignCSRResult, error) {
// Entferne mögliche Whitespace am Anfang/Ende
csrPEM = strings.TrimSpace(csrPEM)
// Base64-kodiere den CSR
csrB64 := base64.StdEncoding.EncodeToString([]byte(csrPEM))
// Erstelle Request Body
requestBody := map[string]interface{}{
"csr": csrB64,
"action": "sign",
"validity_days": 365,
}
// Konvertiere zu JSON
jsonData, err := json.Marshal(requestBody)
if err != nil {
return nil, fmt.Errorf("fehler beim Erstellen des Request-Body: %v", err)
}
// Erstelle HTTP Request
url := fmt.Sprintf("%s/csr", p.baseURL)
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("fehler beim Erstellen des HTTP-Requests: %v", err)
}
req.Header.Set("Content-Type", "application/json")
// Führe Request aus
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("fehler beim Senden des Requests an die CA: %v", err)
}
defer resp.Body.Close()
// Lese Response Body
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("fehler beim Lesen der Response: %v", err)
}
// Prüfe Status Code
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("CA-API Fehler (Status %d): %s", resp.StatusCode, string(body))
}
// Parse Response
var apiResponse struct {
ID string `json:"id"`
Status string `json:"status"`
Message string `json:"message"`
Certificate string `json:"certificate"`
Error string `json:"error"`
}
if err := json.Unmarshal(body, &apiResponse); err != nil {
return nil, fmt.Errorf("fehler beim Parsen der Response: %v", err)
}
// Prüfe auf Fehler in der Response
if apiResponse.Error != "" {
return nil, fmt.Errorf("CA-API Fehler: %s", apiResponse.Error)
}
// Prüfe Status
if apiResponse.Status != "success" {
return nil, fmt.Errorf("CSR-Signierung fehlgeschlagen: %s", apiResponse.Message)
}
// Rückgabe des Ergebnisses
return &SignCSRResult{
CertificatePEM: apiResponse.Certificate,
OrderID: apiResponse.ID,
Status: "issued",
Message: apiResponse.Message,
}, nil
}
// GetCertificate ruft ein Zertifikat über die externe Dummy CA API ab
func (p *DummyCAProvider) GetCertificate(certificateID string, settings map[string]interface{}) (string, error) {
if certificateID == "" {
return "", fmt.Errorf("zertifikat-ID ist erforderlich")
}
// Erstelle HTTP Request
url := fmt.Sprintf("%s/certificate/%s", p.baseURL, certificateID)
resp, err := http.Get(url)
if err != nil {
return "", fmt.Errorf("fehler beim Abrufen des Zertifikats: %v", err)
}
defer resp.Body.Close()
// Lese Response Body
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("fehler beim Lesen der Response: %v", err)
}
// Prüfe Status Code
if resp.StatusCode == http.StatusNotFound {
return "", fmt.Errorf("Zertifikat mit ID %s nicht gefunden", certificateID)
}
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("CA-API Fehler (Status %d): %s", resp.StatusCode, string(body))
}
// Parse Response
var apiResponse struct {
ID string `json:"id"`
Certificate string `json:"certificate"`
CreatedAt string `json:"created_at"`
}
if err := json.Unmarshal(body, &apiResponse); err != nil {
return "", fmt.Errorf("fehler beim Parsen der Response: %v", err)
}
if apiResponse.Certificate == "" {
return "", fmt.Errorf("zertifikat in Response nicht gefunden")
}
return apiResponse.Certificate, nil
}

View File

@@ -0,0 +1,63 @@
package providers
import (
"fmt"
"strings"
)
// HetznerProvider ist der Provider für Hetzner
type HetznerProvider struct{}
func NewHetznerProvider() *HetznerProvider {
return &HetznerProvider{}
}
func (p *HetznerProvider) GetName() string {
return "hetzner"
}
func (p *HetznerProvider) GetDisplayName() string {
return "Hetzner"
}
func (p *HetznerProvider) GetDescription() string {
return "Hetzner SSL Certificate Provider"
}
func (p *HetznerProvider) ValidateConfig(settings map[string]interface{}) error {
apiKey, ok := settings["apiKey"].(string)
if !ok || strings.TrimSpace(apiKey) == "" {
return fmt.Errorf("apiKey ist erforderlich")
}
return nil
}
func (p *HetznerProvider) TestConnection(settings map[string]interface{}) error {
// Hier würde die tatsächliche Verbindung zu Hetzner getestet werden
// Für jetzt nur Validierung
return p.ValidateConfig(settings)
}
// GetRequiredSettings gibt die erforderlichen Einstellungen zurück
func (p *HetznerProvider) GetRequiredSettings() []SettingField {
return []SettingField{
{
Name: "apiKey",
Label: "API Key",
Type: "password",
Required: true,
Description: "Hetzner API Key",
},
}
}
// SignCSR signiert einen CSR (noch nicht implementiert)
func (p *HetznerProvider) SignCSR(csrPEM string, settings map[string]interface{}) (*SignCSRResult, error) {
return nil, fmt.Errorf("Hetzner CSR-Signierung noch nicht implementiert")
}
// GetCertificate ruft ein Zertifikat ab (noch nicht implementiert)
func (p *HetznerProvider) GetCertificate(certificateID string, settings map[string]interface{}) (string, error) {
return "", fmt.Errorf("Hetzner Zertifikat-Abruf noch nicht implementiert")
}

View File

@@ -0,0 +1,214 @@
package providers
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sync"
)
// ProviderConfig enthält die Konfiguration eines Providers
type ProviderConfig struct {
Enabled bool `json:"enabled"`
Settings map[string]interface{} `json:"settings"`
}
// SignCSRResult enthält das Ergebnis einer CSR-Signierung
type SignCSRResult struct {
CertificatePEM string `json:"certificatePEM"`
OrderID string `json:"orderId,omitempty"`
Status string `json:"status"`
Message string `json:"message,omitempty"`
}
// Provider Interface für alle SSL Certificate Provider
type Provider interface {
// GetName gibt den Namen des Providers zurück
GetName() string
// GetDisplayName gibt den Anzeigenamen zurück
GetDisplayName() string
// GetDescription gibt eine Beschreibung zurück
GetDescription() string
// ValidateConfig validiert die Konfiguration
ValidateConfig(settings map[string]interface{}) error
// TestConnection testet die Verbindung zum Provider
TestConnection(settings map[string]interface{}) error
// GetRequiredSettings gibt die erforderlichen Einstellungen zurück
GetRequiredSettings() []SettingField
// SignCSR signiert einen CSR und gibt das Zertifikat zurück
SignCSR(csrPEM string, settings map[string]interface{}) (*SignCSRResult, error)
// GetCertificate ruft ein Zertifikat anhand der Zertifikat-ID ab
GetCertificate(certificateID string, settings map[string]interface{}) (string, error)
}
// ProviderManager verwaltet alle Provider
type ProviderManager struct {
providers map[string]Provider
configs map[string]*ProviderConfig
configDir string
mu sync.RWMutex
}
var manager *ProviderManager
var once sync.Once
// GetManager gibt die Singleton-Instanz des ProviderManagers zurück
func GetManager() *ProviderManager {
once.Do(func() {
manager = &ProviderManager{
providers: make(map[string]Provider),
configs: make(map[string]*ProviderConfig),
configDir: "./config/providers",
}
manager.loadAllConfigs()
})
return manager
}
// RegisterProvider registriert einen neuen Provider
func (pm *ProviderManager) RegisterProvider(provider Provider) {
pm.mu.Lock()
defer pm.mu.Unlock()
providerID := pm.getProviderID(provider.GetName())
pm.providers[providerID] = provider
// Lade Konfiguration falls vorhanden
if pm.configs[providerID] == nil {
pm.configs[providerID] = &ProviderConfig{
Enabled: false,
Settings: make(map[string]interface{}),
}
}
}
// GetProvider gibt einen Provider zurück
func (pm *ProviderManager) GetProvider(id string) (Provider, bool) {
pm.mu.RLock()
defer pm.mu.RUnlock()
provider, exists := pm.providers[id]
return provider, exists
}
// GetAllProviders gibt alle registrierten Provider zurück
func (pm *ProviderManager) GetAllProviders() map[string]Provider {
pm.mu.RLock()
defer pm.mu.RUnlock()
result := make(map[string]Provider)
for id, provider := range pm.providers {
result[id] = provider
}
return result
}
// GetProviderConfig gibt die Konfiguration eines Providers zurück
func (pm *ProviderManager) GetProviderConfig(id string) (*ProviderConfig, error) {
pm.mu.RLock()
defer pm.mu.RUnlock()
config, exists := pm.configs[id]
if !exists {
return &ProviderConfig{
Enabled: false,
Settings: make(map[string]interface{}),
}, nil
}
return config, nil
}
// UpdateProviderConfig aktualisiert die Konfiguration eines Providers
func (pm *ProviderManager) UpdateProviderConfig(id string, config *ProviderConfig) error {
pm.mu.Lock()
defer pm.mu.Unlock()
provider, exists := pm.providers[id]
if !exists {
return fmt.Errorf("provider %s nicht gefunden", id)
}
// Validiere Konfiguration
if err := provider.ValidateConfig(config.Settings); err != nil {
return fmt.Errorf("ungültige Konfiguration: %v", err)
}
pm.configs[id] = config
// Speichere Konfiguration in Datei
return pm.saveConfig(id, config)
}
// SetProviderEnabled aktiviert/deaktiviert einen Provider
func (pm *ProviderManager) SetProviderEnabled(id string, enabled bool) error {
pm.mu.Lock()
defer pm.mu.Unlock()
if pm.configs[id] == nil {
pm.configs[id] = &ProviderConfig{
Enabled: enabled,
Settings: make(map[string]interface{}),
}
} else {
pm.configs[id].Enabled = enabled
}
return pm.saveConfig(id, pm.configs[id])
}
// getProviderID erstellt eine ID aus dem Provider-Namen
func (pm *ProviderManager) getProviderID(name string) string {
return name
}
// loadAllConfigs lädt alle Konfigurationsdateien
func (pm *ProviderManager) loadAllConfigs() {
// Stelle sicher, dass das Verzeichnis existiert
os.MkdirAll(pm.configDir, 0755)
// Lade alle JSON-Dateien im Konfigurationsverzeichnis
files, err := filepath.Glob(filepath.Join(pm.configDir, "*.json"))
if err != nil {
return
}
for _, file := range files {
id := filepath.Base(file[:len(file)-5]) // Entferne .json
config, err := pm.loadConfig(id)
if err == nil {
pm.configs[id] = config
}
}
}
// loadConfig lädt eine Konfigurationsdatei
func (pm *ProviderManager) loadConfig(id string) (*ProviderConfig, error) {
filePath := filepath.Join(pm.configDir, id+".json")
data, err := os.ReadFile(filePath)
if err != nil {
return nil, err
}
var config ProviderConfig
if err := json.Unmarshal(data, &config); err != nil {
return nil, err
}
return &config, nil
}
// saveConfig speichert eine Konfiguration in eine Datei
func (pm *ProviderManager) saveConfig(id string, config *ProviderConfig) error {
// Stelle sicher, dass das Verzeichnis existiert
os.MkdirAll(pm.configDir, 0755)
filePath := filepath.Join(pm.configDir, id+".json")
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
return err
}
return os.WriteFile(filePath, data, 0644)
}

View File

@@ -0,0 +1,22 @@
package providers
// SettingField beschreibt ein Konfigurationsfeld
type SettingField struct {
Name string `json:"name"`
Label string `json:"label"`
Type string `json:"type"` // text, password, number, email, url
Required bool `json:"required"`
Description string `json:"description"`
Default string `json:"default,omitempty"`
}
// ProviderInfo enthält Informationen über einen Provider
type ProviderInfo struct {
ID string `json:"id"`
Name string `json:"name"`
DisplayName string `json:"displayName"`
Description string `json:"description"`
Enabled bool `json:"enabled"`
Settings []SettingField `json:"settings"`
}

BIN
backend/spaces.db-shm Normal file

Binary file not shown.

BIN
backend/spaces.db-wal Normal file

Binary file not shown.