package main import ( "database/sql" "encoding/json" "fmt" "log" "math/rand" "net/http" "os" "os/exec" "strconv" "strings" "time" _ "modernc.org/sqlite" ) type Item struct { ID int `json:"id"` Name string `json:"name"` } type UserInfo struct { Username string `json:"username"` Name string `json:"name"` Email string `json:"email"` Groups string `json:"groups"` Headers map[string]string `json:"headers"` } type MiningRecord struct { ID int `json:"id"` Timestamp time.Time `json:"timestamp"` Value float64 `json:"value"` Rate float64 `json:"rate"` } type MiningData struct { CurrentValue float64 `json:"current_value"` Rate float64 `json:"rate"` History []MiningRecord `json:"history"` } var db *sql.DB func initDB() { var err error db, err = sql.Open("sqlite", "items.db") if err != nil { log.Fatal("Failed to open database:", err) } createItemsTable := ` CREATE TABLE IF NOT EXISTS items ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL );` _, err = db.Exec(createItemsTable) if err != nil { log.Fatal("Failed to create items table:", err) } createMiningTable := ` CREATE TABLE IF NOT EXISTS mining ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME NOT NULL, value REAL NOT NULL, rate REAL NOT NULL );` _, err = db.Exec(createMiningTable) if err != nil { log.Fatal("Failed to create mining table:", err) } // Insert initial mining record if table is empty var count int err = db.QueryRow("SELECT COUNT(*) FROM mining").Scan(&count) if err != nil { log.Fatal("Failed to check mining table:", err) } if count == 0 { _, err = db.Exec("INSERT INTO mining (timestamp, value, rate) VALUES (?, ?, ?)", time.Now(), 100.0, 2.5) if err != nil { log.Fatal("Failed to insert initial mining record:", err) } } } func main() { if len(os.Args) > 1 && os.Args[1] == "--watch" { watchAndRestart() return } // Initialize database initDB() defer db.Close() // Start mining ticker go startMiningTicker() // Your actual API rng := rand.New(rand.NewSource(time.Now().UnixNano())) http.HandleFunc("/randomnumber", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "%d", rng.Intn(1000)) }) http.HandleFunc("/slowtest", func(w http.ResponseWriter, r *http.Request) { time.Sleep(1 * time.Second) fmt.Fprintf(w, "true") }) http.HandleFunc("/welcome", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "welcome to shinyspace play api testing") }) http.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) { if r.Method == "POST" { r.ParseMultipartForm(32 << 20) // 32MB max memory text := r.FormValue("text") fmt.Fprintf(w, "you said %s", text) } else { w.WriteHeader(http.StatusMethodNotAllowed) fmt.Fprintf(w, "Only POST method allowed") } }) // Items API endpoints http.HandleFunc("/items", handleItems) http.HandleFunc("/items/", handleItemDelete) // Mining endpoints http.HandleFunc("/mining", handleMining) // Auth endpoints http.HandleFunc("/auth/user", handleAuthUser) log.Println("API running on :3847") http.ListenAndServe("127.0.0.1:3847", nil) } func watchAndRestart() { binaryPath, _ := os.Executable() var cmd *exec.Cmd start := func() { if cmd != nil && cmd.Process != nil { cmd.Process.Kill() } cmd = exec.Command(binaryPath) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Start() } start() // Initial start lastMod := getModTime(binaryPath) for { time.Sleep(1 * time.Second) if mod := getModTime(binaryPath); mod.After(lastMod) { log.Println("Binary changed, restarting...") lastMod = mod start() } } } func getModTime(path string) time.Time { info, _ := os.Stat(path) return info.ModTime() } func handleItems(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") switch r.Method { case "GET": getItems(w, r) case "POST": createItem(w, r) default: w.WriteHeader(http.StatusMethodNotAllowed) json.NewEncoder(w).Encode(map[string]string{"error": "Method not allowed"}) } } func getItems(w http.ResponseWriter, r *http.Request) { rows, err := db.Query("SELECT id, name FROM items ORDER BY id") if err != nil { w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]string{"error": "Failed to fetch items"}) return } defer rows.Close() var items []Item for rows.Next() { var item Item err := rows.Scan(&item.ID, &item.Name) if err != nil { w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]string{"error": "Failed to scan items"}) return } items = append(items, item) } // Ensure we always return an array, even if empty if items == nil { items = []Item{} } json.NewEncoder(w).Encode(items) } func createItem(w http.ResponseWriter, r *http.Request) { var item Item err := json.NewDecoder(r.Body).Decode(&item) if err != nil { w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(map[string]string{"error": "Invalid JSON"}) return } if item.Name == "" { w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(map[string]string{"error": "Name is required"}) return } result, err := db.Exec("INSERT INTO items (name) VALUES (?)", item.Name) if err != nil { w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]string{"error": "Failed to create item"}) return } id, _ := result.LastInsertId() item.ID = int(id) w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(item) } func handleItemDelete(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if r.Method != "DELETE" { w.WriteHeader(http.StatusMethodNotAllowed) json.NewEncoder(w).Encode(map[string]string{"error": "Method not allowed"}) return } // Extract ID from URL path /items/{id} path := strings.TrimPrefix(r.URL.Path, "/items/") id, err := strconv.Atoi(path) if err != nil { w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(map[string]string{"error": "Invalid item ID"}) return } result, err := db.Exec("DELETE FROM items WHERE id = ?", id) if err != nil { w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]string{"error": "Failed to delete item"}) return } rowsAffected, _ := result.RowsAffected() if rowsAffected == 0 { w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]string{"error": "Item not found"}) return } json.NewEncoder(w).Encode(map[string]string{"message": "Item deleted successfully"}) } func handleAuthUser(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if r.Method != "GET" { w.WriteHeader(http.StatusMethodNotAllowed) json.NewEncoder(w).Encode(map[string]string{"error": "Method not allowed"}) return } // Collect all headers for debugging headers := make(map[string]string) for name, values := range r.Header { headers[name] = strings.Join(values, ", ") } // Extract common Authelia headers userInfo := UserInfo{ Username: r.Header.Get("Remote-User"), Name: r.Header.Get("Remote-Name"), Email: r.Header.Get("Remote-Email"), Groups: r.Header.Get("Remote-Groups"), Headers: headers, } json.NewEncoder(w).Encode(userInfo) } func handleMining(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if r.Method != "GET" { w.WriteHeader(http.StatusMethodNotAllowed) json.NewEncoder(w).Encode(map[string]string{"error": "Method not allowed"}) return } // Get the latest record to calculate current value var latest MiningRecord err := db.QueryRow(` SELECT id, timestamp, value, rate FROM mining ORDER BY timestamp DESC LIMIT 1 `).Scan(&latest.ID, &latest.Timestamp, &latest.Value, &latest.Rate) if err != nil { w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]string{"error": "Failed to get latest mining data"}) return } // Calculate current value based on time elapsed and rate now := time.Now() minutesElapsed := now.Sub(latest.Timestamp).Minutes() currentValue := latest.Value + (latest.Rate * minutesElapsed) // Get recent history (last 5 records) rows, err := db.Query(` SELECT id, timestamp, value, rate FROM mining ORDER BY timestamp DESC LIMIT 5 `) if err != nil { w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]string{"error": "Failed to get mining history"}) return } defer rows.Close() var history []MiningRecord for rows.Next() { var record MiningRecord err := rows.Scan(&record.ID, &record.Timestamp, &record.Value, &record.Rate) if err != nil { w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]string{"error": "Failed to scan mining record"}) return } history = append(history, record) } response := MiningData{ CurrentValue: currentValue, Rate: latest.Rate, History: history, } json.NewEncoder(w).Encode(response) } func startMiningTicker() { ticker := time.NewTicker(1 * time.Minute) defer ticker.Stop() log.Println("Mining ticker started - updating every minute") for { select { case <-ticker.C: updateMiningValue() } } } func updateMiningValue() { // Get the latest record var latest MiningRecord err := db.QueryRow(` SELECT id, timestamp, value, rate FROM mining ORDER BY timestamp DESC LIMIT 1 `).Scan(&latest.ID, &latest.Timestamp, &latest.Value, &latest.Rate) if err != nil { log.Printf("Failed to get latest mining record: %v", err) return } // Calculate new value based on time elapsed and rate now := time.Now() minutesElapsed := now.Sub(latest.Timestamp).Minutes() newValue := latest.Value + (latest.Rate * minutesElapsed) // Insert new record _, err = db.Exec(` INSERT INTO mining (timestamp, value, rate) VALUES (?, ?, ?) `, now, newValue, latest.Rate) if err != nil { log.Printf("Failed to insert new mining record: %v", err) return } log.Printf("Mining updated: %.2f -> %.2f (rate: %.2f/min, elapsed: %.2f min)", latest.Value, newValue, latest.Rate, minutesElapsed) }