package main import ( "encoding/json" "fmt" "os" "strings" ) // ExportEntry represents a single exported key-value pair type ExportEntry struct { Key string `json:"key"` UUID string `json:"uuid"` Timestamp int64 `json:"timestamp"` Data json.RawMessage `json:"data"` } // ExportFile represents the export file format type ExportFile struct { Version string `json:"version"` Entries []ExportEntry `json:"entries"` } // handleExport exports keys to a JSON file func (c *KVSClient) handleExport(args []string) { if len(args) < 2 { fmt.Println(red("Usage: export [key2] [key3] ...")) fmt.Println("Example: export backup.json users/alice users/bob config/settings") return } outputFile := args[0] keys := args[1:] exportFile := ExportFile{ Version: "1.0", Entries: []ExportEntry{}, } fmt.Println(cyan("Exporting"), len(keys), "key(s)...") successCount := 0 failCount := 0 for _, key := range keys { respBody, status, err := c.doRequest("GET", "/kv/"+key, nil) if err != nil { fmt.Printf(red("✗")+" %s: %v\n", key, err) failCount++ continue } if status == 404 { fmt.Printf(yellow("⊘")+" %s: not found\n", key) failCount++ continue } if status != 200 { fmt.Printf(red("✗")+" %s: status %d\n", key, status) failCount++ continue } var stored StoredValue if err := json.Unmarshal(respBody, &stored); err != nil { fmt.Printf(red("✗")+" %s: parse error: %v\n", key, err) failCount++ continue } exportFile.Entries = append(exportFile.Entries, ExportEntry{ Key: key, UUID: stored.UUID, Timestamp: stored.Timestamp, Data: stored.Data, }) fmt.Printf(green("✓")+" %s\n", key) successCount++ } // Write to file data, err := json.MarshalIndent(exportFile, "", " ") if err != nil { fmt.Println(red("Error marshaling export data:"), err) return } if err := os.WriteFile(outputFile, data, 0644); err != nil { fmt.Println(red("Error writing file:"), err) return } fmt.Println() fmt.Println(green("Export complete:")) fmt.Printf(" Exported: %d\n", successCount) if failCount > 0 { fmt.Printf(" Failed: %s\n", red(fmt.Sprintf("%d", failCount))) } fmt.Printf(" File: %s\n", outputFile) } // handleImport imports keys from a JSON file func (c *KVSClient) handleImport(args []string) { if len(args) < 1 { fmt.Println(red("Usage: import [--skip-existing | --overwrite]")) fmt.Println(" --skip-existing: Skip keys that already exist (default)") fmt.Println(" --overwrite: Overwrite existing keys") return } inputFile := args[0] overwrite := false // Parse flags for _, arg := range args[1:] { if arg == "--overwrite" { overwrite = true } } // Read file data, err := os.ReadFile(inputFile) if err != nil { fmt.Println(red("Error reading file:"), err) return } var exportFile ExportFile if err := json.Unmarshal(data, &exportFile); err != nil { fmt.Println(red("Error parsing export file:"), err) return } fmt.Println(cyan("Importing"), len(exportFile.Entries), "key(s)...") if exportFile.Version != "1.0" { fmt.Println(yellow("Warning: Unknown export version"), exportFile.Version) } successCount := 0 skipCount := 0 failCount := 0 for _, entry := range exportFile.Entries { // Check if key exists (unless we're overwriting) if !overwrite { _, status, err := c.doRequest("GET", "/kv/"+entry.Key, nil) if err == nil && status == 200 { fmt.Printf(yellow("⊘")+" %s: exists (skipped)\n", entry.Key) skipCount++ continue } } // Import the key respBody, status, err := c.doRequest("PUT", "/kv/"+entry.Key, entry.Data) if err != nil { fmt.Printf(red("✗")+" %s: %v\n", entry.Key, err) failCount++ continue } if status != 200 && status != 201 { fmt.Printf(red("✗")+" %s: status %d: %s\n", entry.Key, status, string(respBody)) failCount++ continue } fmt.Printf(green("✓")+" %s\n", entry.Key) successCount++ } fmt.Println() fmt.Println(green("Import complete:")) fmt.Printf(" Imported: %d\n", successCount) if skipCount > 0 { fmt.Printf(" Skipped: %d\n", skipCount) } if failCount > 0 { fmt.Printf(" Failed: %s\n", red(fmt.Sprintf("%d", failCount))) } } // handleExportList reads keys from a file and exports them func (c *KVSClient) handleExportList(args []string) { if len(args) < 2 { fmt.Println(red("Usage: export-list ")) fmt.Println("Example: export-list keys.txt backup.json") fmt.Println() fmt.Println("key-list-file should contain one key per line") return } keyListFile := args[0] outputFile := args[1] // Read keys from file data, err := os.ReadFile(keyListFile) if err != nil { fmt.Println(red("Error reading key list file:"), err) return } lines := strings.Split(string(data), "\n") keys := []string{} for _, line := range lines { line = strings.TrimSpace(line) if line != "" && !strings.HasPrefix(line, "#") { keys = append(keys, line) } } if len(keys) == 0 { fmt.Println(yellow("No keys found in file")) return } // Call export with the keys exportArgs := append([]string{outputFile}, keys...) c.handleExport(exportArgs) }