-
Notifications
You must be signed in to change notification settings - Fork 21
CLI Commands for State Management (#573) #577
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
anjor
wants to merge
21
commits into
data-preservation-programs:develop
Choose a base branch
from
anjor:feature/cli-state-management
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
870164c
feat: Comprehensive Deal Tracker Integration for issue #572
anjor c5fe0bc
feat: CLI Commands for State Management (#573)
anjor 10ec307
Merge branch 'develop' into feature/cli-state-management
anjor f8d9c4b
gofmt
anjor 6015eb4
lint
anjor 4cc1122
lint
anjor 9841000
Merge branch 'develop' into feature/cli-state-management
anjor f52c117
fix
anjor 313e8c1
Merge develop branch and resolve conflicts
anjor 70d1be5
fix
anjor 3f49c01
Merge branch 'develop' into feature/cli-state-management
anjor 12101cd
Merge remote-tracking branch 'origin/develop' into feature/cli-state-…
anjor cdfbe1c
Merge branch 'feature/cli-state-management' of github.com:anjor/singu…
anjor 7a82581
fix: restore PieceCID to ProposalID mapping logic lost during merge
anjor 31108af
fixes
anjor 38029f2
fix
anjor caa50d5
fixes
anjor 5a7894c
tests
anjor 192205b
fix test
anjor 5ddbee1
fix
anjor 3a51458
PR feedback
anjor File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package state | ||
|
||
import ( | ||
"encoding/csv" | ||
"encoding/json" | ||
"os" | ||
"path/filepath" | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
"github.com/cockroachdb/errors" | ||
"github.com/data-preservation-programs/singularity/model" | ||
) | ||
|
||
// exportStateChanges exports state changes to the specified format and file path | ||
func exportStateChanges(stateChanges []model.DealStateChange, format, outputPath string) error { | ||
// Validate and clean the output path to prevent directory traversal | ||
cleanPath := filepath.Clean(outputPath) | ||
if filepath.IsAbs(cleanPath) { | ||
return errors.New("absolute paths are not allowed for security reasons") | ||
} | ||
// Check for directory traversal attempts | ||
if strings.Contains(cleanPath, "..") { | ||
return errors.New("directory traversal using '..' is not allowed") | ||
} | ||
if len(cleanPath) > 255 { | ||
return errors.New("output path is too long") | ||
} | ||
|
||
switch format { | ||
case "csv": | ||
return exportToCSV(stateChanges, cleanPath) | ||
case "json": | ||
return exportToJSON(stateChanges, cleanPath) | ||
default: | ||
return errors.Errorf("unsupported export format: %s", format) | ||
} | ||
} | ||
|
||
// exportToCSV exports state changes to a CSV file | ||
func exportToCSV(stateChanges []model.DealStateChange, outputPath string) (err error) { | ||
file, err := os.Create(outputPath) // #nosec G304 -- path is validated in exportStateChanges | ||
if err != nil { | ||
return errors.Wrap(err, "failed to create CSV file") | ||
} | ||
defer func() { | ||
if closeErr := file.Close(); closeErr != nil && err == nil { | ||
err = errors.Wrap(closeErr, "failed to close CSV file") | ||
} | ||
}() | ||
|
||
writer := csv.NewWriter(file) | ||
defer writer.Flush() | ||
|
||
// Write CSV header | ||
// Note: Both ID and DealID are database IDs, not CIDs | ||
// ID = state change record database ID, DealID = internal singularity deal database ID | ||
header := []string{ | ||
"StateChangeID", // Database ID of the state change record | ||
"DealID", // Internal singularity deal database ID | ||
"PreviousState", | ||
"NewState", | ||
"Timestamp", | ||
"EpochHeight", | ||
"SectorID", | ||
"ProviderID", | ||
"ClientAddress", | ||
"Metadata", | ||
} | ||
if err := writer.Write(header); err != nil { | ||
return errors.Wrap(err, "failed to write CSV header") | ||
} | ||
|
||
// Write state change records | ||
for _, change := range stateChanges { | ||
record := []string{ | ||
strconv.FormatUint(change.ID, 10), | ||
strconv.FormatUint(uint64(change.DealID), 10), | ||
string(change.PreviousState), | ||
string(change.NewState), | ||
change.Timestamp.Format("2006-01-02 15:04:05"), | ||
formatOptionalInt32(change.EpochHeight), | ||
formatOptionalString(change.SectorID), | ||
change.ProviderID, | ||
change.ClientAddress, | ||
change.Metadata, | ||
} | ||
if err := writer.Write(record); err != nil { | ||
return errors.Wrap(err, "failed to write CSV record") | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// exportToJSON exports state changes to a JSON file | ||
func exportToJSON(stateChanges []model.DealStateChange, outputPath string) (err error) { | ||
file, err := os.Create(outputPath) // #nosec G304 -- path is validated in exportStateChanges | ||
if err != nil { | ||
return errors.Wrap(err, "failed to create JSON file") | ||
} | ||
defer func() { | ||
if closeErr := file.Close(); closeErr != nil && err == nil { | ||
err = errors.Wrap(closeErr, "failed to close JSON file") | ||
} | ||
}() | ||
|
||
// Create export structure with metadata | ||
exportData := struct { | ||
Metadata struct { | ||
ExportTime string `json:"exportTime"` | ||
TotalCount int `json:"totalCount"` | ||
} `json:"metadata"` | ||
StateChanges []model.DealStateChange `json:"stateChanges"` | ||
}{ | ||
StateChanges: stateChanges, | ||
} | ||
|
||
exportData.Metadata.ExportTime = time.Now().Format(time.RFC3339) | ||
exportData.Metadata.TotalCount = len(stateChanges) | ||
|
||
encoder := json.NewEncoder(file) | ||
encoder.SetIndent("", " ") | ||
if err := encoder.Encode(exportData); err != nil { | ||
return errors.Wrap(err, "failed to encode JSON") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Helper functions for formatting optional fields | ||
func formatOptionalInt32(value *int32) string { | ||
if value == nil { | ||
return "" | ||
} | ||
return strconv.FormatInt(int64(*value), 10) | ||
} | ||
|
||
func formatOptionalString(value *string) string { | ||
if value == nil { | ||
return "" | ||
} | ||
return *value | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.