clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

main
tinyrelay / internal / gitstore / store.go
store.go 2.2K
1package gitstore23import (4 "context"5 "database/sql"6 "errors"7 "path/filepath"89 "github.com/FelineStateMachine/tinyrelay/internal/storage"10 "github.com/FelineStateMachine/tinyrelay/protocol/nostr"11)1213// ErrDuplicate indicates that an event was already persisted.14var ErrDuplicate = errors.New("duplicate event")1516// Store adapts the shared SQLite event store to the Git engine contract.17type Store struct{ store *storage.Store }1819// Open creates a caller-owned SQLite store.20func Open(ctx context.Context, path string) (*Store, error) {21 path, err := filepath.Abs(path)22 if err != nil {23 return nil, err24 }25 store, err := storage.Open(ctx, path)26 if err != nil {27 return nil, err28 }29 return &Store{store: store}, nil30}3132// Wrap adapts an existing host store without taking lifecycle ownership.33func Wrap(store *storage.Store) *Store { return &Store{store: store} }3435func (s *Store) Query(ctx context.Context, f nostr.Filter, now int64, limit int) ([]nostr.Event, error) {36 result, err := s.store.Query(ctx, f, storage.QueryOptions{Now: now, Limit: limit, Access: storage.Access{All: true}})37 return result.Events, err38}39func (s *Store) Save(ctx context.Context, e nostr.Event, now int64) error {40 _, err := s.store.Save(ctx, e, storage.SaveOptions{Now: now})41 if errors.Is(err, storage.ErrDuplicate) {42 return ErrDuplicate43 }44 return err45}46func (s *Store) Latest(ctx context.Context, kind int, pubkey, identifier string) (nostr.Event, bool, error) {47 var raw string48 err := s.store.DB().QueryRowContext(ctx, "SELECT raw FROM events WHERE kind=? AND pubkey=? AND d=? ORDER BY created_at DESC,id ASC LIMIT 1", kind, pubkey, identifier).Scan(&raw)49 if errors.Is(err, sql.ErrNoRows) {50 return nostr.Event{}, false, nil51 }52 if err != nil {53 return nostr.Event{}, false, err54 }55 event, err := nostr.Parse([]byte(raw))56 if err != nil {57 return nostr.Event{}, false, err58 }59 return event, true, nil60}61func (s *Store) Exists(ctx context.Context, id string, kind int) (bool, error) {62 var found int63 err := s.store.DB().QueryRowContext(ctx, "SELECT 1 FROM events WHERE id=? AND kind=?", id, kind).Scan(&found)64 if errors.Is(err, sql.ErrNoRows) {65 return false, nil66 }67 return err == nil, err68}69func (s *Store) Close() error { return s.store.Close() }