meditime/db/badger.go

192 lines
3.9 KiB
Go

package db
import (
"context"
"encoding/json"
"fmt"
"sync"
"time"
"github.com/dgraph-io/badger"
)
// Badger db implementation
type Badger struct {
db *badger.DB
cancelGC func()
wg sync.WaitGroup
}
// NewBadger creates a new badger instance for the given path
func NewBadger(dbPath string) (*Badger, error) {
db, err := badger.Open(badger.DefaultOptions(dbPath).WithLogger(nil))
if err != nil {
return nil, fmt.Errorf("failed to open badger db at path %s: %w", dbPath, err)
}
ctx, cancel := context.WithCancel(context.Background())
b := &Badger{
db: db,
cancelGC: cancel,
}
b.wg.Add(1)
go func() {
defer b.wg.Done()
ticker := time.NewTicker(time.Hour)
defer ticker.Stop()
for {
select {
case <-ticker.C:
for b.db.RunValueLogGC(0.5) == nil && ctx.Err() == nil {
}
case <-ctx.Done():
return
}
}
}()
return b, nil
}
// Close the database
func (b *Badger) Close() error {
b.cancelGC()
b.wg.Wait()
return b.db.Close()
}
// AddUser to the database
func (b *Badger) AddUser(user *User) error {
return b.db.Update(func(tx *badger.Txn) error {
data, err := json.Marshal(user)
if err != nil {
return fmt.Errorf("failed to JSON marshal user: %w", err)
}
key := user.badgerKey()
if _, err = tx.Get(key); err == nil {
return fmt.Errorf("user %s already exists", user.Name)
}
return tx.Set(key, data)
})
}
// GetUser from the database
func (b *Badger) GetUser(username string) (user *User, err error) {
err = b.db.View(func(tx *badger.Txn) error {
item, err := tx.Get(badgerKeyForUsername(username))
if err != nil {
return fmt.Errorf("failed to get user value for username %s: %w", username, err)
}
user = &User{}
return item.Value(func(val []byte) error {
err = json.Unmarshal(val, user)
if err != nil {
return fmt.Errorf("failed to unmarshal user value for username %s: %w", username, err)
}
return nil
})
})
return
}
// ListUsers from the database
func (b *Badger) ListUsers() (users []*User, err error) {
err = b.db.View(func(tx *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.Prefix = []byte("user:")
it := tx.NewIterator(opts)
defer it.Close()
for it.Rewind(); it.Valid(); it.Next() {
item := it.Item()
err := item.Value(func(val []byte) error {
user := &User{}
err := json.Unmarshal(val, user)
if err != nil {
return fmt.Errorf("failed to unmarshal user value for user key %s: %w", string(item.Key()), err)
}
users = append(users, user)
return nil
})
if err != nil {
return err
}
}
return nil
})
return
}
// AddMedication to the database
func (b *Badger) AddMedication(medication *Medication) error {
return b.db.Update(func(tx *badger.Txn) error {
data, err := json.Marshal(medication)
if err != nil {
return fmt.Errorf("failed to JSON marshal medication: %w", err)
}
return tx.Set(medication.badgerKey(), data)
})
}
// RemoveMedication from the database
func (b *Badger) RemoveMedication(medication *Medication) error {
return b.db.Update(func(tx *badger.Txn) error {
return tx.Delete(medication.badgerKey())
})
}
// ListMedicationsForUser from the database
func (b *Badger) ListMedicationsForUser(user *User) (medications []*Medication, err error) {
err = b.db.View(func(tx *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.Prefix = badgerPrefixKeyForMedicationUser(user)
it := tx.NewIterator(opts)
defer it.Close()
for it.Rewind(); it.Valid(); it.Next() {
item := it.Item()
err := item.Value(func(val []byte) error {
medication := &Medication{}
err := json.Unmarshal(val, medication)
if err != nil {
return fmt.Errorf("failed to unmarshal medication value for medication key %s: %w", string(item.Key()), err)
}
medications = append(medications, medication)
return nil
})
if err != nil {
return err
}
}
return nil
})
return
}