package main import ( "database/sql" "encoding/json" "fmt" "io/ioutil" "log" "net/http" "os" "path/filepath" "sort" "strconv" "strings" "time" _ "github.com/go-sql-driver/mysql" ) // Config struct for database connection type Config struct { Username string `json:"username"` Password string `json:"password"` Hostname string `json:"hostname"` Port int `json:"port"` Database string `json:"database"` } // QueryRequest from the frontend type QueryRequest struct { ID string `json:"id"` Query string `json:"query"` } // QueryResult represents a single historical query execution type QueryResult struct { ID string `json:"id"` Query string `json:"query"` Timestamp int64 `json:"timestamp"` Error string `json:"error,omitempty"` Columns []string `json:"columns,omitempty"` Rows [][]interface{} `json:"rows,omitempty"` RowsAffected int64 `json:"rowsAffected,omitempty"` } var db *sql.DB var historyDir = "./history" func main() { // Load configuration file, err := os.Open("config.json") if err != nil { log.Fatalf("Failed to open config file: %v", err) } defer file.Close() var config Config decoder := json.NewDecoder(file) if err := decoder.Decode(&config); err != nil { log.Fatalf("Failed to decode config: %v", err) } // Connect to the database dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true", config.Username, config.Password, config.Hostname, config.Port, config.Database) db, err = sql.Open("mysql", dsn) if err != nil { log.Fatalf("Failed to open database connection: %v", err) } defer db.Close() if err := db.Ping(); err != nil { log.Fatalf("Failed to connect to the database: %v", err) } log.Println("Successfully connected to the database.") // Ensure history directory exists if _, err := os.Stat(historyDir); os.IsNotExist(err) { os.Mkdir(historyDir, 0755) } // HTTP Handlers http.HandleFunc("/", serveIndex) http.HandleFunc("/query", handleQuery) http.HandleFunc("/history", handleHistory) log.Println("Starting server on :8080...") if err := http.ListenAndServe(":8080", nil); err != nil { log.Fatalf("Server failed: %v", err) } } func serveIndex(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "index.html") } func handleHistory(w http.ResponseWriter, r *http.Request) { files, err := ioutil.ReadDir(historyDir) if err != nil { http.Error(w, "Could not read history", http.StatusInternalServerError) return } var history []QueryResult for _, file := range files { if filepath.Ext(file.Name()) == ".json" { data, err := ioutil.ReadFile(filepath.Join(historyDir, file.Name())) if err != nil { continue } var qr QueryResult if err := json.Unmarshal(data, &qr); err == nil { history = append(history, qr) } } } // Sort by timestamp, oldest first sort.Slice(history, func(i, j int) bool { return history[i].Timestamp < history[j].Timestamp }) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(history) } func handleQuery(w http.ResponseWriter, r *http.Request) { var req QueryRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } query := strings.TrimSpace(req.Query) isSelect := strings.HasPrefix(strings.ToLower(query), "select") || strings.HasPrefix(strings.ToLower(query), "show") || strings.HasPrefix(strings.ToLower(query), "describe") result := QueryResult{ ID: req.ID, Query: req.Query, Timestamp: time.Now().UnixNano(), } // Generate new ID if it's a new query if result.ID == "" { result.ID = strconv.FormatInt(result.Timestamp, 10) } if isSelect { // It's a query that returns rows rows, err := db.Query(query) if err != nil { result.Error = err.Error() } else { defer rows.Close() cols, _ := rows.Columns() result.Columns = cols for rows.Next() { // Create a slice of interface{}'s to represent a row columns := make([]interface{}, len(cols)) columnPointers := make([]interface{}, len(cols)) for i := range columns { columnPointers[i] = &columns[i] } // Scan the result into the column pointers... if err := rows.Scan(columnPointers...); err != nil { result.Error = err.Error() break } // Convert byte slices to strings for better JSON representation for i, col := range columns { if b, ok := col.([]byte); ok { columns[i] = string(b) } } result.Rows = append(result.Rows, columns) } if err := rows.Err(); err != nil { result.Error = err.Error() } } } else { // It's a statement like INSERT, UPDATE, DELETE res, err := db.Exec(query) if err != nil { result.Error = err.Error() } else { affected, err := res.RowsAffected() if err != nil { result.Error = err.Error() } else { result.RowsAffected = affected } } } // Save to history filePath := filepath.Join(historyDir, result.ID+".json") fileData, _ := json.MarshalIndent(result, "", " ") if err := ioutil.WriteFile(filePath, fileData, 0644); err != nil { log.Printf("Failed to write history file %s: %v", filePath, err) // Don't send a server error back to client, they can still see the result } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(result) }