Skip to content

Commit

Permalink
feat: Add simple data import and export (#1630)
Browse files Browse the repository at this point in the history
## Relevant issue(s)

Resolves #1544

## Description

This PR adds import and export functionality to the http api and cli. It
can export to json to reduce the potential file size. At this stage csv
output was not implemented as it would require extensive type casting
(everything in csv is a string) on both writing and reading from the
csv.
  • Loading branch information
fredcarle committed Jul 22, 2023
1 parent 6d896ba commit bc8ada9
Show file tree
Hide file tree
Showing 51 changed files with 4,471 additions and 144 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ Read the documentation on [docs.source.network](https://docs.source.network/).
- [Collection subscription example](#collection-subscription-example)
- [Replicator example](#replicator-example)
- [Securing the HTTP API with TLS](#securing-the-http-api-with-tls)
- [Supporting CORS](#supporting-cors)
- [Backing up and restoring](#backing-up-and-restoring)
- [Licensing](#licensing)
- [Contributors](#contributors)

Expand Down Expand Up @@ -387,6 +389,25 @@ defradb start --allowed-origins=http://localhost:3000

The catch-all `*` is also a valid origin.

## Backing up and restoring

It is currently not possible to do a full backup of DefraDB that includes the history of changes through the Merkle DAG. However, DefraDB currently supports a simple backup of the current data state in JSON format that can be used to seed a database or help with transitioning from one DefraDB version to another.

To backup the data, run the following command:
```shell
defradb client backup export path/to/backup.json
```

To pretty print the JSON content when exporting, run the following command:
```shell
defradb client backup export --pretty path/to/backup.json
```

To restore the data, run the following command:
```shell
defradb client backup import path/to/backup.json
```

## Community

Discuss on [Discord](https://discord.source.network/) or [Github Discussions](https://github.com/sourcenetwork/defradb/discussions). The Source project is on [Twitter](https://twitter.com/sourcenetwrk).
Expand Down
2 changes: 2 additions & 0 deletions api/http/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ var (
ErrPeerIdUnavailable = errors.New("no PeerID available. P2P might be disabled")
ErrStreamingUnsupported = errors.New("streaming unsupported")
ErrNoEmail = errors.New("email address must be specified for tls with autocert")
ErrPayloadFormat = errors.New("invalid payload format")
ErrMissingNewKey = errors.New("missing _newKey for imported doc")
)

// ErrorResponse is the GQL top level object holding error items for the response payload.
Expand Down
2 changes: 1 addition & 1 deletion api/http/handlerfuncs.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func pingHandler(rw http.ResponseWriter, req *http.Request) {
sendJSON(
req.Context(),
rw,
simpleDataResponse("response", "pong", "test"),
simpleDataResponse("response", "pong"),
http.StatusOK,
)
}
Expand Down
123 changes: 123 additions & 0 deletions api/http/handlerfuncs_backup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2023 Democratized Data Foundation
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package http

import (
"context"
"net/http"
"os"
"strings"

"github.com/sourcenetwork/defradb/client"
"github.com/sourcenetwork/defradb/errors"
)

func exportHandler(rw http.ResponseWriter, req *http.Request) {
db, err := dbFromContext(req.Context())
if err != nil {
handleErr(req.Context(), rw, err, http.StatusInternalServerError)
return
}

cfg := &client.BackupConfig{}
err = getJSON(req, cfg)
if err != nil {
handleErr(req.Context(), rw, err, http.StatusBadRequest)
return
}

err = validateBackupConfig(req.Context(), cfg, db)
if err != nil {
handleErr(req.Context(), rw, err, http.StatusBadRequest)
return
}

err = db.BasicExport(req.Context(), cfg)
if err != nil {
handleErr(req.Context(), rw, err, http.StatusInternalServerError)
return
}

sendJSON(
req.Context(),
rw,
simpleDataResponse("result", "success"),
http.StatusOK,
)
}

func importHandler(rw http.ResponseWriter, req *http.Request) {
db, err := dbFromContext(req.Context())
if err != nil {
handleErr(req.Context(), rw, err, http.StatusInternalServerError)
return
}

cfg := &client.BackupConfig{}
err = getJSON(req, cfg)
if err != nil {
handleErr(req.Context(), rw, err, http.StatusBadRequest)
return
}

err = validateBackupConfig(req.Context(), cfg, db)
if err != nil {
handleErr(req.Context(), rw, err, http.StatusBadRequest)
return
}

err = db.BasicImport(req.Context(), cfg.Filepath)
if err != nil {
handleErr(req.Context(), rw, err, http.StatusInternalServerError)
return
}

sendJSON(
req.Context(),
rw,
simpleDataResponse("result", "success"),
http.StatusOK,
)
}

func validateBackupConfig(ctx context.Context, cfg *client.BackupConfig, db client.DB) error {
if !isValidPath(cfg.Filepath) {
return errors.New("invalid file path")
}

if cfg.Format != "" && strings.ToLower(cfg.Format) != "json" {
return errors.New("only JSON format is supported at the moment")
}
for _, colName := range cfg.Collections {
_, err := db.GetCollectionByName(ctx, colName)
if err != nil {
return errors.Wrap("collection does not exist", err)
}
}
return nil
}

func isValidPath(filepath string) bool {
// if a file exists, return true
if _, err := os.Stat(filepath); err == nil {
return true
}

// if not, attempt to write to the path and if successful,
// remove the file and return true
var d []byte
if err := os.WriteFile(filepath, d, 0o644); err == nil {
_ = os.Remove(filepath)
return true
}

return false
}
Loading

0 comments on commit bc8ada9

Please sign in to comment.