package main import ( "encoding/json" "fmt" "log" "net/http" "os" "sync" "time" "envguard/internal/models" ) var ( services = []models.Service{} history = []models.HistoryEntry{} mu sync.Mutex dataFile = "services.json" historyFile = "history.json" apiToken = "ENVGUARD_SECRET_TOKEN" ) func main() { if err := loadServices(); err != nil { log.Printf("⚠️ No se pudo cargar services.json, usando defaults: %v", err) services = []models.Service{ {Name: "auth-service"}, {Name: "payments-api"}, {Name: "user-db"}, {Name: "notifications"}, {Name: "front-web"}, } } else { fmt.Printf("✅ %d servicios cargados desde disco\n", len(services)) } if err := loadHistory(); err != nil { log.Printf("⚠️ No se pudo cargar history.json: %v", err) } else { fmt.Printf("📜 %d entradas de historial cargadas\n", len(history)) } http.HandleFunc("/services", authMiddleware(handleServices)) http.HandleFunc("/history", authMiddleware(handleHistory)) http.HandleFunc("/lock", authMiddleware(handleLock)) http.HandleFunc("/unlock", authMiddleware(handleUnlock)) fmt.Println("🚦 Lock Server corriendo en :8080") if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatal(err) } } func loadServices() error { file, err := os.Open(dataFile) if err != nil { return err } defer file.Close() return json.NewDecoder(file).Decode(&services) } func saveServices() error { file, err := os.Create(dataFile) if err != nil { return err } defer file.Close() encoder := json.NewEncoder(file) encoder.SetIndent("", " ") return encoder.Encode(services) } func loadHistory() error { file, err := os.Open(historyFile) if err != nil { if os.IsNotExist(err) { return nil } return err } defer file.Close() return json.NewDecoder(file).Decode(&history) } func saveHistory() error { file, err := os.Create(historyFile) if err != nil { return err } defer file.Close() encoder := json.NewEncoder(file) encoder.SetIndent("", " ") return encoder.Encode(history) } func handleServices(w http.ResponseWriter, r *http.Request) { mu.Lock() defer mu.Unlock() w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(services) } func handleLock(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req models.LockRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } mu.Lock() defer mu.Unlock() for i := range services { if services[i].Name == req.ServiceName { if services[i].IsLocked { http.Error(w, "Service already locked", http.StatusConflict) return } services[i].IsLocked = true services[i].LockedBy = req.User services[i].LockedAt = time.Now() services[i].JiraTickets = req.JiraTickets services[i].Description = req.Description if err := saveServices(); err != nil { log.Printf("Error guardando estado: %v", err) } // Add to history history = append(history, models.HistoryEntry{ ServiceName: req.ServiceName, Action: "LOCK", User: req.User, Timestamp: time.Now(), JiraTickets: req.JiraTickets, Description: req.Description, }) saveHistory() w.WriteHeader(http.StatusOK) return } } http.Error(w, "Service not found", http.StatusNotFound) } func handleUnlock(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req models.UnlockRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } mu.Lock() defer mu.Unlock() for i := range services { if services[i].Name == req.ServiceName { if !services[i].IsLocked { http.Error(w, "Service is not locked", http.StatusBadRequest) return } if services[i].LockedBy != req.User { http.Error(w, "You cannot unlock a service locked by someone else", http.StatusForbidden) return } services[i].IsLocked = false services[i].LockedBy = "" services[i].LockedAt = time.Time{} services[i].JiraTickets = "" services[i].Description = "" if err := saveServices(); err != nil { log.Printf("Error guardando estado: %v", err) } // Add to history history = append(history, models.HistoryEntry{ ServiceName: req.ServiceName, Action: "UNLOCK", User: req.User, Timestamp: time.Now(), }) saveHistory() w.WriteHeader(http.StatusOK) return } } http.Error(w, "Service not found", http.StatusNotFound) } func handleHistory(w http.ResponseWriter, r *http.Request) { serviceName := r.URL.Query().Get("service") mu.Lock() defer mu.Unlock() var result []models.HistoryEntry for _, h := range history { if serviceName == "" || h.ServiceName == serviceName { result = append(result, h) } } // Reverse order (newest first) for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 { result[i], result[j] = result[j], result[i] } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(result) } func authMiddleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("X-API-Key") if token != apiToken { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } next(w, r) } }