feat: add export and backup commands
- nib export: dump all entities to JSON (stdout or --output file) - nib backup: atomic SQLite backup via VACUUM INTO (WAL-safe) - Store.Backup() method on db layer - Tests for both commands
This commit is contained in:
+113
@@ -0,0 +1,113 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/lerko/nib/internal/db"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var exportOutput string
|
||||
|
||||
var exportCmd = &cobra.Command{
|
||||
Use: "export",
|
||||
Short: "dump all entities to JSON",
|
||||
RunE: runExport,
|
||||
}
|
||||
|
||||
func init() {
|
||||
exportCmd.Flags().StringVarP(&exportOutput, "output", "o", "", "write to file instead of stdout")
|
||||
rootCmd.AddCommand(exportCmd)
|
||||
}
|
||||
|
||||
type exportEntity struct {
|
||||
ID string `json:"id"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
ModifiedAt string `json:"modified_at"`
|
||||
Body string `json:"body"`
|
||||
Title *string `json:"title,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Glyph string `json:"glyph"`
|
||||
TimeAnchor *string `json:"time_anchor,omitempty"`
|
||||
CompletedAt *string `json:"completed_at,omitempty"`
|
||||
Pinned bool `json:"pinned"`
|
||||
DeletedAt *string `json:"deleted_at,omitempty"`
|
||||
Tags []string `json:"tags"`
|
||||
CardType *string `json:"card_type,omitempty"`
|
||||
CardData *string `json:"card_data,omitempty"`
|
||||
UseCount int `json:"use_count"`
|
||||
LastUsedAt *string `json:"last_used_at,omitempty"`
|
||||
}
|
||||
|
||||
func runExport(cmd *cobra.Command, _ []string) error {
|
||||
store, err := openStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
ctx := cmd.Context()
|
||||
|
||||
p := db.DefaultListParams()
|
||||
p.IncludeDeleted = true
|
||||
p.Limit = 10000
|
||||
|
||||
entities, err := store.List(ctx, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out := make([]exportEntity, len(entities))
|
||||
for i, e := range entities {
|
||||
out[i] = exportEntity{
|
||||
ID: e.ID,
|
||||
CreatedAt: e.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
ModifiedAt: e.ModifiedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
Body: e.Body,
|
||||
Title: e.Title,
|
||||
Glyph: string(e.Glyph),
|
||||
TimeAnchor: e.TimeAnchor,
|
||||
Pinned: e.Pinned,
|
||||
Tags: e.Tags,
|
||||
CardData: e.CardData,
|
||||
UseCount: e.UseCount,
|
||||
}
|
||||
if e.Description != nil {
|
||||
out[i].Description = e.Description
|
||||
}
|
||||
if e.CompletedAt != nil {
|
||||
s := e.CompletedAt.Format("2006-01-02T15:04:05Z07:00")
|
||||
out[i].CompletedAt = &s
|
||||
}
|
||||
if e.DeletedAt != nil {
|
||||
s := e.DeletedAt.Format("2006-01-02T15:04:05Z07:00")
|
||||
out[i].DeletedAt = &s
|
||||
}
|
||||
if e.CardType != nil {
|
||||
s := string(*e.CardType)
|
||||
out[i].CardType = &s
|
||||
}
|
||||
if e.LastUsedAt != nil {
|
||||
s := e.LastUsedAt.Format("2006-01-02T15:04:05Z07:00")
|
||||
out[i].LastUsedAt = &s
|
||||
}
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(out, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if exportOutput != "" {
|
||||
if err := os.WriteFile(exportOutput, data, 0o600); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(cmd.ErrOrStderr(), "exported %d entities to %s\n", len(out), exportOutput)
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println(string(data))
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user