131 lines
3.2 KiB
Go
131 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"strings"
|
|
|
|
"fiatjaf.com/nostr"
|
|
"fiatjaf.com/nostr/eventstore"
|
|
"fiatjaf.com/nostr/eventstore/badger"
|
|
"fiatjaf.com/nostr/eventstore/lmdb"
|
|
"fiatjaf.com/nostr/eventstore/slicestore"
|
|
"github.com/urfave/cli/v3"
|
|
)
|
|
|
|
var db eventstore.Store
|
|
|
|
var app = &cli.Command{
|
|
Name: "eventstore",
|
|
Usage: "a CLI for all the eventstore backends",
|
|
UsageText: "eventstore -d ./data/sqlite <query|save|delete> ...",
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "store",
|
|
Aliases: []string{"d"},
|
|
Usage: "path to the database file or directory or database connection uri",
|
|
Required: true,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "type",
|
|
Aliases: []string{"t"},
|
|
Usage: "store type ('lmdb', 'badger', 'mmm')",
|
|
},
|
|
},
|
|
Before: func(ctx context.Context, c *cli.Command) (context.Context, error) {
|
|
path := strings.Trim(c.String("store"), "/")
|
|
typ := c.String("type")
|
|
if typ != "" {
|
|
// bypass automatic detection
|
|
// this also works for creating disk databases from scratch
|
|
} else {
|
|
// try to detect based on url scheme
|
|
switch {
|
|
case strings.HasPrefix(path, "postgres://"), strings.HasPrefix(path, "postgresql://"):
|
|
typ = "postgres"
|
|
case strings.HasPrefix(path, "mysql://"):
|
|
typ = "mysql"
|
|
case strings.HasPrefix(path, "https://"):
|
|
// if we ever add something else that uses URLs we'll have to modify this
|
|
typ = "elasticsearch"
|
|
case strings.HasSuffix(path, ".jsonl"):
|
|
typ = "file"
|
|
default:
|
|
// try to detect based on the form and names of disk files
|
|
dbname, err := detect(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return ctx, fmt.Errorf(
|
|
"'%s' does not exist, to create a store there specify the --type argument", path)
|
|
}
|
|
return ctx, fmt.Errorf("failed to detect store type: %w", err)
|
|
}
|
|
typ = dbname
|
|
}
|
|
}
|
|
|
|
switch typ {
|
|
case "lmdb":
|
|
db = &lmdb.LMDBBackend{Path: path}
|
|
case "badger":
|
|
db = &badger.BadgerBackend{Path: path}
|
|
case "mmm":
|
|
var err error
|
|
if db, err = doMmmInit(path); err != nil {
|
|
return ctx, err
|
|
}
|
|
case "file":
|
|
db = &slicestore.SliceStore{}
|
|
|
|
// run this after we've called db.Init()
|
|
defer func() {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
log.Printf("failed to file at '%s': %s\n", path, err)
|
|
os.Exit(3)
|
|
}
|
|
scanner := bufio.NewScanner(f)
|
|
scanner.Buffer(make([]byte, 16*1024*1024), 256*1024*1024)
|
|
i := 0
|
|
for scanner.Scan() {
|
|
var evt nostr.Event
|
|
if err := json.Unmarshal(scanner.Bytes(), &evt); err != nil {
|
|
log.Printf("invalid event read at line %d: %s (`%s`)\n", i, err, scanner.Text())
|
|
}
|
|
db.SaveEvent(evt)
|
|
i++
|
|
}
|
|
}()
|
|
case "":
|
|
return ctx, fmt.Errorf("couldn't determine store type, you can use --type to specify it manually")
|
|
default:
|
|
return ctx, fmt.Errorf("'%s' store type is not supported by this CLI", typ)
|
|
}
|
|
|
|
if err := db.Init(); err != nil {
|
|
return ctx, err
|
|
}
|
|
|
|
return ctx, nil
|
|
},
|
|
Commands: []*cli.Command{
|
|
queryOrSave,
|
|
query,
|
|
save,
|
|
delete_,
|
|
neg,
|
|
},
|
|
DefaultCommand: "query-or-save",
|
|
}
|
|
|
|
func main() {
|
|
if err := app.Run(context.Background(), os.Args); err != nil {
|
|
fmt.Println(err)
|
|
os.Exit(1)
|
|
}
|
|
}
|