package apienhancement import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "net/http" "strings" "sync" "time" "github.com/labstack/echo/v4" "github.com/sirupsen/logrus" ) type RESTAPIEnhancement struct { logger *logrus.Logger webhooks *WebhookManager integrations *IntegrationManager rateLimiter *RateLimiter auth *AuthManager } type WebhookManager struct { webhooks map[string]*Webhook mu sync.RWMutex } type Webhook struct { ID string `json:"id"` Name string `json:"name"` URL string `json:"url"` Events []string `json:"events"` Secret string `json:"secret,omitempty"` Headers map[string]string `json:"headers"` Enabled bool `json:"enabled"` RetryCount int `json:"retry_count"` LastSent *time.Time `json:"last_sent,omitempty"` LastError string `json:"last_error,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type IntegrationManager struct { integrations map[string]*Integration mu sync.RWMutex } type Integration struct { ID string `json:"id"` Name string `json:"name"` Type string `json:"type"` Config map[string]interface{} `json:"config"` Enabled bool `json:"enabled"` LastSync *time.Time `json:"last_sync,omitempty"` Status string `json:"status"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } type RateLimiter struct { limits map[string]*RateLimit mu sync.RWMutex } type RateLimit struct { Key string Requests int Window time.Duration LastReset time.Time } type AuthManager struct { apiKeys map[string]*APIKey mu sync.RWMutex } type APIKey struct { ID string `json:"id"` Name string `json:"name"` Key string `json:"key,omitempty"` Scopes []string `json:"scopes"` ExpiresAt *time.Time `json:"expires_at,omitempty"` CreatedAt time.Time `json:"created_at"` LastUsed *time.Time `json:"last_used,omitempty"` } type WebhookEvent struct { Type string `json:"type"` Timestamp time.Time `json:"timestamp"` Data map[string]interface{} `json:"data"` Source string `json:"source"` } type APIResponse struct { Success bool `json:"success"` Data interface{} `json:"data,omitempty"` Error string `json:"error,omitempty"` Timestamp time.Time `json:"timestamp"` Metadata map[string]interface{} `json:"metadata,omitempty"` } func NewRESTAPIEnhancement(logger *logrus.Logger) *RESTAPIEnhancement { enhancement := &RESTAPIEnhancement{ logger: logger, webhooks: NewWebhookManager(), integrations: NewIntegrationManager(), rateLimiter: NewRateLimiter(), auth: NewAuthManager(), } return enhancement } func NewWebhookManager() *WebhookManager { return &WebhookManager{ webhooks: make(map[string]*Webhook), } } func NewIntegrationManager() *IntegrationManager { return &IntegrationManager{ integrations: make(map[string]*Integration), } } func NewRateLimiter() *RateLimiter { return &RateLimiter{ limits: make(map[string]*RateLimit), } } func NewAuthManager() *AuthManager { return &AuthManager{ apiKeys: make(map[string]*APIKey), } } func (rae *RESTAPIEnhancement) RegisterRoutes(e *echo.Echo) { // Webhook management e.GET("/api/v1/webhooks", rae.ListWebhooks) e.POST("/api/v1/webhooks", rae.CreateWebhook) e.GET("/api/v1/webhooks/:id", rae.GetWebhook) e.PUT("/api/v1/webhooks/:id", rae.UpdateWebhook) e.DELETE("/api/v1/webhooks/:id", rae.DeleteWebhook) e.POST("/api/v1/webhooks/:id/test", rae.TestWebhook) // Integration management e.GET("/api/v1/integrations", rae.ListIntegrations) e.POST("/api/v1/integrations", rae.CreateIntegration) e.GET("/api/v1/integrations/:id", rae.GetIntegration) e.PUT("/api/v1/integrations/:id", rae.UpdateIntegration) e.DELETE("/api/v1/integrations/:id", rae.DeleteIntegration) e.POST("/api/v1/integrations/:id/sync", rae.SyncIntegration) // API key management e.GET("/api/v1/api-keys", rae.ListAPIKeys) e.POST("/api/v1/api-keys", rae.CreateAPIKey) e.GET("/api/v1/api-keys/:id", rae.GetAPIKey) e.PUT("/api/v1/api-keys/:id", rae.UpdateAPIKey) e.DELETE("/api/v1/api-keys/:id", rae.DeleteAPIKey) // Enhanced API endpoints e.GET("/api/v1/status", rae.GetSystemStatus) e.GET("/api/v1/health", rae.GetHealthCheck) e.GET("/api/v1/version", rae.GetVersion) // Apply middleware e.Use(rae.RateLimitMiddleware) e.Use(rae.AuthMiddleware) e.Use(rae.LoggingMiddleware) e.Use(rae.CORSMiddleware) } // Webhook management func (rae *RESTAPIEnhancement) ListWebhooks(c echo.Context) error { webhooks := rae.webhooks.ListWebhooks() return c.JSON(http.StatusOK, webhooks) } func (rae *RESTAPIEnhancement) CreateWebhook(c echo.Context) error { var webhook Webhook if err := c.Bind(&webhook); err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid webhook data: %v", err)) } // Validate webhook if err := rae.validateWebhook(&webhook); err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("webhook validation failed: %v", err)) } // Set timestamps now := time.Now() webhook.ID = generateID("webhook") webhook.CreatedAt = now webhook.UpdatedAt = now // Save webhook rae.webhooks.AddWebhook(&webhook) rae.logger.Infof("Created webhook: %s", webhook.ID) return c.JSON(http.StatusCreated, webhook) } func (rae *RESTAPIEnhancement) GetWebhook(c echo.Context) error { id := c.Param("id") webhook, exists := rae.webhooks.GetWebhook(id) if !exists { return echo.NewHTTPError(http.StatusNotFound, "webhook not found") } return c.JSON(http.StatusOK, webhook) } func (rae *RESTAPIEnhancement) UpdateWebhook(c echo.Context) error { id := c.Param("id") // Get existing webhook existing, exists := rae.webhooks.GetWebhook(id) if !exists { return echo.NewHTTPError(http.StatusNotFound, "webhook not found") } // Bind update data var update Webhook if err := c.Bind(&update); err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid update data: %v", err)) } // Update fields update.ID = existing.ID update.CreatedAt = existing.CreatedAt update.UpdatedAt = time.Now() // Validate updated webhook if err := rae.validateWebhook(&update); err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("webhook validation failed: %v", err)) } // Save updated webhook rae.webhooks.UpdateWebhook(&update) rae.logger.Infof("Updated webhook: %s", id) return c.JSON(http.StatusOK, update) } func (rae *RESTAPIEnhancement) DeleteWebhook(c echo.Context) error { id := c.Param("id") if err := rae.webhooks.DeleteWebhook(id); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to delete webhook: %v", err)) } rae.logger.Infof("Deleted webhook: %s", id) return c.NoContent(http.StatusNoContent) } func (rae *RESTAPIEnhancement) TestWebhook(c echo.Context) error { id := c.Param("id") webhook, exists := rae.webhooks.GetWebhook(id) if !exists { return echo.NewHTTPError(http.StatusNotFound, "webhook not found") } // Send test event event := WebhookEvent{ Type: "test", Timestamp: time.Now(), Data: map[string]interface{}{ "message": "This is a test webhook event", "webhook_id": webhook.ID, }, Source: "debian-forge-composer", } if err := rae.sendWebhook(webhook, event); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("webhook test failed: %v", err)) } return c.JSON(http.StatusOK, map[string]string{"status": "test sent successfully"}) } // Integration management func (rae *RESTAPIEnhancement) ListIntegrations(c echo.Context) error { integrations := rae.integrations.ListIntegrations() return c.JSON(http.StatusOK, integrations) } func (rae *RESTAPIEnhancement) CreateIntegration(c echo.Context) error { var integration Integration if err := c.Bind(&integration); err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid integration data: %v", err)) } // Validate integration if err := rae.validateIntegration(&integration); err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("integration validation failed: %v", err)) } // Set timestamps now := time.Now() integration.ID = generateID("integration") integration.CreatedAt = now integration.UpdatedAt = now integration.Status = "active" // Save integration rae.integrations.AddIntegration(&integration) rae.logger.Infof("Created integration: %s", integration.ID) return c.JSON(http.StatusCreated, integration) } func (rae *RESTAPIEnhancement) GetIntegration(c echo.Context) error { id := c.Param("id") integration, exists := rae.integrations.GetIntegration(id) if !exists { return echo.NewHTTPError(http.StatusNotFound, "integration not found") } return c.JSON(http.StatusOK, integration) } func (rae *RESTAPIEnhancement) UpdateIntegration(c echo.Context) error { id := c.Param("id") // Get existing integration existing, exists := rae.integrations.GetIntegration(id) if !exists { return echo.NewHTTPError(http.StatusNotFound, "integration not found") } // Bind update data var update Integration if err := c.Bind(&update); err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid update data: %v", err)) } // Update fields update.ID = existing.ID update.CreatedAt = existing.CreatedAt update.UpdatedAt = time.Now() // Validate updated integration if err := rae.validateIntegration(&update); err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("integration validation failed: %v", err)) } // Save updated integration rae.integrations.UpdateIntegration(&update) rae.logger.Infof("Updated integration: %s", id) return c.JSON(http.StatusOK, update) } func (rae *RESTAPIEnhancement) DeleteIntegration(c echo.Context) error { id := c.Param("id") if err := rae.integrations.DeleteIntegration(id); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to delete integration: %v", err)) } rae.logger.Infof("Deleted integration: %s", id) return c.NoContent(http.StatusNoContent) } func (rae *RESTAPIEnhancement) SyncIntegration(c echo.Context) error { id := c.Param("id") integration, exists := rae.integrations.GetIntegration(id) if !exists { return echo.NewHTTPError(http.StatusNotFound, "integration not found") } // Perform integration sync if err := rae.performIntegrationSync(integration); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("integration sync failed: %v", err)) } // Update last sync time now := time.Now() integration.LastSync = &now integration.UpdatedAt = now rae.integrations.UpdateIntegration(integration) return c.JSON(http.StatusOK, map[string]string{"status": "sync completed successfully"}) } // API key management func (rae *RESTAPIEnhancement) ListAPIKeys(c echo.Context) error { apiKeys := rae.auth.ListAPIKeys() return c.JSON(http.StatusOK, apiKeys) } func (rae *RESTAPIEnhancement) CreateAPIKey(c echo.Context) error { var apiKey APIKey if err := c.Bind(&apiKey); err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid API key data: %v", err)) } // Validate API key if err := rae.validateAPIKey(&apiKey); err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("API key validation failed: %v", err)) } // Generate API key apiKey.ID = generateID("apikey") apiKey.Key = generateAPIKey() apiKey.CreatedAt = time.Now() // Save API key rae.auth.AddAPIKey(&apiKey) rae.logger.Infof("Created API key: %s", apiKey.ID) return c.JSON(http.StatusCreated, apiKey) } func (rae *RESTAPIEnhancement) GetAPIKey(c echo.Context) error { id := c.Param("id") apiKey, exists := rae.auth.GetAPIKey(id) if !exists { return echo.NewHTTPError(http.StatusNotFound, "API key not found") } // Don't expose the actual key apiKey.Key = "" return c.JSON(http.StatusOK, apiKey) } func (rae *RESTAPIEnhancement) UpdateAPIKey(c echo.Context) error { id := c.Param("id") // Get existing API key existing, exists := rae.auth.GetAPIKey(id) if !exists { return echo.NewHTTPError(http.StatusNotFound, "API key not found") } // Bind update data var update APIKey if err := c.Bind(&update); err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid update data: %v", err)) } // Update fields update.ID = existing.ID update.Key = existing.Key // Keep existing key update.CreatedAt = existing.CreatedAt update.UpdatedAt = time.Now() // Validate updated API key if err := rae.validateAPIKey(&update); err != nil { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("API key validation failed: %v", err)) } // Save updated API key rae.auth.UpdateAPIKey(&update) rae.logger.Infof("Updated API key: %s", id) return c.JSON(http.StatusOK, update) } func (rae *RESTAPIEnhancement) DeleteAPIKey(c echo.Context) error { id := c.Param("id") if err := rae.auth.DeleteAPIKey(id); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to delete API key: %v", err)) } rae.logger.Infof("Deleted API key: %s", id) return c.NoContent(http.StatusNoContent) } // Enhanced API endpoints func (rae *RESTAPIEnhancement) GetSystemStatus(c echo.Context) error { status := map[string]interface{}{ "status": "operational", "timestamp": time.Now(), "version": "1.0.0", "uptime": "24h30m15s", "services": map[string]string{ "api": "healthy", "database": "healthy", "workers": "healthy", "webhooks": "healthy", "integrations": "healthy", }, } return c.JSON(http.StatusOK, status) } func (rae *RESTAPIEnhancement) GetHealthCheck(c echo.Context) error { health := map[string]interface{}{ "status": "healthy", "checks": map[string]interface{}{ "database": map[string]interface{}{ "status": "healthy", "response_time": "5ms", }, "workers": map[string]interface{}{ "status": "healthy", "active_count": 5, "total_count": 8, }, "webhooks": map[string]interface{}{ "status": "healthy", "active_count": 3, }, }, } return c.JSON(http.StatusOK, health) } func (rae *RESTAPIEnhancement) GetVersion(c echo.Context) error { version := map[string]interface{}{ "version": "1.0.0", "build_date": "2024-12-19", "git_commit": "abc123def", "go_version": "1.23.9", "api_version": "v1", } return c.JSON(http.StatusOK, version) } // Middleware func (rae *RESTAPIEnhancement) RateLimitMiddleware(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { clientIP := c.RealIP() if !rae.rateLimiter.AllowRequest(clientIP) { return echo.NewHTTPError(http.StatusTooManyRequests, "rate limit exceeded") } return next(c) } } func (rae *RESTAPIEnhancement) AuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { // Skip auth for public endpoints if isPublicEndpoint(c.Path()) { return next(c) } apiKey := c.Request().Header.Get("X-API-Key") if apiKey == "" { return echo.NewHTTPError(http.StatusUnauthorized, "API key required") } if !rae.auth.ValidateAPIKey(apiKey) { return echo.NewHTTPError(http.StatusUnauthorized, "invalid API key") } return next(c) } } func (rae *RESTAPIEnhancement) LoggingMiddleware(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { start := time.Now() err := next(c) // Log request rae.logger.WithFields(logrus.Fields{ "method": c.Request().Method, "path": c.Path(), "status": c.Response().Status, "duration": time.Since(start), "user_agent": c.Request().UserAgent(), "ip": c.RealIP(), }).Info("API request") return err } } func (rae *RESTAPIEnhancement) CORSMiddleware(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { c.Response().Header().Set("Access-Control-Allow-Origin", "*") c.Response().Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") c.Response().Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-API-Key") if c.Request().Method == "OPTIONS" { return c.NoContent(http.StatusNoContent) } return next(c) } } // Helper functions func (rae *RESTAPIEnhancement) validateWebhook(webhook *Webhook) error { if webhook.Name == "" { return fmt.Errorf("name is required") } if webhook.URL == "" { return fmt.Errorf("URL is required") } if len(webhook.Events) == 0 { return fmt.Errorf("at least one event is required") } return nil } func (rae *RESTAPIEnhancement) validateIntegration(integration *Integration) error { if integration.Name == "" { return fmt.Errorf("name is required") } if integration.Type == "" { return fmt.Errorf("type is required") } return nil } func (rae *RESTAPIEnhancement) validateAPIKey(apiKey *APIKey) error { if apiKey.Name == "" { return fmt.Errorf("name is required") } if len(apiKey.Scopes) == 0 { return fmt.Errorf("at least one scope is required") } return nil } func (rae *RESTAPIEnhancement) sendWebhook(webhook *Webhook, event WebhookEvent) error { // Prepare payload payload, err := json.Marshal(event) if err != nil { return fmt.Errorf("failed to marshal event: %w", err) } // Create request req, err := http.NewRequest("POST", webhook.URL, strings.NewReader(string(payload))) if err != nil { return fmt.Errorf("failed to create request: %w", err) } // Set headers req.Header.Set("Content-Type", "application/json") req.Header.Set("User-Agent", "debian-forge-composer/1.0.0") // Add signature if secret is configured if webhook.Secret != "" { signature := generateWebhookSignature(payload, webhook.Secret) req.Header.Set("X-Webhook-Signature", signature) } // Add custom headers for key, value := range webhook.Headers { req.Header.Set(key, value) } // Send request client := &http.Client{Timeout: 30 * time.Second} resp, err := client.Do(req) if err != nil { return fmt.Errorf("failed to send webhook: %w", err) } defer resp.Body.Close() // Check response if resp.StatusCode < 200 || resp.StatusCode >= 300 { return fmt.Errorf("webhook returned status %d", resp.StatusCode) } // Update webhook status now := time.Now() webhook.LastSent = &now webhook.LastError = "" webhook.UpdatedAt = now return nil } func (rae *RESTAPIEnhancement) performIntegrationSync(integration *Integration) error { // This would implement the actual integration sync logic // For now, just simulate a sync time.Sleep(100 * time.Millisecond) return nil } func generateWebhookSignature(payload []byte, secret string) string { h := hmac.New(sha256.New, []byte(secret)) h.Write(payload) return "sha256=" + hex.EncodeToString(h.Sum(nil)) } func generateID(prefix string) string { return fmt.Sprintf("%s-%d", prefix, time.Now().UnixNano()) } func generateAPIKey() string { // Generate a random API key return fmt.Sprintf("dfc_%s", generateRandomString(32)) } func generateRandomString(length int) string { const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" b := make([]byte, length) for i := range b { b[i] = charset[time.Now().UnixNano()%int64(len(charset))] } return string(b) } func isPublicEndpoint(path string) bool { publicPaths := []string{ "/api/v1/status", "/api/v1/health", "/api/v1/version", } for _, publicPath := range publicPaths { if path == publicPath { return true } } return false } // WebhookManager methods func (wm *WebhookManager) AddWebhook(webhook *Webhook) { wm.mu.Lock() defer wm.mu.Unlock() wm.webhooks[webhook.ID] = webhook } func (wm *WebhookManager) GetWebhook(id string) (*Webhook, bool) { wm.mu.RLock() defer wm.mu.RUnlock() webhook, exists := wm.webhooks[id] return webhook, exists } func (wm *WebhookManager) UpdateWebhook(webhook *Webhook) { wm.mu.Lock() defer wm.mu.Unlock() wm.webhooks[webhook.ID] = webhook } func (wm *WebhookManager) DeleteWebhook(id string) error { wm.mu.Lock() defer wm.mu.Unlock() if _, exists := wm.webhooks[id]; !exists { return fmt.Errorf("webhook not found") } delete(wm.webhooks, id) return nil } func (wm *WebhookManager) ListWebhooks() []*Webhook { wm.mu.RLock() defer wm.mu.RUnlock() webhooks := make([]*Webhook, 0, len(wm.webhooks)) for _, webhook := range wm.webhooks { webhooks = append(webhooks, webhook) } return webhooks } // IntegrationManager methods func (im *IntegrationManager) AddIntegration(integration *Integration) { im.mu.Lock() defer im.mu.Unlock() im.integrations[integration.ID] = integration } func (im *IntegrationManager) GetIntegration(id string) (*Integration, bool) { im.mu.RLock() defer im.mu.RUnlock() integration, exists := im.integrations[id] return integration, exists } func (im *IntegrationManager) UpdateIntegration(integration *Integration) { im.mu.Lock() defer im.mu.Unlock() im.integrations[integration.ID] = integration } func (im *IntegrationManager) DeleteIntegration(id string) error { im.mu.Lock() defer im.mu.Unlock() if _, exists := im.integrations[id]; !exists { return fmt.Errorf("integration not found") } delete(im.integrations, id) return nil } func (im *IntegrationManager) ListIntegrations() []*Integration { im.mu.RLock() defer im.mu.RUnlock() integrations := make([]*Integration, 0, len(im.integrations)) for _, integration := range im.integrations { integrations = append(integrations, integration) } return integrations } // RateLimiter methods func (rl *RateLimiter) AllowRequest(clientIP string) bool { rl.mu.Lock() defer rl.mu.Unlock() now := time.Now() limit, exists := rl.limits[clientIP] if !exists { limit = &RateLimit{ Key: clientIP, Requests: 0, Window: 1 * time.Minute, LastReset: now, } rl.limits[clientIP] = limit } // Reset counter if window has passed if now.Sub(limit.LastReset) > limit.Window { limit.Requests = 0 limit.LastReset = now } // Check if limit exceeded (100 requests per minute) if limit.Requests >= 100 { return false } limit.Requests++ return true } // AuthManager methods func (am *AuthManager) AddAPIKey(apiKey *APIKey) { am.mu.Lock() defer am.mu.Unlock() am.apiKeys[apiKey.ID] = apiKey } func (am *AuthManager) GetAPIKey(id string) (*APIKey, bool) { am.mu.RLock() defer am.mu.RUnlock() apiKey, exists := am.apiKeys[id] return apiKey, exists } func (am *AuthManager) UpdateAPIKey(apiKey *APIKey) { am.mu.Lock() defer am.mu.Unlock() am.apiKeys[apiKey.ID] = apiKey } func (am *AuthManager) DeleteAPIKey(id string) error { am.mu.Lock() defer am.mu.Unlock() if _, exists := am.apiKeys[id]; !exists { return fmt.Errorf("API key not found") } delete(am.apiKeys, id) return nil } func (am *AuthManager) ListAPIKeys() []*APIKey { am.mu.RLock() defer am.mu.RUnlock() apiKeys := make([]*APIKey, 0, len(am.apiKeys)) for _, apiKey := range am.apiKeys { // Don't expose actual keys apiKey.Key = "" apiKeys = append(apiKeys, apiKey) } return apiKeys } func (am *AuthManager) ValidateAPIKey(key string) bool { am.mu.RLock() defer am.mu.RUnlock() for _, apiKey := range am.apiKeys { if apiKey.Key == key { // Check if expired if apiKey.ExpiresAt != nil && time.Now().After(*apiKey.ExpiresAt) { return false } // Update last used now := time.Now() apiKey.LastUsed = &now return true } } return false }