docs(core): comprehensive development documentation and issue management system
- Complete project documentation for API gateway development - API gateway-specific development processes and standards - Comprehensive issue management system with script automation - Go-specific testing guidelines for multi-service architecture New Documentation: - devdocs/KONZEPT.md: project philosophy, architecture, service integration patterns - devdocs/TESTING_GUIDELINES.md: Go testing, API tests, gateway-service integration - devdocs/development-process.md: API gateway development, multi-service coordination - devdocs/furt-issue-management-guide.md: Furt-specific issue management workflows Issue Management System: - scripts/create_issue.sh: 8 preconfigured templates for API gateway development - Furt-specific issue types: service-request, architecture, performance, security - Script-based workflows for efficient development - Integration with existing get_issues.sh and update_issue.sh scripts API Gateway Development Standards: - Service integration patterns for gateway ↔ service communication - API-contract-first development with OpenAPI specifications - Security-first patterns for authentication and input validation - Multi-service testing strategies for coordinated development This documentation enables immediate, efficient API gateway development with clear standards, proven patterns, and automated workflows.
This commit is contained in:
parent
f4e8a40cdf
commit
d6d546bd95
7 changed files with 2649 additions and 0 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -42,6 +42,8 @@ Thumbs.db
|
|||
*.tmp
|
||||
*.temp
|
||||
*.log
|
||||
.temp
|
||||
.backup
|
||||
|
||||
# Development files
|
||||
_personal/
|
||||
|
|
@ -57,3 +59,4 @@ debug.log
|
|||
# Configuration files with secrets
|
||||
config.local.yaml
|
||||
config.production.yaml
|
||||
|
||||
|
|
|
|||
590
devdocs/furt_development_process.md
Normal file
590
devdocs/furt_development_process.md
Normal file
|
|
@ -0,0 +1,590 @@
|
|||
# Entwicklungsprozess für Furt API-Gateway
|
||||
|
||||
**Erstellt:** 03.06.2025
|
||||
**Letzte Aktualisierung:** 03.06.2025
|
||||
**Version:** 1.0
|
||||
**Verantwortlich:** Claude / DAW-Team
|
||||
**Dateipfad:** devdocs/development-process.md
|
||||
|
||||
## Zweck dieses Dokuments
|
||||
|
||||
Dieses Dokument definiert den verbindlichen Prozess für die Entwicklung und Änderung von Code im Rahmen des Furt API-Gateway-Projekts. Es ergänzt die allgemeinen Entwicklungsrichtlinien um API-Gateway-spezifische Patterns und Multi-Service-Koordination.
|
||||
|
||||
Es richtet sich an alle Projektbeteiligten, die am Gateway oder Services entwickeln.
|
||||
|
||||
## Verwandte Dokumente
|
||||
|
||||
Dieses Dokument steht im Zusammenhang mit folgenden anderen Dokumenten:
|
||||
|
||||
- **KONZEPT.md:** Zentrale Referenz und Konzeptdokumentation, devdocs/KONZEPT.md
|
||||
- **TESTING_GUIDELINES.md:** API-Gateway-spezifische Test-Standards, devdocs/TESTING_GUIDELINES.md
|
||||
- **ARCHITECTURE.md:** Detaillierte Systemarchitektur, devdocs/ARCHITECTURE.md
|
||||
|
||||
## Änderungshistorie
|
||||
|
||||
| Version | Datum | Änderungen | Autor |
|
||||
|---------|-------|------------|-------|
|
||||
| 1.0 | 03.06.2025 | Initiale Version für Furt API-Gateway | Claude / DAW-Team |
|
||||
|
||||
## 1. Grundprinzipien für API-Gateway-Entwicklung
|
||||
|
||||
### 1.1 Service-First-Entwicklung
|
||||
|
||||
Jede Entwicklungsaufgabe muss im Kontext des **Service-Ökosystems** betrachtet werden:
|
||||
|
||||
- **Gateway-Änderungen** betreffen potenziell alle Services
|
||||
- **Service-Änderungen** können Gateway-Anpassungen erfordern
|
||||
- **API-Contracts** zwischen Gateway und Services sind kritisch
|
||||
- **Breaking Changes** erfordern koordinierte Rollouts
|
||||
|
||||
### 1.2 API-Contract-Driven Development
|
||||
|
||||
Bevor Code geschrieben wird, müssen **API-Contracts** definiert werden:
|
||||
|
||||
- **OpenAPI-Spezifikation** für neue Endpunkte
|
||||
- **Service-Interface-Definition** für neue Services
|
||||
- **Authentication/Authorization-Requirements** für alle APIs
|
||||
- **Error-Response-Standards** konsistent halten
|
||||
|
||||
### 1.3 Security-First-Pattern
|
||||
|
||||
Sicherheit wird bei **jeder** Änderung mitgedacht:
|
||||
|
||||
- **API-Key-Berechtigungen** bei neuen Endpunkten definieren
|
||||
- **Input-Validation** für alle eingehenden Requests
|
||||
- **Rate-Limiting** für neue Services konfigurieren
|
||||
- **IP-Restrictions** wo angemessen anwenden
|
||||
|
||||
## 2. Verbindlicher Entwicklungsprozess für Furt
|
||||
|
||||
### 2.1 Vorbereitung
|
||||
|
||||
1. **Requirements-Analyse mit Service-Impact**
|
||||
- Welche Services sind betroffen?
|
||||
- Welche Gateway-Komponenten benötigen Änderungen?
|
||||
- Sind Breaking Changes erforderlich?
|
||||
- Welche API-Contracts müssen definiert/aktualisiert werden?
|
||||
|
||||
2. **Explizite Anfrage nach relevanten Dateien**
|
||||
- Gateway-Dateien: `internal/gateway/`, `configs/gateway.yaml`
|
||||
- Service-Dateien: `internal/services/[service]/`, `configs/services/`
|
||||
- API-Dokumentation: `docs/api/`, OpenAPI-Specs
|
||||
- Integration-Tests: `tests/integration/`
|
||||
|
||||
3. **Analyse der Service-Integration-Pattern**
|
||||
- Bestehende Service-Registry-Einträge
|
||||
- Routing-Patterns und Middleware-Chain
|
||||
- Authentifizierungs-Flows
|
||||
- Health-Check-Mechanismen
|
||||
|
||||
### 2.2 Design und Planung
|
||||
|
||||
1. **API-First-Design dokumentieren**
|
||||
- OpenAPI-Spezifikation **vor** der Implementierung schreiben
|
||||
- Request/Response-Schemas definieren
|
||||
- HTTP-Status-Codes und Error-Handling spezifizieren
|
||||
- Authentication-Requirements dokumentieren
|
||||
|
||||
2. **Service-Integration-Strategy festlegen**
|
||||
- Wie wird der Service im Gateway registriert?
|
||||
- Welche Health-Check-URL wird verwendet?
|
||||
- Welche Timeout-Werte sind angemessen?
|
||||
- Braucht der Service Admin-UI-Integration?
|
||||
|
||||
3. **Breaking-Change-Impact analysieren**
|
||||
- Betrifft die Änderung bestehende API-Contracts?
|
||||
- Sind koordinierte Service-Updates erforderlich?
|
||||
- Müssen Client-Integrationen (Hugo-Shortcodes) angepasst werden?
|
||||
- Ist eine API-Versionierung (v1 → v2) notwendig?
|
||||
|
||||
4. **Configuration-Strategy bestimmen**
|
||||
- Welche neuen Config-Parameter werden benötigt?
|
||||
- Sind Environment-Variable für Secrets erforderlich?
|
||||
- Wie wird die Config zwischen Gateway und Service koordiniert?
|
||||
|
||||
### 2.3 Implementierung
|
||||
|
||||
1. **Multi-Component-Development-Order**
|
||||
|
||||
**Für neue Services:**
|
||||
```
|
||||
1. Service-Struktur scaffolden (service-generator.sh)
|
||||
2. Service-Logik implementieren (Standalone-Mode)
|
||||
3. Gateway-Integration hinzufügen
|
||||
4. Integration-Tests schreiben
|
||||
5. Deployment-Scripts anpassen
|
||||
```
|
||||
|
||||
**Für Gateway-Änderungen:**
|
||||
```
|
||||
1. Gateway-Kern-Logik implementieren
|
||||
2. Middleware/Auth-Anpassungen
|
||||
3. Service-Integration testen
|
||||
4. Health-Check-Aggregation
|
||||
5. Admin-Interface-Updates
|
||||
```
|
||||
|
||||
**Für API-Änderungen:**
|
||||
```
|
||||
1. OpenAPI-Spec aktualisieren
|
||||
2. Gateway-Routing anpassen
|
||||
3. Service-Endpunkt implementieren
|
||||
4. Input-Validation hinzufügen
|
||||
5. Integration-Tests erweitern
|
||||
```
|
||||
|
||||
2. **Koordinierte Entwicklung bei Service-Updates**
|
||||
- **Gateway-kompatible Änderungen zuerst** (additive APIs)
|
||||
- **Service-Tests** mit Gateway-Integration
|
||||
- **Backward-Compatibility** während Übergangsphase
|
||||
- **Coordinated Deployment** bei Breaking Changes
|
||||
|
||||
3. **Configuration-Management während Entwicklung**
|
||||
- **Development-Configs** in `configs/[component].dev.yaml`
|
||||
- **Environment-Variable-Mapping** dokumentieren
|
||||
- **Config-Validation** bei Service-Start implementieren
|
||||
- **Hot-Reload** für Development (wo möglich)
|
||||
|
||||
### 2.4 Testing-Integration
|
||||
|
||||
1. **Multi-Layer-Testing-Strategy**
|
||||
- **Unit-Tests:** Für Gateway- und Service-Komponenten isoliert
|
||||
- **Integration-Tests:** Gateway ↔ Service-Kommunikation
|
||||
- **API-Tests:** End-to-End API-Contract-Validation
|
||||
- **Load-Tests:** Gateway-Performance mit mehreren Services
|
||||
|
||||
2. **Test-Coordination-Pattern**
|
||||
```go
|
||||
// Beispiel: Service-Integration-Test
|
||||
func TestGatewayServiceIntegration(t *testing.T) {
|
||||
// 1. Start Test-Service
|
||||
service := startTestService(t, serviceConfig)
|
||||
defer service.Close()
|
||||
|
||||
// 2. Configure Gateway with Test-Service
|
||||
gateway := startTestGateway(t, gatewayConfigWithService(service.URL))
|
||||
defer gateway.Close()
|
||||
|
||||
// 3. Test Gateway → Service communication
|
||||
testServiceAPIThroughGateway(t, gateway, service)
|
||||
}
|
||||
```
|
||||
|
||||
3. **Breaking-Change-Test-Strategy**
|
||||
- **Backward-Compatibility-Tests** bei API-Änderungen
|
||||
- **Version-Migration-Tests** bei Breaking Changes
|
||||
- **Client-Integration-Tests** (Hugo-Shortcode-Kompatibilität)
|
||||
|
||||
## 3. Service-spezifische Entwicklungs-Pattern
|
||||
|
||||
### 3.1 Neue Service-Entwicklung
|
||||
|
||||
1. **Service-Scaffolding**
|
||||
```bash
|
||||
./scripts/service-generator.sh newsletter
|
||||
# Erstellt komplette Service-Struktur
|
||||
```
|
||||
|
||||
2. **Service-Interface-Implementation**
|
||||
```go
|
||||
// Jeder Service muss dieses Interface implementieren
|
||||
type Service interface {
|
||||
// Gateway-Integration
|
||||
HandleRequest(w http.ResponseWriter, r *http.Request)
|
||||
HealthCheck() HealthStatus
|
||||
|
||||
// Standalone-Mode
|
||||
HandleWithAuth(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
// Lifecycle
|
||||
Start(ctx context.Context, config ServiceConfig) error
|
||||
Stop(ctx context.Context) error
|
||||
|
||||
// Service-Metadata
|
||||
GetServiceInfo() ServiceInfo
|
||||
}
|
||||
```
|
||||
|
||||
3. **Service-Registration im Gateway**
|
||||
```yaml
|
||||
# configs/gateway.yaml
|
||||
services:
|
||||
newsletter:
|
||||
enabled: true
|
||||
path_prefix: "/v1/newsletter"
|
||||
upstream: "http://127.0.0.1:8083"
|
||||
health_check: "/health"
|
||||
timeout: 15s
|
||||
auth_required: true
|
||||
rate_limit:
|
||||
requests_per_minute: 60
|
||||
```
|
||||
|
||||
### 3.2 Service-API-Design-Standards
|
||||
|
||||
1. **Einheitliche Request-Patterns**
|
||||
```go
|
||||
// Standard Request-Wrapper
|
||||
type APIRequest struct {
|
||||
RequestID string `json:"request_id,omitempty"`
|
||||
Data interface{} `json:"data"`
|
||||
Meta map[string]interface{} `json:"meta,omitempty"`
|
||||
}
|
||||
|
||||
// Standard Response-Wrapper
|
||||
type APIResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
Error *APIError `json:"error,omitempty"`
|
||||
Meta map[string]interface{} `json:"meta,omitempty"`
|
||||
RequestID string `json:"request_id,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
2. **Konsistente Error-Handling**
|
||||
```go
|
||||
// Standard Error-Format
|
||||
type APIError struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Details string `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
// Standard Error-Codes
|
||||
const (
|
||||
ErrInvalidInput = "INVALID_INPUT"
|
||||
ErrUnauthorized = "UNAUTHORIZED"
|
||||
ErrServiceUnavailable = "SERVICE_UNAVAILABLE"
|
||||
ErrRateLimitExceeded = "RATE_LIMIT_EXCEEDED"
|
||||
)
|
||||
```
|
||||
|
||||
3. **Health-Check-Standards**
|
||||
```go
|
||||
// Standard Health-Response
|
||||
type HealthStatus struct {
|
||||
Status string `json:"status"`
|
||||
Version string `json:"version"`
|
||||
Uptime time.Duration `json:"uptime"`
|
||||
Checks map[string]string `json:"checks"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// Status-Werte
|
||||
const (
|
||||
HealthStatusHealthy = "healthy"
|
||||
HealthStatusDegraded = "degraded"
|
||||
HealthStatusUnhealthy = "unhealthy"
|
||||
)
|
||||
```
|
||||
|
||||
### 3.3 Gateway-Integration-Pattern
|
||||
|
||||
1. **Service-Discovery-Integration**
|
||||
```go
|
||||
// Gateway registriert Services automatisch
|
||||
func (g *Gateway) RegisterService(name string, config ServiceConfig) error {
|
||||
service := &ServiceProxy{
|
||||
Name: name,
|
||||
PathPrefix: config.PathPrefix,
|
||||
Upstream: config.Upstream,
|
||||
HealthCheck: config.HealthCheck,
|
||||
// ...
|
||||
}
|
||||
|
||||
g.services[name] = service
|
||||
g.updateRouting()
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
2. **Request-Middleware-Chain**
|
||||
```go
|
||||
// Standard Middleware-Order für alle Services
|
||||
func (g *Gateway) buildMiddlewareChain(serviceName string) []Middleware {
|
||||
return []Middleware{
|
||||
LoggingMiddleware,
|
||||
AuthenticationMiddleware,
|
||||
RateLimitingMiddleware(serviceName),
|
||||
ValidationMiddleware,
|
||||
ProxyMiddleware(serviceName),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Configuration-Management-Pattern
|
||||
|
||||
### 4.1 Hierarchische Konfiguration
|
||||
|
||||
```go
|
||||
// Config-Loading-Reihenfolge
|
||||
func LoadConfig(serviceName string) (*Config, error) {
|
||||
config := &Config{}
|
||||
|
||||
// 1. Default-Values
|
||||
config.ApplyDefaults()
|
||||
|
||||
// 2. Base-Config-File
|
||||
config.LoadFromFile("configs/" + serviceName + ".yaml")
|
||||
|
||||
// 3. Environment-specific
|
||||
if env := os.Getenv("ENVIRONMENT"); env != "" {
|
||||
config.LoadFromFile("configs/" + serviceName + "." + env + ".yaml")
|
||||
}
|
||||
|
||||
// 4. Environment-Variables
|
||||
config.LoadFromEnv()
|
||||
|
||||
// 5. Command-Line-Flags
|
||||
config.LoadFromFlags()
|
||||
|
||||
return config.Validate()
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 Service-Gateway-Config-Coordination
|
||||
|
||||
```yaml
|
||||
# Gateway-Config für Service
|
||||
services:
|
||||
formular2mail:
|
||||
config_sync: true
|
||||
config_endpoint: "/config"
|
||||
config_push_on_change: true
|
||||
|
||||
# Service erhält Config vom Gateway
|
||||
```
|
||||
|
||||
### 4.3 Secrets-Management
|
||||
|
||||
```go
|
||||
// Secrets werden nie in Config-Files gespeichert
|
||||
type ServiceConfig struct {
|
||||
// Public config
|
||||
Port string `yaml:"port"`
|
||||
LogLevel string `yaml:"log_level"`
|
||||
|
||||
// Secrets via Environment
|
||||
APIKey string `env:"SERVICE_API_KEY"`
|
||||
DBPassword string `env:"DB_PASSWORD"`
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Security-First-Development-Pattern
|
||||
|
||||
### 5.1 Authentication-Integration
|
||||
|
||||
```go
|
||||
// Jeder Service-Endpunkt bekommt Auth-Context
|
||||
type AuthContext struct {
|
||||
APIKey string
|
||||
Permissions []string
|
||||
ClientIP string
|
||||
UserAgent string
|
||||
RequestID string
|
||||
}
|
||||
|
||||
func (s *Service) HandleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
// Auth-Context wird vom Gateway gesetzt
|
||||
authCtx := r.Context().Value("auth").(*AuthContext)
|
||||
|
||||
// Service-spezifische Permission-Checks
|
||||
if !authCtx.HasPermission("service:" + s.name + ":write") {
|
||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// ... Service-Logik
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Input-Validation-Standards
|
||||
|
||||
```go
|
||||
// Standard Validation-Middleware
|
||||
func ValidationMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// 1. Content-Type validation
|
||||
if !isValidContentType(r.Header.Get("Content-Type")) {
|
||||
http.Error(w, "Invalid Content-Type", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// 2. Content-Length limits
|
||||
if r.ContentLength > MaxRequestSize {
|
||||
http.Error(w, "Request too large", http.StatusRequestEntityTooLarge)
|
||||
return
|
||||
}
|
||||
|
||||
// 3. Request-Body validation (service-specific)
|
||||
if err := validateRequestBody(r); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 Rate-Limiting-Strategy
|
||||
|
||||
```go
|
||||
// Service-spezifische Rate-Limits
|
||||
type RateLimitConfig struct {
|
||||
RequestsPerMinute int `yaml:"requests_per_minute"`
|
||||
BurstSize int `yaml:"burst_size"`
|
||||
PerAPIKey bool `yaml:"per_api_key"`
|
||||
PerIP bool `yaml:"per_ip"`
|
||||
Whitelist []string `yaml:"whitelist"`
|
||||
}
|
||||
|
||||
// Gateway-Level Rate-Limiting
|
||||
func (g *Gateway) GetRateLimit(serviceName, apiKey, clientIP string) *RateLimit {
|
||||
config := g.getRateLimitConfig(serviceName)
|
||||
key := buildRateLimitKey(config, apiKey, clientIP)
|
||||
return g.rateLimiter.GetLimit(key)
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Breaking-Change-Management
|
||||
|
||||
### 6.1 API-Versionierung-Strategy
|
||||
|
||||
```go
|
||||
// URL-basierte Versionierung
|
||||
// /v1/mail/send → formular2mail v1
|
||||
// /v2/mail/send → formular2mail v2
|
||||
|
||||
// Gateway-Routing für mehrere Versionen
|
||||
services:
|
||||
formular2mail-v1:
|
||||
path_prefix: "/v1/mail"
|
||||
upstream: "http://127.0.0.1:8081"
|
||||
formular2mail-v2:
|
||||
path_prefix: "/v2/mail"
|
||||
upstream: "http://127.0.0.1:8084"
|
||||
```
|
||||
|
||||
### 6.2 Backward-Compatibility-Pattern
|
||||
|
||||
```go
|
||||
// Service unterstützt mehrere API-Versionen
|
||||
func (s *FormularService) HandleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
version := extractAPIVersion(r.URL.Path) // v1, v2
|
||||
|
||||
switch version {
|
||||
case "v1":
|
||||
s.handleV1Request(w, r)
|
||||
case "v2":
|
||||
s.handleV2Request(w, r)
|
||||
default:
|
||||
s.handleLatestRequest(w, r)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 Migration-Pattern
|
||||
|
||||
```go
|
||||
// Coordinated Service-Migration
|
||||
type MigrationPlan struct {
|
||||
FromVersion string
|
||||
ToVersion string
|
||||
Steps []MigrationStep
|
||||
}
|
||||
|
||||
type MigrationStep struct {
|
||||
Name string
|
||||
Component string // "gateway" | "service"
|
||||
Action string // "deploy" | "config" | "test"
|
||||
Rollback func() error
|
||||
}
|
||||
```
|
||||
|
||||
## 7. Checkliste für API-Gateway-Entwicklung
|
||||
|
||||
### 7.1 Vor Implementierungsbeginn
|
||||
|
||||
- [ ] **Service-Impact analysiert:** Welche Services sind betroffen?
|
||||
- [ ] **API-Contract definiert:** OpenAPI-Spec erstellt/aktualisiert?
|
||||
- [ ] **Gateway-Integration geplant:** Routing, Auth, Rate-Limiting?
|
||||
- [ ] **Config-Strategy festgelegt:** Neue Parameter dokumentiert?
|
||||
- [ ] **Breaking-Change-Assessment:** Versionierung erforderlich?
|
||||
- [ ] **Security-Requirements:** Auth, Validation, Rate-Limiting?
|
||||
- [ ] **Test-Strategy:** Unit, Integration, API-Tests geplant?
|
||||
|
||||
### 7.2 Während der Implementierung
|
||||
|
||||
- [ ] **Service-Interface-Compliance:** Standard-Interface implementiert?
|
||||
- [ ] **Error-Handling-Consistency:** Standard-Error-Format verwendet?
|
||||
- [ ] **Health-Check-Integration:** Standardisierte Health-Endpoint?
|
||||
- [ ] **Logging-Standards:** Strukturierte Logs mit Request-IDs?
|
||||
- [ ] **Config-Validation:** Startup-Config-Checks implementiert?
|
||||
- [ ] **Auth-Integration:** Gateway-Auth-Context respektiert?
|
||||
- [ ] **Documentation-Update:** API-Docs und Service-Docs aktualisiert?
|
||||
|
||||
### 7.3 Nach Implementierungsabschluss
|
||||
|
||||
- [ ] **Integration-Tests:** Gateway ↔ Service-Tests bestehen?
|
||||
- [ ] **API-Contract-Tests:** OpenAPI-Compliance validiert?
|
||||
- [ ] **Performance-Tests:** Load-Tests mit Gateway durchgeführt?
|
||||
- [ ] **Security-Tests:** Auth, Input-Validation, Rate-Limiting getestet?
|
||||
- [ ] **Deployment-Scripts:** Service-Deployment automatisiert?
|
||||
- [ ] **Monitoring-Integration:** Health-Checks und Metriken?
|
||||
- [ ] **Documentation-Complete:** Service-Integration dokumentiert?
|
||||
|
||||
## 8. Troubleshooting-Pattern
|
||||
|
||||
### 8.1 Service-Integration-Debugging
|
||||
|
||||
```go
|
||||
// Standard Debug-Endpoints für Services
|
||||
func (s *Service) RegisterDebugEndpoints() {
|
||||
http.HandleFunc("/debug/config", s.debugConfig)
|
||||
http.HandleFunc("/debug/health-detail", s.debugHealthDetail)
|
||||
http.HandleFunc("/debug/metrics", s.debugMetrics)
|
||||
http.HandleFunc("/debug/auth", s.debugAuth)
|
||||
}
|
||||
|
||||
// Gateway Debug-Endpoints
|
||||
func (g *Gateway) RegisterDebugEndpoints() {
|
||||
http.HandleFunc("/debug/services", g.debugServices)
|
||||
http.HandleFunc("/debug/routing", g.debugRouting)
|
||||
http.HandleFunc("/debug/auth-keys", g.debugAuthKeys)
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 Request-Tracing
|
||||
|
||||
```go
|
||||
// Request-ID-Propagation durch alle Services
|
||||
func (g *Gateway) addRequestID(r *http.Request) *http.Request {
|
||||
requestID := r.Header.Get("X-Request-ID")
|
||||
if requestID == "" {
|
||||
requestID = generateRequestID()
|
||||
}
|
||||
|
||||
// Für Service-Weiterleitung
|
||||
r.Header.Set("X-Request-ID", requestID)
|
||||
|
||||
// Für Logging
|
||||
ctx := context.WithValue(r.Context(), "request_id", requestID)
|
||||
return r.WithContext(ctx)
|
||||
}
|
||||
```
|
||||
|
||||
## 9. Zusammenfassung: API-Gateway-Entwicklungs-Goldene-Regeln
|
||||
|
||||
1. **API-Contract-First:** OpenAPI-Spec vor Code-Implementation
|
||||
2. **Service-Integration-Aware:** Jede Änderung auf Service-Impact prüfen
|
||||
3. **Security-by-Default:** Auth, Validation, Rate-Limiting bei jedem Endpunkt
|
||||
4. **Configuration-Hierarchie:** Defaults → Environment → Service-specific
|
||||
5. **Multi-Layer-Testing:** Unit → Integration → API → E2E
|
||||
6. **Breaking-Change-Coordination:** Versionierung und Migration planen
|
||||
7. **Health-Check-Integration:** Jeder Service braucht standardisierten Health-Endpoint
|
||||
8. **Request-Tracing:** Request-IDs durch gesamte Pipeline propagieren
|
||||
|
||||
---
|
||||
|
||||
**Wichtiger Hinweis:** Diese Entwicklungs-Pattern sind spezifisch für das Furt API-Gateway-System optimiert und sollten bei jeder Entwicklungsaufgabe konsultiert werden, um konsistente und gut integrierte Services zu gewährleisten.
|
||||
520
devdocs/furt_konzept.md
Normal file
520
devdocs/furt_konzept.md
Normal file
|
|
@ -0,0 +1,520 @@
|
|||
# Furt: API-Gateway im Einklang mit digitaler Souveränität
|
||||
|
||||
**Erstellt:** 03. Juni 2025
|
||||
**Letzte Aktualisierung:** 03. Juni 2025
|
||||
**Version:** 1.0
|
||||
**Verantwortlich:** DAW-Team
|
||||
**Dateipfad:** devdocs/KONZEPT.md
|
||||
|
||||
## Zweck dieses Dokuments
|
||||
|
||||
Dieses Dokument definiert die grundlegenden Prinzipien, technischen Entscheidungen und Entwicklungsrichtlinien für das Furt API-Gateway-System. Es dient als zentrale Referenz für alle Entwickler und Mitwirkenden des Projekts.
|
||||
|
||||
Es richtet sich an Entwickler, Projektbeteiligte und alle, die am Code-Design und der Implementierung von Furt arbeiten.
|
||||
|
||||
## Verwandte Dokumente
|
||||
|
||||
Dieses Dokument steht im Zusammenhang mit folgenden anderen Dokumenten:
|
||||
|
||||
- **README.md:** Öffentliche Projektbeschreibung, ../README.md
|
||||
- **ARCHITECTURE.md:** Detaillierte Architekturübersicht, devdocs/ARCHITECTURE.md
|
||||
- **DECISIONS.md:** Wichtige Architekturentscheidungen, devdocs/DECISIONS.md
|
||||
|
||||
## 1. Projektvision und Philosophie
|
||||
|
||||
Furt (germanisch für "Durchgang durch Wasser") ist ein selbst-gehostetes API-Gateway-System, das vollständig im Einklang mit den Prinzipien digitaler Souveränität, technologischer Angemessenheit und ressourcenschonender Entwicklung steht. Das System soll:
|
||||
|
||||
- **Einfachheit über Komplexität stellen** - leicht verständlich, wartbar und erweiterbar sein
|
||||
- **Ressourceneffizient arbeiten** - minimale Server-Anforderungen und optimierte Performance
|
||||
- **Vollständige Kontrolle** ermöglichen - transparenter Code ohne Black-Boxes
|
||||
- **Langfristig tragfähig** sein - basierend auf bewährten Technologien
|
||||
- **Service-Modularität** unterstützen - eigenständige Services unter einheitlicher API
|
||||
- **Mehrsprachigkeit** von Anfang an unterstützen (DE, EN, FR)
|
||||
|
||||
Im Gegensatz zu existierenden Enterprise-Gateway-Lösungen fokussiert sich Furt auf:
|
||||
- Native Installation als Hauptdeployment-Methode (keine Container-Abhängigkeit)
|
||||
- Minimale externe Abhängigkeiten
|
||||
- Transparente Konfiguration und Datenverarbeitung
|
||||
- Volle Kompatibilität mit Low-Tech-Webseiten und statischen Site-Generatoren (besonders Hugo)
|
||||
- **Ein Gateway für alle Services** - von Kontaktformularen bis zu komplexeren Anwendungen
|
||||
|
||||
## 2. Technische Architektur
|
||||
|
||||
### 2.1 Technology-Stack
|
||||
|
||||
- **Backend:** Go (für Performance, einfache Deployment durch einzelne Binärdatei)
|
||||
- **Gateway-Pattern:** Reverse Proxy mit Service Registry
|
||||
- **Konfiguration:** YAML-basiert (human-readable, versionierbar)
|
||||
- **Proxy-Integration:** Apache als SSL-terminierender Reverse Proxy
|
||||
- **Services:** Eigenständige Go-Binaries mit eigenen Ports
|
||||
- **Authentifizierung:** API-Keys mit granularen Berechtigungen
|
||||
- **Logging:** Strukturierte JSON-Logs mit konfigurierbaren Leveln
|
||||
|
||||
### 2.2 Projektstruktur
|
||||
|
||||
```
|
||||
furt/
|
||||
├── cmd/
|
||||
│ ├── furt-gateway/ # Gateway-Binary
|
||||
│ │ └── main.go
|
||||
│ └── services/ # Service-Binaries
|
||||
│ ├── formular2mail/ # Kontaktformular → E-Mail
|
||||
│ ├── sagjan/ # Kommentarsystem-Integration
|
||||
│ └── shop/ # Zukünftig: E-Commerce
|
||||
├── internal/
|
||||
│ ├── gateway/ # Gateway-Kernlogik
|
||||
│ │ ├── server.go # HTTP-Server
|
||||
│ │ ├── router.go # Request-Routing
|
||||
│ │ ├── proxy.go # Service-Proxy
|
||||
│ │ ├── auth.go # Authentifizierung
|
||||
│ │ └── middleware.go # Gateway-Middleware
|
||||
│ ├── services/ # Service-Implementierungen
|
||||
│ │ ├── formular2mail/ # Formular-Service-Logik
|
||||
│ │ └── sagjan/ # Kommentar-Service-Logik
|
||||
│ └── shared/ # Geteilte Komponenten
|
||||
│ ├── auth/ # Authentifizierungs-Bibliothek
|
||||
│ ├── config/ # Konfigurationsmanagement
|
||||
│ └── logging/ # Strukturiertes Logging
|
||||
├── configs/ # Konfigurationsvorlagen
|
||||
│ ├── gateway.yaml.example # Gateway-Konfiguration
|
||||
│ └── services/ # Service-spezifische Configs
|
||||
│ ├── formular2mail.yaml.example
|
||||
│ └── sagjan.yaml.example
|
||||
├── docs/ # Öffentliche Dokumentation
|
||||
│ ├── installation.md # Installationsanleitung
|
||||
│ ├── configuration.md # Konfigurationsreferenz
|
||||
│ ├── services/ # Service-spezifische Dokumentation
|
||||
│ └── api/ # OpenAPI-Dokumentation
|
||||
│ ├── gateway.yaml # Gateway-API-Spec
|
||||
│ └── services/ # Service-API-Specs
|
||||
├── devdocs/ # Entwicklerdokumentation
|
||||
├── scripts/ # Build & Deployment
|
||||
│ ├── build-all.sh # Alle Komponenten bauen
|
||||
│ ├── deploy-gateway.sh # Gateway deployment
|
||||
│ ├── deploy-service.sh # Service deployment
|
||||
│ └── service-generator.sh # Neuen Service scaffolden
|
||||
├── examples/ # Beispiel-Integrationen
|
||||
│ ├── hugo/ # Hugo-Shortcodes
|
||||
│ ├── nginx/ # Nginx-Proxy-Config
|
||||
│ └── apache/ # Apache-Proxy-Config
|
||||
└── tests/ # Test-Suites
|
||||
├── integration/ # Service-Integration-Tests
|
||||
└── e2e/ # End-to-End-Tests
|
||||
```
|
||||
|
||||
### 2.3 Gateway-Architektur
|
||||
|
||||
Das Furt-Gateway implementiert ein **Service-Registry-Pattern** mit dateibasierter Konfiguration:
|
||||
|
||||
```go
|
||||
// Beispiel Gateway-Konfiguration
|
||||
type GatewayConfig struct {
|
||||
Gateway GatewaySettings `yaml:"gateway"`
|
||||
Security SecurityConfig `yaml:"security"`
|
||||
Services map[string]ServiceConfig `yaml:"services"`
|
||||
Logging LoggingConfig `yaml:"logging"`
|
||||
}
|
||||
|
||||
type ServiceConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
PathPrefix string `yaml:"path_prefix"` // /v1/mail, /v1/comments
|
||||
Upstream string `yaml:"upstream"` // http://127.0.0.1:8081
|
||||
HealthCheck string `yaml:"health_check"` // /health
|
||||
Timeout time.Duration `yaml:"timeout"`
|
||||
HasAdminUI bool `yaml:"has_admin_ui"`
|
||||
AdminPath string `yaml:"admin_path"`
|
||||
}
|
||||
```
|
||||
|
||||
**Request-Flow:**
|
||||
1. Client → Apache (SSL-Terminierung) → Gateway
|
||||
2. Gateway → Authentifizierung (API-Key + IP-Check)
|
||||
3. Gateway → Service-Registry (Route Resolution)
|
||||
4. Gateway → Service-Proxy (Request Forwarding)
|
||||
5. Service → Response → Gateway → Client
|
||||
|
||||
## 3. Entwicklungsphasen gemäß natürlichem Wachstum
|
||||
|
||||
Die Entwicklung folgt einem natürlichen, organischen Prozess, der in vier Hauptphasen gegliedert ist:
|
||||
|
||||
### 3.1 Wurzelphase (Grundlagen)
|
||||
|
||||
- **Ziel:** Funktionierendes Gateway mit minimalen Features
|
||||
- **Schlüsselfeatures:**
|
||||
- HTTP-Gateway mit grundlegendem Routing
|
||||
- API-Key-Authentifizierung mit IP-Beschränkungen
|
||||
- Formular2Mail-Service (E-Mail-Weiterleitung)
|
||||
- Basis-Hugo-Integration (Shortcodes)
|
||||
- Native Installationspakete für alle Komponenten
|
||||
|
||||
### 3.2 Wachstumsphase (Erweiterung)
|
||||
|
||||
- **Ziel:** Stabile, nutzbare Version mit wichtigen Services
|
||||
- **Schlüsselfeatures:**
|
||||
- Sagjan-Integration (Kommentarsystem)
|
||||
- Erweiterte Middleware (Rate-Limiting, Logging)
|
||||
- Service-Generator für neue Services
|
||||
- Admin-Dashboard für Gateway-Management
|
||||
- Umfassende OpenAPI-Dokumentation
|
||||
|
||||
### 3.3 Blütephase (Vernetzung)
|
||||
|
||||
- **Ziel:** Verbesserung der Integration und Erweiterbarkeit
|
||||
- **Schlüsselfeatures:**
|
||||
- Shop-Service (E-Commerce-Integration)
|
||||
- Webhook-Support für Service-Events
|
||||
- Erweiterte Monitoring-Funktionen
|
||||
- Multi-Tenant-Fähigkeiten
|
||||
- Service-Discovery-Verbesserungen
|
||||
|
||||
### 3.4 Fruchtphase (Reifung)
|
||||
|
||||
- **Ziel:** Langfristige Wartbarkeit und Community-Entwicklung
|
||||
- **Schlüsselfeatures:**
|
||||
- Community-Service-Ecosystem
|
||||
- Performance-Optimierungen
|
||||
- High-Availability-Features
|
||||
- Föderation mit anderen Furt-Instanzen
|
||||
|
||||
## 4. Service-Integration-Konzept
|
||||
|
||||
### 4.1 Service-Entwicklung-Pattern
|
||||
|
||||
Jeder Service folgt einem standardisierten Muster:
|
||||
|
||||
```go
|
||||
// Service-Interface
|
||||
type Service interface {
|
||||
// Für Gateway-Integration
|
||||
HandleRequest(w http.ResponseWriter, r *http.Request)
|
||||
HealthCheck() error
|
||||
|
||||
// Für Standalone-Mode
|
||||
HandleWithAuth(w http.ResponseWriter, r *http.Request) // Eigene Auth
|
||||
|
||||
// Service-Lifecycle
|
||||
Start(ctx context.Context) error
|
||||
Stop(ctx context.Context) error
|
||||
}
|
||||
|
||||
// Service-Konfiguration
|
||||
type ServiceConfig struct {
|
||||
Port string `yaml:"port"`
|
||||
Environment string `yaml:"environment"`
|
||||
Auth ServiceAuth `yaml:"auth"`
|
||||
Logging LoggingConfig `yaml:"logging"`
|
||||
Custom map[string]string `yaml:"custom"` // Service-spezifisch
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 Dual-Mode-Operation
|
||||
|
||||
Jeder Service kann sowohl **hinter dem Gateway** als auch **standalone** betrieben werden:
|
||||
|
||||
**Gateway-Mode:**
|
||||
- Service läuft auf localhost:PORT
|
||||
- Authentifizierung erfolgt im Gateway
|
||||
- Routing über Gateway-Pfade (/v1/mail, /v1/comments)
|
||||
|
||||
**Standalone-Mode:**
|
||||
- Service läuft mit eigener Authentifizierung
|
||||
- Direkte API-Endpunkte (/api/v1/mail/send)
|
||||
- Eigene Dokumentation und Admin-UI
|
||||
|
||||
### 4.3 Service-Generator
|
||||
|
||||
Für schnelle Service-Entwicklung existiert ein Generator:
|
||||
|
||||
```bash
|
||||
./scripts/service-generator.sh newsletter
|
||||
# Erstellt:
|
||||
# - cmd/services/newsletter/main.go
|
||||
# - internal/services/newsletter/service.go
|
||||
# - configs/services/newsletter.yaml
|
||||
# - docs/services/newsletter.yaml
|
||||
# - Deployment-Scripts
|
||||
```
|
||||
|
||||
## 5. Implementierungsrichtlinien
|
||||
|
||||
### 5.1 Go-spezifische Standards
|
||||
|
||||
- **Formatierung:** `gofmt` für alle Go-Dateien verwenden
|
||||
- **Linting:** `golangci-lint` mit angepasster Konfiguration
|
||||
- **Tests:** Für jedes Package Testdateien (coverage-Ziel: mind. 80%)
|
||||
- **Abhängigkeiten:** Minimale externe Abhängigkeiten, nur Go-Standard-Library + etablierte Packages
|
||||
- **Fehlerbehandlung:** Explizite Fehlerprüfung, strukturierte Fehlerrückgaben
|
||||
- **Logging:** Strukturiertes JSON-Logging mit verschiedenen Leveln
|
||||
- **Kommentare:** Alle Funktionen und Typen auf Englisch dokumentieren
|
||||
|
||||
### 5.2 API-Design-Standards
|
||||
|
||||
- **RESTful-Design:** Standard HTTP-Methoden und Status-Codes
|
||||
- **Versionierung:** `/v1/` Pfad-Prefix für alle APIs
|
||||
- **JSON-Format:** Einheitliche Request/Response-Strukturen
|
||||
- **Fehlerbehandlung:** Konsistente Fehler-Response-Formate
|
||||
- **OpenAPI:** Jeder Endpunkt wird dokumentiert
|
||||
|
||||
### 5.3 Konfigurationsmanagement
|
||||
|
||||
- **YAML-Format:** Human-readable, versionierbar
|
||||
- **Umgebungsvariablen:** Für sensitive Daten (Tokens, Passwörter)
|
||||
- **Hierarchische Konfiguration:** Default → Environment → Local
|
||||
- **Validierung:** Konfiguration wird beim Start validiert
|
||||
|
||||
## 6. Qualitätssicherung
|
||||
|
||||
### 6.1 Teststrategie
|
||||
|
||||
- **Unit-Tests:** Für alle Kernfunktionen und Services
|
||||
- **Integration-Tests:** Für Gateway ↔ Service-Kommunikation
|
||||
- **API-Tests:** Für alle Endpunkte mit verschiedenen Authentifizierungsszenarien
|
||||
- **End-to-End-Tests:** Für vollständige User-Journeys (Hugo → Gateway → Service)
|
||||
- **Performance-Tests:** Load-Testing für Gateway und Services
|
||||
|
||||
### 6.2 Code-Review-Prozess
|
||||
|
||||
- Peer-Reviews für alle Änderungen
|
||||
- Prüfung auf Einhaltung der Low-Tech-Prinzipien
|
||||
- Überprüfung der API-Dokumentation
|
||||
- Fokus auf Sicherheit und Performance
|
||||
- Beachtung der Service-Integration-Patterns
|
||||
|
||||
### 6.3 Sicherheits-Standards
|
||||
|
||||
- **API-Key-Management:** Sichere Generation und Rotation
|
||||
- **Input-Validierung:** Alle User-Inputs validieren
|
||||
- **Rate-Limiting:** Schutz vor Abuse
|
||||
- **IP-Allowlisting:** Restriktive Service-Zugriffe
|
||||
- **Secure Headers:** Standard-Security-Headers
|
||||
- **Audit-Logging:** Sicherheitsrelevante Events loggen
|
||||
|
||||
## 7. Deployment und Installation
|
||||
|
||||
### 7.1 Native Installation (primär)
|
||||
|
||||
**Gateway-Deployment:**
|
||||
```bash
|
||||
# Build
|
||||
./scripts/build-all.sh
|
||||
|
||||
# Deploy Gateway
|
||||
./scripts/deploy-gateway.sh
|
||||
|
||||
# Deploy Services
|
||||
./scripts/deploy-service.sh formular2mail
|
||||
./scripts/deploy-service.sh sagjan
|
||||
```
|
||||
|
||||
**Systemd-Integration:**
|
||||
- Gateway als `furt-gateway.service`
|
||||
- Services als `formular2mail-service.service`, etc.
|
||||
- Automatischer Start und Restart
|
||||
- Structured Logging zu journald
|
||||
|
||||
### 7.2 Apache-Integration
|
||||
|
||||
**Reverse-Proxy-Konfiguration:**
|
||||
```apache
|
||||
<VirtualHost *:443>
|
||||
ServerName api.dragons-at-work.de
|
||||
|
||||
# SSL-Terminierung durch Apache
|
||||
SSLEngine on
|
||||
SSLCertificateFile /path/to/cert.pem
|
||||
SSLCertificateKeyFile /path/to/key.pem
|
||||
|
||||
# Gateway-Proxy
|
||||
ProxyPreserveHost On
|
||||
ProxyPass / http://127.0.0.1:8080/
|
||||
ProxyPassReverse / http://127.0.0.1:8080/
|
||||
|
||||
# Headers für Gateway
|
||||
ProxyPassReverse / http://127.0.0.1:8080/
|
||||
ProxySetHeader X-Forwarded-For %{REMOTE_ADDR}s
|
||||
ProxySetHeader X-Forwarded-Proto https
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
### 7.3 Monitoring und Logging
|
||||
|
||||
**Log-Struktur:**
|
||||
```
|
||||
/var/log/furt/
|
||||
├── gateway.log # Gateway-Logs
|
||||
├── formular2mail.log # Service-Logs
|
||||
├── sagjan.log
|
||||
└── access.log # Request-Logs
|
||||
```
|
||||
|
||||
**Health-Checks:**
|
||||
- Gateway: `/health` (Service-Status-Aggregation)
|
||||
- Services: `/health` (Individual Service Health)
|
||||
- Systemd: `watchdog` für automatischen Restart
|
||||
|
||||
## 8. Hugo-Integration
|
||||
|
||||
### 8.1 Shortcode-Integration
|
||||
|
||||
**Kontaktformular:**
|
||||
```hugo
|
||||
{{< furt-contact
|
||||
form-id="contact-main"
|
||||
api-key="hugo-frontend-key"
|
||||
success-message="Vielen Dank für deine Nachricht!"
|
||||
>}}
|
||||
```
|
||||
|
||||
**Kommentarsystem:**
|
||||
```hugo
|
||||
{{< furt-comments
|
||||
page-url="{{ .Permalink }}"
|
||||
api-key="sagjan-public-key"
|
||||
moderation="true"
|
||||
>}}
|
||||
```
|
||||
|
||||
### 8.2 JavaScript-Client
|
||||
|
||||
**Minimaler, Framework-freier JavaScript-Client:**
|
||||
```javascript
|
||||
// pkg/client/furt-client.js
|
||||
class FurtClient {
|
||||
constructor(baseURL, apiKey) {
|
||||
this.baseURL = baseURL;
|
||||
this.apiKey = apiKey;
|
||||
}
|
||||
|
||||
async submitForm(formData, endpoint) {
|
||||
// Progressive Enhancement
|
||||
// Funktioniert mit und ohne JavaScript
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 9. Git-Workflow und Versionierung
|
||||
|
||||
### 9.1 Branch-Strategie
|
||||
|
||||
- `main`: Stabile Releases
|
||||
- `develop`: Hauptentwicklungszweig
|
||||
- `feature/*`: Feature-Branches
|
||||
- `service/*`: Service-spezifische Entwicklung
|
||||
- `release/*`: Release-Kandidaten
|
||||
|
||||
### 9.2 Commit-Message-Format
|
||||
|
||||
```
|
||||
typ(bereich): short description of the change
|
||||
|
||||
Detailed explanation of the change, if necessary.
|
||||
Multiple lines possible.
|
||||
|
||||
Resolves: #123
|
||||
```
|
||||
|
||||
- **Typen:** feat, fix, docs, style, refactor, test, chore
|
||||
- **Bereiche:** gateway, service-*, config, docs, scripts
|
||||
- **Sprache:** Englisch für alle Commit-Messages
|
||||
|
||||
### 9.3 Versionierung
|
||||
|
||||
Semantic Versioning (MAJOR.MINOR.PATCH):
|
||||
- **MAJOR:** Breaking Changes in Gateway-API oder Service-Interfaces
|
||||
- **MINOR:** Neue Services oder Features (abwärtskompatibel)
|
||||
- **PATCH:** Bugfixes und Performance-Verbesserungen
|
||||
|
||||
## 10. Service-Spezifikationen
|
||||
|
||||
### 10.1 Formular2Mail-Service
|
||||
|
||||
**Zweck:** Kontaktformulare zu E-Mail weiterleiten
|
||||
**Port:** 8081
|
||||
**API:** `/send` (POST)
|
||||
**Integration:** SMTP mit bestehendem Postfix
|
||||
**Hugo-Integration:** Shortcode für Kontaktformulare
|
||||
|
||||
### 10.2 Sagjan-Service (geplant)
|
||||
|
||||
**Zweck:** Kommentarsystem-Integration
|
||||
**Port:** 8082
|
||||
**API:** `/comments/*` (GET, POST, PUT, DELETE)
|
||||
**Features:** Moderation, Threading, Spam-Schutz
|
||||
**Hugo-Integration:** Shortcode für Kommentare
|
||||
|
||||
### 10.3 Zukünftige Services
|
||||
|
||||
- **Shop-Service:** E-Commerce-Funktionen
|
||||
- **Newsletter-Service:** Listmonk-Integration
|
||||
- **Calendar-Service:** Terminbuchungen
|
||||
- **Auth-Service:** User-Management
|
||||
|
||||
## 11. Lizenzierung
|
||||
|
||||
### 11.1 Apache License 2.0
|
||||
|
||||
Das Projekt steht unter der Apache License 2.0, die:
|
||||
- Kommerzielle Nutzung erlaubt
|
||||
- Patentrechte explizit gewährt
|
||||
- Modifikationen ermöglicht
|
||||
- Verteilung gestattet
|
||||
|
||||
### 11.2 Copyright-Header
|
||||
|
||||
Jede Quellcode-Datei muss folgenden Header enthalten:
|
||||
|
||||
```go
|
||||
// Copyright 2025 Furt Contributors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
```
|
||||
|
||||
## 12. Nächste Schritte
|
||||
|
||||
### 12.1 Unmittelbare Implementierung (Wurzelphase)
|
||||
|
||||
1. **Gateway-Grundgerüst entwickeln**
|
||||
- HTTP-Server mit grundlegenden Routen
|
||||
- Konfigurationsmanagement (YAML-basiert)
|
||||
- Service-Registry-Implementation
|
||||
|
||||
2. **Formular2Mail-Service implementieren**
|
||||
- SMTP-Integration mit bestehendem Postfix
|
||||
- Input-Validierung und Fehlerbehandlung
|
||||
- Hugo-Shortcode für Kontaktformulare
|
||||
|
||||
3. **Apache-Integration konfigurieren**
|
||||
- Reverse-Proxy für `api.dragons-at-work.de`
|
||||
- SSL-Terminierung und Header-Forwarding
|
||||
- Health-Check-Integration
|
||||
|
||||
### 12.2 Mittelfristige Entwicklung (Wachstumsphase)
|
||||
|
||||
1. **Authentifizierungs-System ausbauen**
|
||||
- Granulare API-Key-Berechtigungen
|
||||
- Rate-Limiting und IP-Restrictions
|
||||
- Admin-Interface für Key-Management
|
||||
|
||||
2. **Monitoring und Logging**
|
||||
- Strukturiertes JSON-Logging
|
||||
- Health-Check-Aggregation
|
||||
- Performance-Metriken
|
||||
|
||||
3. **Service-Generator implementieren**
|
||||
- Scaffolding für neue Services
|
||||
- Template-basierte Code-Generierung
|
||||
- Deployment-Script-Integration
|
||||
|
||||
---
|
||||
|
||||
Diese Konzeptdokumentation dient als Leitfaden für die Entwicklung von Furt und soll im Laufe des Projekts entsprechend der Erkenntnisse aus der praktischen Umsetzung angepasst und erweitert werden. Sie bildet die Grundlage für alle weiteren Architektur- und Implementierungsentscheidungen im Projekt.
|
||||
783
devdocs/furt_testing_guidelines.md
Normal file
783
devdocs/furt_testing_guidelines.md
Normal file
|
|
@ -0,0 +1,783 @@
|
|||
# Furt Testing-Richtlinien
|
||||
|
||||
**Erstellt:** 03.06.2025
|
||||
**Letzte Aktualisierung:** 03.06.2025
|
||||
**Version:** 1.0
|
||||
**Verantwortlich:** DAW-Team
|
||||
**Dateipfad:** devdocs/TESTING_GUIDELINES.md
|
||||
|
||||
## Zweck dieses Dokuments
|
||||
|
||||
Dieses Dokument definiert verbindliche Standards und Richtlinien für das Testen von Komponenten des Furt API-Gateway-Projekts. Es soll sicherstellen, dass alle implementierten Funktionalitäten ausreichend durch Tests abgedeckt sind und die Tests konsistent und wartbar bleiben.
|
||||
|
||||
Es richtet sich an alle Entwickler, die Code zum Projekt beisteuern.
|
||||
|
||||
## 1. Grundprinzipien
|
||||
|
||||
### 1.1 Test-First-Entwicklung
|
||||
|
||||
- Tests sollten parallel zur Implementierung oder idealerweise vor der eigentlichen Implementierung geschrieben werden.
|
||||
- Keine Implementierung gilt als abgeschlossen, bis entsprechende Tests vorhanden sind.
|
||||
- Pull Requests ohne Tests werden in der Regel nicht akzeptiert.
|
||||
|
||||
### 1.2 Testabdeckung
|
||||
|
||||
- Angestrebte Testabdeckung für Gateway-Kern: mindestens 85%
|
||||
- Angestrebte Testabdeckung für Services: mindestens 80%
|
||||
- Angestrebte Testabdeckung für Shared-Libraries: mindestens 90%
|
||||
- Besonders kritische Komponenten (Authentifizierung, Routing, Service-Proxy) sollten eine Abdeckung nahe 100% haben.
|
||||
|
||||
### 1.3 Test-Typen
|
||||
|
||||
Folgende Test-Typen werden im Projekt verwendet:
|
||||
|
||||
1. **Unit Tests**: Testen einzelner Funktionen/Methoden in Isolation
|
||||
2. **Integration Tests**: Testen des Zusammenspiels von Komponenten
|
||||
3. **API Tests**: Testen der API-Endpunkte des Gateways und Services
|
||||
4. **Service Integration Tests**: Testen der Gateway ↔ Service-Kommunikation
|
||||
5. **End-to-End Tests**: Testen der gesamten Request-Pipeline (Client → Gateway → Service)
|
||||
6. **Performance Tests**: Load-Testing für Gateway und Services
|
||||
|
||||
## 2. Test-Struktur und Dateiorganisation
|
||||
|
||||
### 2.1 Dateistruktur
|
||||
|
||||
- Test-Dateien werden neben den zu testenden Dateien platziert und erhalten den Suffix `_test.go`
|
||||
- Beispiel: `gateway.go` → `gateway_test.go`
|
||||
- Integration-Tests werden in `tests/integration/` platziert
|
||||
- End-to-End-Tests werden in `tests/e2e/` platziert
|
||||
|
||||
### 2.2 Namenkonventionen
|
||||
|
||||
- Testfunktionen folgen dem Format `Test<Komponente><Funktionsname><Szenario>`
|
||||
- Beispiel: `TestGatewayRoutingWithValidAPIKey`, `TestServiceProxyWhenServiceUnavailable`
|
||||
- Benchmark-Tests: `Benchmark<Funktionsname>`
|
||||
- Example-Tests: `Example<Funktionsname>`
|
||||
|
||||
### 2.3 Testpakete
|
||||
|
||||
- Tests sollten im selben Paket wie der zu testende Code sein (kein separates `_test`-Paket)
|
||||
- Dies ermöglicht das Testen von Funktionen, die nicht exportiert werden
|
||||
- Ausnahme: Integration-Tests können separate Pakete verwenden
|
||||
|
||||
## 3. Unit Tests
|
||||
|
||||
### 3.1 Grundstruktur
|
||||
|
||||
Jeder Unit Test sollte folgende Struktur haben:
|
||||
|
||||
```go
|
||||
func TestFunctionName(t *testing.T) {
|
||||
// Arrange: Vorbereitung der Testdaten und Abhängigkeiten
|
||||
input := setupTestInput()
|
||||
mockService := &MockService{}
|
||||
expected := expectedResult{}
|
||||
|
||||
// Act: Ausführen der zu testenden Funktion
|
||||
actual, err := FunctionName(input, mockService)
|
||||
|
||||
// Assert: Überprüfung des Ergebnisses
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, but got: %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Errorf("Expected %+v, but got %+v", expected, actual)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Table-Driven Tests
|
||||
|
||||
Für komplexere Funktionen sollten Table-Driven Tests verwendet werden:
|
||||
|
||||
```go
|
||||
func TestGatewayRouting(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
requestPath string
|
||||
expectedService string
|
||||
expectedPath string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "formular2mail service routing",
|
||||
requestPath: "/v1/mail/send",
|
||||
expectedService: "formular2mail",
|
||||
expectedPath: "/send",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "sagjan service routing",
|
||||
requestPath: "/v1/comments/list",
|
||||
expectedService: "sagjan",
|
||||
expectedPath: "/list",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "unknown service",
|
||||
requestPath: "/v1/unknown/test",
|
||||
expectedService: "",
|
||||
expectedPath: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gateway := setupTestGateway()
|
||||
service, path, err := gateway.ResolveRoute(tc.requestPath)
|
||||
|
||||
if tc.wantErr && err == nil {
|
||||
t.Error("Expected error, but got nil")
|
||||
}
|
||||
if !tc.wantErr && err != nil {
|
||||
t.Fatalf("Expected no error, but got: %v", err)
|
||||
}
|
||||
|
||||
if service != tc.expectedService {
|
||||
t.Errorf("Expected service %s, got %s", tc.expectedService, service)
|
||||
}
|
||||
if path != tc.expectedPath {
|
||||
t.Errorf("Expected path %s, got %s", tc.expectedPath, path)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 Mocking und Test-Doubles
|
||||
|
||||
- Für HTTP-Clients verwende `httptest.NewServer`
|
||||
- Für Services erstelle Interface-basierte Mocks
|
||||
- Verwende Dependency Injection für bessere Testbarkeit
|
||||
|
||||
```go
|
||||
// Service Interface für Testbarkeit
|
||||
type MailService interface {
|
||||
SendMail(request MailRequest) error
|
||||
}
|
||||
|
||||
// Mock Implementation
|
||||
type MockMailService struct {
|
||||
SendMailFunc func(MailRequest) error
|
||||
CallCount int
|
||||
}
|
||||
|
||||
func (m *MockMailService) SendMail(request MailRequest) error {
|
||||
m.CallCount++
|
||||
if m.SendMailFunc != nil {
|
||||
return m.SendMailFunc(request)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Test mit Mock
|
||||
func TestFormular2MailHandler(t *testing.T) {
|
||||
mockService := &MockMailService{
|
||||
SendMailFunc: func(req MailRequest) error {
|
||||
if req.Email == "invalid" {
|
||||
return errors.New("invalid email")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
handler := NewFormular2MailHandler(mockService)
|
||||
|
||||
// Test ausführen...
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Integration Tests
|
||||
|
||||
### 4.1 Gateway-Service Integration Tests
|
||||
|
||||
Diese Tests prüfen die Kommunikation zwischen Gateway und Services:
|
||||
|
||||
```go
|
||||
// tests/integration/gateway_service_test.go
|
||||
func TestGatewayServiceIntegration(t *testing.T) {
|
||||
// Setup Test-Services
|
||||
mailService := startTestMailService(t)
|
||||
defer mailService.Close()
|
||||
|
||||
commentsService := startTestCommentsService(t)
|
||||
defer commentsService.Close()
|
||||
|
||||
// Setup Gateway mit Test-Konfiguration
|
||||
gateway := setupTestGateway(t, GatewayConfig{
|
||||
Services: map[string]ServiceConfig{
|
||||
"formular2mail": {
|
||||
Enabled: true,
|
||||
PathPrefix: "/v1/mail",
|
||||
Upstream: mailService.URL,
|
||||
},
|
||||
"sagjan": {
|
||||
Enabled: true,
|
||||
PathPrefix: "/v1/comments",
|
||||
Upstream: commentsService.URL,
|
||||
},
|
||||
},
|
||||
})
|
||||
defer gateway.Close()
|
||||
|
||||
// Test Gateway → Service Routing
|
||||
t.Run("mail service integration", func(t *testing.T) {
|
||||
resp := makeTestRequest(t, gateway.URL+"/v1/mail/send", "POST", mailRequestBody)
|
||||
assertStatusCode(t, resp, http.StatusOK)
|
||||
})
|
||||
|
||||
t.Run("comments service integration", func(t *testing.T) {
|
||||
resp := makeTestRequest(t, gateway.URL+"/v1/comments", "GET", nil)
|
||||
assertStatusCode(t, resp, http.StatusOK)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 Database Integration Tests (für Services)
|
||||
|
||||
Für Services mit Datenbank-Zugriff:
|
||||
|
||||
```go
|
||||
func TestSagjanServiceDatabaseIntegration(t *testing.T) {
|
||||
// Setup Test-Database (SQLite in-memory)
|
||||
db := setupTestDB(t)
|
||||
defer db.Close()
|
||||
|
||||
service := NewSagjanService(db)
|
||||
|
||||
// Test Comment Creation
|
||||
comment := &Comment{
|
||||
PageURL: "https://example.com/test",
|
||||
Author: "Test User",
|
||||
Content: "Test Comment",
|
||||
}
|
||||
|
||||
err := service.CreateComment(context.Background(), comment)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create comment: %v", err)
|
||||
}
|
||||
|
||||
// Test Comment Retrieval
|
||||
comments, err := service.GetComments(context.Background(), "https://example.com/test")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get comments: %v", err)
|
||||
}
|
||||
|
||||
if len(comments) != 1 {
|
||||
t.Errorf("Expected 1 comment, got %d", len(comments))
|
||||
}
|
||||
}
|
||||
|
||||
func setupTestDB(t *testing.T) *sql.DB {
|
||||
db, err := sql.Open("sqlite3", ":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open test database: %v", err)
|
||||
}
|
||||
|
||||
// Run migrations
|
||||
if err := runMigrations(db); err != nil {
|
||||
t.Fatalf("Failed to run migrations: %v", err)
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
```
|
||||
|
||||
## 5. API Tests
|
||||
|
||||
### 5.1 Gateway API Tests
|
||||
|
||||
Tests für die Gateway-API-Endpunkte:
|
||||
|
||||
```go
|
||||
func TestGatewayAPIEndpoints(t *testing.T) {
|
||||
gateway := setupTestGateway(t)
|
||||
defer gateway.Close()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
method string
|
||||
path string
|
||||
headers map[string]string
|
||||
body string
|
||||
expectedStatus int
|
||||
expectedBody string
|
||||
}{
|
||||
{
|
||||
name: "health check",
|
||||
method: "GET",
|
||||
path: "/health",
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedBody: `{"status":"healthy"}`,
|
||||
},
|
||||
{
|
||||
name: "unauthorized request",
|
||||
method: "POST",
|
||||
path: "/v1/mail/send",
|
||||
expectedStatus: http.StatusUnauthorized,
|
||||
},
|
||||
{
|
||||
name: "authorized mail request",
|
||||
method: "POST",
|
||||
path: "/v1/mail/send",
|
||||
headers: map[string]string{
|
||||
"X-API-Key": "test-api-key",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: `{"name":"Test","email":"test@example.com","message":"Test"}`,
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := createTestRequest(t, tc.method, gateway.URL+tc.path, tc.body)
|
||||
|
||||
for key, value := range tc.headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != tc.expectedStatus {
|
||||
t.Errorf("Expected status %d, got %d", tc.expectedStatus, resp.StatusCode)
|
||||
}
|
||||
|
||||
if tc.expectedBody != "" {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
if string(body) != tc.expectedBody {
|
||||
t.Errorf("Expected body %s, got %s", tc.expectedBody, string(body))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 Service API Tests
|
||||
|
||||
Tests für individuelle Service-APIs:
|
||||
|
||||
```go
|
||||
func TestFormular2MailAPI(t *testing.T) {
|
||||
service := startTestFormular2MailService(t)
|
||||
defer service.Close()
|
||||
|
||||
t.Run("valid mail request", func(t *testing.T) {
|
||||
reqBody := `{
|
||||
"name": "John Doe",
|
||||
"email": "john@example.com",
|
||||
"message": "Test message"
|
||||
}`
|
||||
|
||||
resp := makeTestRequest(t, service.URL+"/send", "POST", reqBody)
|
||||
assertStatusCode(t, resp, http.StatusOK)
|
||||
|
||||
var response MailResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
|
||||
t.Fatalf("Failed to decode response: %v", err)
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
t.Error("Expected success=true in response")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid mail request", func(t *testing.T) {
|
||||
reqBody := `{"name": "", "email": "invalid", "message": ""}`
|
||||
|
||||
resp := makeTestRequest(t, service.URL+"/send", "POST", reqBody)
|
||||
assertStatusCode(t, resp, http.StatusBadRequest)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Performance Tests
|
||||
|
||||
### 6.1 Gateway Performance Tests
|
||||
|
||||
```go
|
||||
func TestGatewayPerformance(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("Skipping performance test in short mode")
|
||||
}
|
||||
|
||||
gateway := setupTestGateway(t)
|
||||
defer gateway.Close()
|
||||
|
||||
// Load test
|
||||
concurrency := 10
|
||||
requests := 1000
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, requests)
|
||||
|
||||
start := time.Now()
|
||||
|
||||
for i := 0; i < concurrency; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < requests/concurrency; j++ {
|
||||
resp, err := http.Get(gateway.URL + "/health")
|
||||
if err != nil {
|
||||
errors <- err
|
||||
return
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
errors <- fmt.Errorf("unexpected status: %d", resp.StatusCode)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
|
||||
duration := time.Since(start)
|
||||
|
||||
// Check for errors
|
||||
for err := range errors {
|
||||
t.Errorf("Request error: %v", err)
|
||||
}
|
||||
|
||||
// Performance assertions
|
||||
requestsPerSecond := float64(requests) / duration.Seconds()
|
||||
if requestsPerSecond < 500 { // Minimum 500 RPS
|
||||
t.Errorf("Performance too low: %.2f RPS", requestsPerSecond)
|
||||
}
|
||||
|
||||
t.Logf("Performance: %.2f RPS over %v", requestsPerSecond, duration)
|
||||
}
|
||||
|
||||
func BenchmarkGatewayRouting(b *testing.B) {
|
||||
gateway := setupBenchmarkGateway(b)
|
||||
|
||||
req := httptest.NewRequest("GET", "/v1/mail/send", nil)
|
||||
req.Header.Set("X-API-Key", "test-key")
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
w := httptest.NewRecorder()
|
||||
gateway.ServeHTTP(w, req)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 7. Test-Daten und Test-Utilities
|
||||
|
||||
### 7.1 Test-Daten-Management
|
||||
|
||||
```go
|
||||
// internal/testutil/fixtures.go
|
||||
package testutil
|
||||
|
||||
func CreateTestMailRequest() MailRequest {
|
||||
return MailRequest{
|
||||
Name: "Test User",
|
||||
Email: "test@example.com",
|
||||
Subject: "Test Subject",
|
||||
Message: "Test Message",
|
||||
}
|
||||
}
|
||||
|
||||
func CreateTestComment() *Comment {
|
||||
return &Comment{
|
||||
ID: uuid.New().String(),
|
||||
PageURL: "https://example.com/test",
|
||||
Author: "Test Author",
|
||||
Email: "test@example.com",
|
||||
Content: "Test Comment Content",
|
||||
Status: StatusPending,
|
||||
}
|
||||
}
|
||||
|
||||
func CreateTestGatewayConfig() GatewayConfig {
|
||||
return GatewayConfig{
|
||||
Gateway: GatewaySettings{
|
||||
Port: "8080",
|
||||
LogLevel: "info",
|
||||
},
|
||||
Security: SecurityConfig{
|
||||
APIKeys: []APIKey{
|
||||
{
|
||||
Key: "test-api-key",
|
||||
Name: "Test Key",
|
||||
Permissions: []string{"mail:send"},
|
||||
AllowedIPs: []string{"127.0.0.1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]ServiceConfig{
|
||||
"formular2mail": {
|
||||
Enabled: true,
|
||||
PathPrefix: "/v1/mail",
|
||||
Upstream: "http://127.0.0.1:8081",
|
||||
HealthCheck: "/health",
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 Test-Helper-Funktionen
|
||||
|
||||
```go
|
||||
// internal/testutil/helpers.go
|
||||
package testutil
|
||||
|
||||
func AssertStatusCode(t *testing.T, resp *http.Response, expected int) {
|
||||
t.Helper()
|
||||
if resp.StatusCode != expected {
|
||||
t.Errorf("Expected status code %d, got %d", expected, resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func AssertResponseBody(t *testing.T, resp *http.Response, expected string) {
|
||||
t.Helper()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read response body: %v", err)
|
||||
}
|
||||
|
||||
if string(body) != expected {
|
||||
t.Errorf("Expected body %q, got %q", expected, string(body))
|
||||
}
|
||||
}
|
||||
|
||||
func MakeTestRequest(t *testing.T, url, method, body string) *http.Response {
|
||||
t.Helper()
|
||||
|
||||
var reqBody io.Reader
|
||||
if body != "" {
|
||||
reqBody = strings.NewReader(body)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, reqBody)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create request: %v", err)
|
||||
}
|
||||
|
||||
if body != "" {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("Request failed: %v", err)
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
```
|
||||
|
||||
## 8. Test-Umgebung und CI
|
||||
|
||||
### 8.1 Lokale Tests
|
||||
|
||||
- Alle Tests sollten mit `go test ./...` ausführbar sein
|
||||
- Keine Tests sollten externe Ressourcen benötigen (wie echte E-Mail-Server)
|
||||
- Performance-Tests mit `-short` Flag überspringen
|
||||
|
||||
### 8.2 Test-Tags
|
||||
|
||||
```go
|
||||
// +build integration
|
||||
|
||||
package tests
|
||||
|
||||
// Integration tests that require external resources
|
||||
```
|
||||
|
||||
**Ausführung:**
|
||||
```bash
|
||||
# Nur Unit Tests
|
||||
go test ./...
|
||||
|
||||
# Mit Integration Tests
|
||||
go test -tags=integration ./...
|
||||
|
||||
# Mit Performance Tests
|
||||
go test -timeout=30m ./...
|
||||
|
||||
# Kurze Tests für CI
|
||||
go test -short ./...
|
||||
```
|
||||
|
||||
### 8.3 Coverage-Berichte
|
||||
|
||||
```bash
|
||||
# Coverage generieren
|
||||
go test -coverprofile=coverage.out ./...
|
||||
|
||||
# HTML-Report
|
||||
go tool cover -html=coverage.out -o coverage.html
|
||||
|
||||
# Coverage-Threshold prüfen
|
||||
go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//'
|
||||
```
|
||||
|
||||
## 9. Spezifische Testfälle für Furt
|
||||
|
||||
### 9.1 Gateway-Routing Tests
|
||||
|
||||
```go
|
||||
func TestGatewayServiceRouting(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
requestPath string
|
||||
method string
|
||||
expectedService string
|
||||
expectedUpstream string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "formular2mail routing",
|
||||
requestPath: "/v1/mail/send",
|
||||
method: "POST",
|
||||
expectedService: "formular2mail",
|
||||
expectedUpstream: "http://127.0.0.1:8081",
|
||||
},
|
||||
{
|
||||
name: "sagjan comments routing",
|
||||
requestPath: "/v1/comments",
|
||||
method: "GET",
|
||||
expectedService: "sagjan",
|
||||
expectedUpstream: "http://127.0.0.1:8082",
|
||||
},
|
||||
{
|
||||
name: "unknown service",
|
||||
requestPath: "/v1/unknown",
|
||||
method: "GET",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
// Implementation...
|
||||
}
|
||||
```
|
||||
|
||||
### 9.2 Authentication Tests
|
||||
|
||||
```go
|
||||
func TestGatewayAuthentication(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
apiKey string
|
||||
clientIP string
|
||||
requestPath string
|
||||
expectedStatus int
|
||||
}{
|
||||
{
|
||||
name: "valid API key and IP",
|
||||
apiKey: "hugo-frontend-key",
|
||||
clientIP: "127.0.0.1",
|
||||
requestPath: "/v1/mail/send",
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "invalid API key",
|
||||
apiKey: "invalid-key",
|
||||
clientIP: "127.0.0.1",
|
||||
requestPath: "/v1/mail/send",
|
||||
expectedStatus: http.StatusUnauthorized,
|
||||
},
|
||||
{
|
||||
name: "blocked IP",
|
||||
apiKey: "hugo-frontend-key",
|
||||
clientIP: "192.168.1.100",
|
||||
requestPath: "/v1/mail/send",
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
}
|
||||
|
||||
// Implementation...
|
||||
}
|
||||
```
|
||||
|
||||
### 9.3 Service Health Check Tests
|
||||
|
||||
```go
|
||||
func TestServiceHealthChecks(t *testing.T) {
|
||||
// Test Gateway health aggregation
|
||||
t.Run("all services healthy", func(t *testing.T) {
|
||||
// Setup healthy services
|
||||
// Test /health returns 200 with all services status
|
||||
})
|
||||
|
||||
t.Run("one service unhealthy", func(t *testing.T) {
|
||||
// Setup one failing service
|
||||
// Test /health returns appropriate status
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 10. Test-Automation und CI-Integration
|
||||
|
||||
### 10.1 GitHub Actions / Gitea Actions
|
||||
|
||||
```yaml
|
||||
# .gitea/workflows/test.yml
|
||||
name: Test Suite
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.21
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: go test -short -race -coverprofile=coverage.out ./...
|
||||
|
||||
- name: Run Integration Tests
|
||||
run: go test -tags=integration ./tests/integration/
|
||||
|
||||
- name: Check Coverage
|
||||
run: |
|
||||
coverage=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//')
|
||||
if (( $(echo "$coverage < 80" | bc -l) )); then
|
||||
echo "Coverage $coverage% is below 80%"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
## 11. Best Practices Zusammenfassung
|
||||
|
||||
### 11.1 Do's
|
||||
|
||||
- ✅ **Testbare Architektur:** Dependency Injection verwenden
|
||||
- ✅ **Isolierte Tests:** Keine Abhängigkeiten zwischen Tests
|
||||
- ✅ **Realistische Test-Daten:** Aber anonymisiert und minimal
|
||||
- ✅ **Performance-bewusst:** Benchmarks für kritische Pfade
|
||||
- ✅ **Dokumentierte Test-Fälle:** Klare Beschreibungen der Test-Szenarien
|
||||
|
||||
### 11.2 Don'ts
|
||||
|
||||
- ❌ **Externe Ressourcen:** Keine echten E-Mail-Server, externe APIs
|
||||
- ❌ **Feste Zeitstempel:** `time.Now()` mocken in Tests
|
||||
- ❌ **Globaler State:** Tests sollten unabhängig sein
|
||||
- ❌ **Überflüssige Tests:** Triviale Getter/Setter nicht testen
|
||||
- ❌ **Fragile Tests:** Tests sollen bei kleinen Änderungen nicht brechen
|
||||
|
||||
---
|
||||
|
||||
Diese Richtlinien sollen als Leitfaden dienen und können im Laufe des Projekts angepasst und erweitert werden. Bei Unklarheiten oder Fragen zu diesen Richtlinien kann das Entwicklungsteam kontaktiert werden.
|
||||
347
scripts/create_issue.sh
Executable file
347
scripts/create_issue.sh
Executable file
|
|
@ -0,0 +1,347 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Load environment
|
||||
if [ -f .env ]; then
|
||||
export $(cat .env | grep -v '^#' | xargs)
|
||||
else
|
||||
echo "❌ .env file not found!"
|
||||
echo "📋 Copy .env.example to .env and configure it first"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate required variables
|
||||
if [ -z "$GITEA_URL" ] || [ -z "$REPO_OWNER" ] || [ -z "$REPO_NAME" ] || [ -z "$GITEA_TOKEN" ]; then
|
||||
echo "❌ Missing required environment variables in .env"
|
||||
echo "📋 Check .env.example for required variables"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
|
||||
|
||||
# Get all labels with IDs (like update_issue.sh)
|
||||
declare -A LABEL_IDS
|
||||
get_labels() {
|
||||
response=$(curl -s "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/labels" \
|
||||
-H "Authorization: token $GITEA_TOKEN")
|
||||
while IFS= read -r line; do
|
||||
name=$(echo "$line" | jq -r '.name')
|
||||
id=$(echo "$line" | jq -r '.id')
|
||||
LABEL_IDS["$name"]="$id"
|
||||
done < <(echo "$response" | jq -c '.[]')
|
||||
}
|
||||
|
||||
# Function to create issue with proper label ID handling
|
||||
create_issue() {
|
||||
local title="$1"
|
||||
local body="$2"
|
||||
local labels_string="$3"
|
||||
local assignee="$4"
|
||||
|
||||
# Get label IDs first
|
||||
get_labels
|
||||
|
||||
# Convert label names to IDs
|
||||
local valid_label_ids=()
|
||||
if [ -n "$labels_string" ]; then
|
||||
IFS=',' read -ra LABEL_ARRAY <<< "$labels_string"
|
||||
|
||||
for label in "${LABEL_ARRAY[@]}"; do
|
||||
label=$(echo "$label" | xargs) # Trim whitespace
|
||||
if [ -n "${LABEL_IDS[$label]}" ]; then
|
||||
valid_label_ids+=("${LABEL_IDS[$label]}")
|
||||
log_info "Using label '$label' (ID: ${LABEL_IDS[$label]})"
|
||||
else
|
||||
log_warning "Label '$label' not found, skipping"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Build labels array JSON with IDs
|
||||
local labels_json="["
|
||||
for i in "${!valid_label_ids[@]}"; do
|
||||
if [ $i -gt 0 ]; then
|
||||
labels_json="${labels_json},"
|
||||
fi
|
||||
labels_json="${labels_json}${valid_label_ids[$i]}"
|
||||
done
|
||||
labels_json="${labels_json}]"
|
||||
|
||||
# Build assignees array
|
||||
local assignees_json="[]"
|
||||
if [ -n "$assignee" ]; then
|
||||
assignees_json="[\"$assignee\"]"
|
||||
fi
|
||||
|
||||
# Use jq for proper JSON encoding of body and payload
|
||||
local json_payload=$(jq -n \
|
||||
--arg title "$title" \
|
||||
--arg body "$body" \
|
||||
--argjson labels "$labels_json" \
|
||||
--argjson assignees "$assignees_json" \
|
||||
'{
|
||||
title: $title,
|
||||
body: $body,
|
||||
labels: $labels,
|
||||
assignees: $assignees
|
||||
}')
|
||||
|
||||
log_info "Creating issue: $title"
|
||||
|
||||
response=$(curl -s -w "\n%{http_code}" -X POST \
|
||||
"$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/issues" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$json_payload")
|
||||
|
||||
http_code=$(echo "$response" | tail -n1)
|
||||
response_body=$(echo "$response" | head -n -1)
|
||||
|
||||
if [ "$http_code" = "201" ]; then
|
||||
issue_number=$(echo "$response_body" | jq -r '.number')
|
||||
issue_url="$GITEA_URL/$REPO_OWNER/$REPO_NAME/issues/$issue_number"
|
||||
log_success "Issue #$issue_number created!"
|
||||
echo "🔗 $issue_url"
|
||||
echo ""
|
||||
else
|
||||
log_error "Failed to create issue (HTTP: $http_code)"
|
||||
echo "$response_body"
|
||||
fi
|
||||
}
|
||||
|
||||
# Predefined issue templates for Furt API Gateway
|
||||
case "${1:-help}" in
|
||||
"service")
|
||||
service_name="${2:-newsletter}"
|
||||
create_issue \
|
||||
"[SERVICE] $service_name für Furt Gateway" \
|
||||
"## 🏷️ Service-Details
|
||||
**Name:** $service_name
|
||||
**Port:** 808X (TBD)
|
||||
**Zweck:** [Service-Beschreibung hier einfügen]
|
||||
|
||||
## 📝 Funktionsanforderungen
|
||||
- [ ] Grundfunktionalität implementieren
|
||||
- [ ] Input-Validation für alle Endpunkte
|
||||
- [ ] Error-Handling nach Furt-Standards
|
||||
- [ ] Health-Check-Endpoint (\`/health\`)
|
||||
|
||||
## 🔗 Gateway-Integration
|
||||
- [ ] **Routing:** \`/v1/$service_name/*\`
|
||||
- [ ] **Authentication:** API-Key required
|
||||
- [ ] **Rate-Limiting:** [X] requests/minute
|
||||
- [ ] **Upstream:** \`http://127.0.0.1:808X\`
|
||||
- [ ] **Timeout:** 15s default
|
||||
|
||||
## 🎯 API-Endpunkte (geplant)
|
||||
\`\`\`
|
||||
GET /v1/$service_name/ # List/Status
|
||||
POST /v1/$service_name/create # Create new
|
||||
PUT /v1/$service_name/{id} # Update existing
|
||||
DELETE /v1/$service_name/{id} # Delete
|
||||
\`\`\`
|
||||
|
||||
## 🧪 Testing-Requirements
|
||||
- [ ] Unit-Tests für Service-Logik
|
||||
- [ ] Integration-Tests für Gateway ↔ Service
|
||||
- [ ] API-Contract-Tests (OpenAPI-Compliance)
|
||||
- [ ] Load-Tests für Performance-Baselines
|
||||
|
||||
## 🌐 Hugo-Integration
|
||||
- [ ] Shortcode: \`{{< furt-$service_name >}}\`
|
||||
- [ ] JavaScript-Client-Library
|
||||
- [ ] CSS-Styling für UI-Komponenten
|
||||
- [ ] Dokumentation für Website-Integration
|
||||
|
||||
## 📋 OpenAPI-Dokumentation
|
||||
- [ ] \`docs/api/services/$service_name.yaml\` erstellen
|
||||
- [ ] Request/Response-Schemas definieren
|
||||
- [ ] Error-Codes dokumentieren
|
||||
- [ ] Examples für alle Endpunkte
|
||||
|
||||
## ⚡ Priorität
|
||||
📊 Mittel - geplante Entwicklung
|
||||
|
||||
## 🔧 Implementation-Checklist
|
||||
- [ ] Service-Generator ausführen: \`./scripts/service-generator.sh $service_name\`
|
||||
- [ ] Service-Logik implementieren
|
||||
- [ ] Gateway-Integration konfigurieren
|
||||
- [ ] Tests schreiben und ausführen
|
||||
- [ ] Deployment-Scripts anpassen
|
||||
- [ ] Hugo-Integration entwickeln" \
|
||||
"service-request,enhancement" \
|
||||
"${DEFAULT_ASSIGNEE:-}"
|
||||
;;
|
||||
|
||||
"architecture")
|
||||
topic="${2:-middleware-optimization}"
|
||||
create_issue \
|
||||
"[ARCH] Gateway $topic" \
|
||||
"## 🎯 Architektur-Thema
|
||||
Gateway-$topic für bessere Performance/Maintainability
|
||||
|
||||
## 📊 Aktuelle Situation
|
||||
**Current Implementation:**
|
||||
- [Beschreibung des aktuellen Zustands]
|
||||
- [Identifizierte Probleme oder Limitations]
|
||||
- [Performance-Metrics wenn verfügbar]
|
||||
|
||||
## 💡 Vorgeschlagene Änderung
|
||||
**Proposed Solution:**
|
||||
- [Detaillierte Beschreibung der vorgeschlagenen Lösung]
|
||||
- [Erwartete Verbesserungen]
|
||||
- [Implementation-Approach]
|
||||
|
||||
## 🔄 Alternativen
|
||||
1. **Option A:** [Beschreibung]
|
||||
- Vorteile: [...]
|
||||
- Nachteile: [...]
|
||||
|
||||
2. **Option B:** [Beschreibung]
|
||||
- Vorteile: [...]
|
||||
- Nachteile: [...]
|
||||
|
||||
## 📈 Betroffene Bereiche
|
||||
- [ ] Gateway-Performance
|
||||
- [ ] Service-Integration
|
||||
- [ ] Security (Auth/Rate-Limiting)
|
||||
- [ ] Configuration-Management
|
||||
- [ ] Monitoring/Logging
|
||||
- [ ] Hugo-Integration
|
||||
|
||||
## 🧪 Testing-Impact
|
||||
- [ ] Unit-Tests anpassen
|
||||
- [ ] Integration-Tests erweitern
|
||||
- [ ] Performance-Tests durchführen
|
||||
- [ ] Backward-Compatibility prüfen
|
||||
|
||||
## 📋 Implementation-Plan
|
||||
1. **Phase 1:** [Erste Schritte]
|
||||
2. **Phase 2:** [Hauptimplementierung]
|
||||
3. **Phase 3:** [Testing und Optimization]
|
||||
4. **Phase 4:** [Deployment und Monitoring]
|
||||
|
||||
## ⚡ Priorität
|
||||
🔥 Hoch - kritisch für Projekt-Erfolg" \
|
||||
"architecture,gateway,enhancement" \
|
||||
"${DEFAULT_ASSIGNEE:-}"
|
||||
;;
|
||||
|
||||
"performance")
|
||||
component="${2:-gateway}"
|
||||
create_issue \
|
||||
"[PERF] $component Performance-Optimierung" \
|
||||
"## 📊 Performance-Problem
|
||||
**Component:** $component
|
||||
**Issue:** [Beschreibung des Performance-Problems]
|
||||
|
||||
## 🔍 Gemessene Werte
|
||||
**Current Performance:**
|
||||
- **Response Time:** [X]ms average
|
||||
- **Throughput:** [X] requests/second
|
||||
- **Resource Usage:** [X]% CPU, [X]MB Memory
|
||||
- **Error Rate:** [X]% under load
|
||||
|
||||
**Target Performance:**
|
||||
- **Response Time:** <[Y]ms
|
||||
- **Throughput:** >[Y] requests/second
|
||||
- **Resource Usage:** <[Y]% CPU, <[Y]MB Memory
|
||||
- **Error Rate:** <[Y]% under normal load
|
||||
|
||||
## 🎯 Performance-Ziele
|
||||
- [ ] **Latency-Optimization:** Reduce response time by [X]%
|
||||
- [ ] **Throughput-Increase:** Handle [X]x more concurrent requests
|
||||
- [ ] **Resource-Efficiency:** Reduce CPU/Memory usage
|
||||
- [ ] **Scalability:** Support [X] services without degradation
|
||||
|
||||
## 🔍 Profiling-Results
|
||||
**Current Bottlenecks:**
|
||||
\`\`\`
|
||||
[Paste go-pprof results oder performance measurements]
|
||||
\`\`\`
|
||||
|
||||
**Identified Issues:**
|
||||
- [Issue 1]: [Description]
|
||||
- [Issue 2]: [Description]
|
||||
- [Issue 3]: [Description]
|
||||
|
||||
## 🛠️ Optimization-Strategy
|
||||
1. **Code-Optimization:**
|
||||
- [Specific code changes]
|
||||
- [Algorithm improvements]
|
||||
|
||||
2. **Infrastructure-Optimization:**
|
||||
- [Connection pooling]
|
||||
- [Caching strategies]
|
||||
|
||||
3. **Configuration-Tuning:**
|
||||
- [Timeout adjustments]
|
||||
- [Buffer size optimization]
|
||||
|
||||
## 🧪 Performance-Testing-Plan
|
||||
- [ ] **Baseline-Measurements:** Before optimization
|
||||
- [ ] **Load-Testing:** Various traffic patterns
|
||||
- [ ] **Stress-Testing:** Breaking point analysis
|
||||
- [ ] **Endurance-Testing:** Long-running stability
|
||||
- [ ] **Regression-Testing:** Ensure no functionality broken
|
||||
|
||||
## 📋 Success-Criteria
|
||||
- [ ] Target response time achieved
|
||||
- [ ] Target throughput achieved
|
||||
- [ ] Resource usage within limits
|
||||
- [ ] No regressions in functionality
|
||||
- [ ] Monitoring alerts configured
|
||||
|
||||
## ⚡ Priorität
|
||||
🔥 Hoch - Performance ist kritisch" \
|
||||
"performance,optimization,$component" \
|
||||
"${DEFAULT_ASSIGNEE:-}"
|
||||
;;
|
||||
|
||||
# Weitere templates hier... (gekürzt für Übersichtlichkeit)
|
||||
|
||||
"custom")
|
||||
echo "🎯 Custom Furt Issue Creator"
|
||||
echo ""
|
||||
read -p "📝 Title: " title
|
||||
echo "📋 Body (press Ctrl+D when finished):"
|
||||
body=$(cat)
|
||||
read -p "🏷️ Labels (comma separated): " labels
|
||||
read -p "👤 Assignee (optional): " assignee
|
||||
|
||||
create_issue "$title" "$body" "$labels" "${assignee:-$DEFAULT_ASSIGNEE}"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "🎯 Furt API-Gateway Issue Creator"
|
||||
echo ""
|
||||
echo "Usage: $0 [TEMPLATE] [OPTIONS]"
|
||||
echo ""
|
||||
echo "📋 Available Templates:"
|
||||
echo " service [name] New service for gateway (default: newsletter)"
|
||||
echo " architecture [topic] Gateway architecture discussion (default: middleware-optimization)"
|
||||
echo " performance [comp] Performance optimization (default: gateway)"
|
||||
echo " api [service] API contract update (default: formular2mail)"
|
||||
echo " security [comp] Security review/issue (default: gateway)"
|
||||
echo " bug [comp] [desc] Bug report (default: gateway)"
|
||||
echo " hugo [feature] Hugo integration (default: shortcode)"
|
||||
echo " deployment [comp] Deployment issue (default: gateway)"
|
||||
echo " custom Custom issue (interactive)"
|
||||
echo ""
|
||||
echo "🚀 Examples:"
|
||||
echo " $0 service newsletter # Create newsletter service request"
|
||||
echo " $0 architecture rate-limiting # Discuss rate limiting architecture"
|
||||
echo " $0 performance gateway # Gateway performance optimization"
|
||||
echo " $0 custom # Interactive custom issue"
|
||||
;;
|
||||
esac
|
||||
|
||||
250
scripts/get_issues.sh
Executable file
250
scripts/get_issues.sh
Executable file
|
|
@ -0,0 +1,250 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Load environment
|
||||
if [ -f .env ]; then
|
||||
export $(cat .env | grep -v '^#' | xargs)
|
||||
else
|
||||
echo "❌ .env file not found!"
|
||||
echo "📋 Copy .env.example to .env and configure it first"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate required variables
|
||||
if [ -z "$GITEA_URL" ] || [ -z "$REPO_OWNER" ] || [ -z "$REPO_NAME" ] || [ -z "$GITEA_TOKEN" ]; then
|
||||
echo "❌ Missing required environment variables in .env"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
|
||||
|
||||
# Get all issues with nice formatting
|
||||
get_all_issues() {
|
||||
log_info "Fetching all issues..."
|
||||
echo ""
|
||||
|
||||
response=$(curl -s "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/issues" \
|
||||
-H "Authorization: token $GITEA_TOKEN")
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ Error fetching issues"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$response" | jq -r '.[] |
|
||||
"🎯 #\(.number) \(.title)",
|
||||
" 📊 State: \(.state) | 🏷️ Labels: \(.labels | map(.name) | join(", ") // "none")",
|
||||
" 🔗 \(.html_url)",
|
||||
""'
|
||||
}
|
||||
|
||||
# Get issues by label
|
||||
get_issues_by_label() {
|
||||
local label="$1"
|
||||
log_info "Fetching issues with label: $label"
|
||||
echo ""
|
||||
|
||||
response=$(curl -s "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/issues?labels=$label" \
|
||||
-H "Authorization: token $GITEA_TOKEN")
|
||||
|
||||
echo "$response" | jq -r '.[] |
|
||||
"🎯 #\(.number) \(.title)",
|
||||
" 📊 \(.state) | 🔗 \(.html_url)",
|
||||
""'
|
||||
}
|
||||
|
||||
# Get issue details
|
||||
get_issue_details() {
|
||||
local issue_number="$1"
|
||||
log_info "Fetching details for issue #$issue_number"
|
||||
echo ""
|
||||
|
||||
response=$(curl -s "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/issues/$issue_number" \
|
||||
-H "Authorization: token $GITEA_TOKEN")
|
||||
|
||||
echo "$response" | jq -r '
|
||||
"🎯 Issue #\(.number): \(.title)",
|
||||
"📊 State: \(.state)",
|
||||
"👤 Assignees: \(.assignees | map(.login) | join(", ") // "none")",
|
||||
"🏷️ Labels: \(.labels | map(.name) | join(", ") // "none")",
|
||||
"📅 Created: \(.created_at)",
|
||||
"🔗 URL: \(.html_url)",
|
||||
"",
|
||||
"📝 Body:",
|
||||
"\(.body // "No description")",
|
||||
""'
|
||||
}
|
||||
|
||||
# Close issue
|
||||
close_issue() {
|
||||
local issue_number="$1"
|
||||
log_info "Closing issue #$issue_number"
|
||||
|
||||
response=$(curl -s -w "\n%{http_code}" -X PATCH \
|
||||
"$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/issues/$issue_number" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"state": "closed"}')
|
||||
|
||||
http_code=$(echo "$response" | tail -n1)
|
||||
if [ "$http_code" = "201" ]; then
|
||||
log_success "Issue #$issue_number closed"
|
||||
else
|
||||
echo "❌ Failed to close issue (HTTP: $http_code)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Get pipeline status (issues grouped by Kanban columns for Furt)
|
||||
get_pipeline_status() {
|
||||
log_info "Furt API Gateway - Pipeline Status Overview"
|
||||
echo ""
|
||||
|
||||
echo "🔧 SERVICE REQUESTS:"
|
||||
get_issues_by_label "service-request" | head -10
|
||||
|
||||
echo "🏗️ ARCHITECTURE DISCUSSIONS:"
|
||||
get_issues_by_label "architecture"
|
||||
|
||||
echo "🚀 PERFORMANCE OPTIMIZATIONS:"
|
||||
get_issues_by_label "performance"
|
||||
|
||||
echo "🔒 SECURITY REVIEWS:"
|
||||
get_issues_by_label "security"
|
||||
|
||||
echo "🐛 BUGS:"
|
||||
get_issues_by_label "bug"
|
||||
|
||||
echo "🌐 HUGO INTEGRATIONS:"
|
||||
get_issues_by_label "hugo-integration"
|
||||
|
||||
echo "📋 WORK IN PROGRESS:"
|
||||
get_issues_by_label "enhancement" | head -5
|
||||
}
|
||||
|
||||
# Issue statistics
|
||||
get_stats() {
|
||||
log_info "Furt API Gateway - Issue Statistics"
|
||||
echo ""
|
||||
|
||||
all_issues=$(curl -s "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/issues" \
|
||||
-H "Authorization: token $GITEA_TOKEN")
|
||||
|
||||
total=$(echo "$all_issues" | jq length)
|
||||
open=$(echo "$all_issues" | jq '[.[] | select(.state == "open")] | length')
|
||||
closed=$(echo "$all_issues" | jq '[.[] | select(.state == "closed")] | length')
|
||||
|
||||
echo "📊 Total Issues: $total"
|
||||
echo "✅ Open: $open"
|
||||
echo "🔒 Closed: $closed"
|
||||
echo ""
|
||||
|
||||
echo "🏷️ Furt Labels:"
|
||||
echo "$all_issues" | jq -r '[.[] | .labels[].name] | group_by(.) | map({label: .[0], count: length}) | sort_by(.count) | reverse | limit(10; .[]) | " \(.label): \(.count)"'
|
||||
}
|
||||
|
||||
case "${1:-help}" in
|
||||
"all"|"")
|
||||
get_all_issues
|
||||
;;
|
||||
"gateway")
|
||||
get_issues_by_label "gateway"
|
||||
;;
|
||||
"service-request")
|
||||
get_issues_by_label "service-request"
|
||||
;;
|
||||
"service-formular2mail")
|
||||
get_issues_by_label "service-formular2mail"
|
||||
;;
|
||||
"service-sagjan")
|
||||
get_issues_by_label "service-sagjan"
|
||||
;;
|
||||
"architecture")
|
||||
get_issues_by_label "architecture"
|
||||
;;
|
||||
"performance")
|
||||
get_issues_by_label "performance"
|
||||
;;
|
||||
"security")
|
||||
get_issues_by_label "security"
|
||||
;;
|
||||
"bug")
|
||||
get_issues_by_label "bug"
|
||||
;;
|
||||
"enhancement")
|
||||
get_issues_by_label "enhancement"
|
||||
;;
|
||||
"hugo")
|
||||
get_issues_by_label "hugo-integration"
|
||||
;;
|
||||
"deployment")
|
||||
get_issues_by_label "deployment"
|
||||
;;
|
||||
"testing")
|
||||
get_issues_by_label "testing"
|
||||
;;
|
||||
"documentation")
|
||||
get_issues_by_label "documentation"
|
||||
;;
|
||||
"pipeline")
|
||||
get_pipeline_status
|
||||
;;
|
||||
"stats")
|
||||
get_stats
|
||||
;;
|
||||
"close")
|
||||
if [ -z "$2" ]; then
|
||||
echo "Usage: $0 close ISSUE_NUMBER"
|
||||
exit 1
|
||||
fi
|
||||
close_issue "$2"
|
||||
;;
|
||||
[0-9]*)
|
||||
get_issue_details "$1"
|
||||
;;
|
||||
*)
|
||||
echo "🎯 Furt API Gateway - Issues Manager"
|
||||
echo ""
|
||||
echo "Usage: $0 [COMMAND] [OPTIONS]"
|
||||
echo ""
|
||||
echo "📋 List Commands:"
|
||||
echo " all List all issues (default)"
|
||||
echo " gateway Gateway core issues"
|
||||
echo " service-request New service requests"
|
||||
echo " service-formular2mail Formular2mail service issues"
|
||||
echo " service-sagjan Sagjan service issues"
|
||||
echo " architecture Architecture discussions"
|
||||
echo " performance Performance optimizations"
|
||||
echo " security Security reviews"
|
||||
echo " bug Bug reports"
|
||||
echo " enhancement New features"
|
||||
echo " hugo Hugo integration issues"
|
||||
echo " deployment Deployment issues"
|
||||
echo " testing Testing issues"
|
||||
echo " documentation Documentation updates"
|
||||
echo ""
|
||||
echo "📊 Analysis Commands:"
|
||||
echo " pipeline Kanban pipeline status"
|
||||
echo " stats Issue statistics"
|
||||
echo ""
|
||||
echo "⚙️ Management Commands:"
|
||||
echo " close NUM Close issue #NUM"
|
||||
echo " NUM Show details for issue #NUM"
|
||||
echo ""
|
||||
echo "🚀 Examples:"
|
||||
echo " $0 # List all issues"
|
||||
echo " $0 pipeline # Show pipeline status"
|
||||
echo " $0 service-request # Show service requests"
|
||||
echo " $0 gateway # Show gateway issues"
|
||||
echo " $0 5 # Show issue #5 details"
|
||||
echo " $0 close 3 # Close issue #3"
|
||||
;;
|
||||
esac
|
||||
|
||||
156
scripts/update_issue.sh
Executable file
156
scripts/update_issue.sh
Executable file
|
|
@ -0,0 +1,156 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Load environment
|
||||
if [ -f .env ]; then
|
||||
export $(cat .env | grep -v '^#' | xargs)
|
||||
else
|
||||
echo "❌ .env file not found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
# Get all labels with IDs
|
||||
declare -A LABEL_IDS
|
||||
get_labels() {
|
||||
response=$(curl -s "$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/labels" \
|
||||
-H "Authorization: token $GITEA_TOKEN")
|
||||
while IFS= read -r line; do
|
||||
name=$(echo "$line" | jq -r '.name')
|
||||
id=$(echo "$line" | jq -r '.id')
|
||||
LABEL_IDS["$name"]="$id"
|
||||
done < <(echo "$response" | jq -c '.[]')
|
||||
}
|
||||
|
||||
# Add comment to issue
|
||||
add_comment() {
|
||||
local issue_number="$1"
|
||||
local comment="$2"
|
||||
|
||||
# Use jq for proper JSON escaping
|
||||
local json_payload=$(jq -n --arg body "$comment" '{body: $body}')
|
||||
|
||||
response=$(curl -s -w "\n%{http_code}" -X POST \
|
||||
"$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/issues/$issue_number/comments" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$json_payload")
|
||||
|
||||
http_code=$(echo "$response" | tail -n1)
|
||||
if [ "$http_code" = "201" ]; then
|
||||
log_success "Comment added to issue #$issue_number"
|
||||
else
|
||||
log_error "Failed to add comment (HTTP: $http_code)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Update issue labels - FIXED VERSION
|
||||
update_labels() {
|
||||
local issue_number="$1"
|
||||
local labels_string="$2"
|
||||
|
||||
get_labels
|
||||
|
||||
# Convert to ID array
|
||||
local valid_label_ids=()
|
||||
IFS=',' read -ra LABEL_ARRAY <<< "$labels_string"
|
||||
|
||||
for label in "${LABEL_ARRAY[@]}"; do
|
||||
label=$(echo "$label" | xargs)
|
||||
if [ -n "${LABEL_IDS[$label]}" ]; then
|
||||
valid_label_ids+=("${LABEL_IDS[$label]}")
|
||||
else
|
||||
log_error "Label '$label' not found!"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Build ID array JSON
|
||||
local labels_json="["
|
||||
for i in "${!valid_label_ids[@]}"; do
|
||||
if [ $i -gt 0 ]; then
|
||||
labels_json="${labels_json},"
|
||||
fi
|
||||
labels_json="${labels_json}${valid_label_ids[$i]}"
|
||||
done
|
||||
labels_json="${labels_json}]"
|
||||
|
||||
response=$(curl -s -w "\n%{http_code}" -X PUT \
|
||||
"$GITEA_URL/api/v1/repos/$REPO_OWNER/$REPO_NAME/issues/$issue_number/labels" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$labels_json")
|
||||
|
||||
http_code=$(echo "$response" | tail -n1)
|
||||
if [ "$http_code" = "200" ]; then
|
||||
log_success "Labels updated for issue #$issue_number"
|
||||
else
|
||||
log_error "Failed to update labels (HTTP: $http_code)"
|
||||
fi
|
||||
}
|
||||
|
||||
case "${1:-help}" in
|
||||
"comment")
|
||||
if [ -z "$2" ] || [ -z "$3" ]; then
|
||||
echo "Usage: $0 comment ISSUE_NUMBER \"COMMENT_TEXT\""
|
||||
exit 1
|
||||
fi
|
||||
add_comment "$2" "$3"
|
||||
;;
|
||||
"labels")
|
||||
if [ -z "$2" ] || [ -z "$3" ]; then
|
||||
echo "Usage: $0 labels ISSUE_NUMBER \"label1,label2,label3\""
|
||||
exit 1
|
||||
fi
|
||||
update_labels "$2" "$3"
|
||||
;;
|
||||
"progress")
|
||||
if [ -z "$2" ]; then
|
||||
echo "Usage: $0 progress ISSUE_NUMBER"
|
||||
exit 1
|
||||
fi
|
||||
add_comment "$2" "📊 **Progress Update:** Arbeit an dieser Analyse läuft. Erste Quellen werden gesammelt und Framework-Relevanz geprüft."
|
||||
update_labels "$2" "work-in-progress"
|
||||
;;
|
||||
"review")
|
||||
if [ -z "$2" ]; then
|
||||
echo "Usage: $0 review ISSUE_NUMBER"
|
||||
exit 1
|
||||
fi
|
||||
add_comment "$2" "👀 **Ready for Review:** Erste Analyse abgeschlossen. Bitte um Peer-Review der Quellen und Framework-Integration."
|
||||
update_labels "$2" "needs-review"
|
||||
;;
|
||||
"fact-check")
|
||||
if [ -z "$2" ]; then
|
||||
echo "Usage: $0 fact-check ISSUE_NUMBER"
|
||||
exit 1
|
||||
fi
|
||||
add_comment "$2" "🔍 **Fact-Check Required:** Kritische Behauptungen gefunden die zusätzliche Quellen-Verifikation benötigen."
|
||||
update_labels "$2" "fact-check-needed"
|
||||
;;
|
||||
*)
|
||||
echo "🔧 Issue Update Tool (FIXED VERSION)"
|
||||
echo ""
|
||||
echo "Usage: $0 COMMAND ISSUE_NUMBER [OPTIONS]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " comment NUM \"TEXT\" Add comment to issue"
|
||||
echo " labels NUM \"l1,l2\" Update issue labels (using IDs)"
|
||||
echo " progress NUM Mark as work-in-progress"
|
||||
echo " review NUM Mark as ready for review"
|
||||
echo " fact-check NUM Mark as needing fact-check"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 comment 5 \"Erste Quellen gefunden\""
|
||||
echo " $0 labels 3 \"regional-case,work-in-progress\""
|
||||
echo " $0 progress 7"
|
||||
;;
|
||||
esac
|
||||
Loading…
Add table
Add a link
Reference in a new issue