~mariusor/motley

b5f98d5f30654fc8eea548cfc03bd41160ce7844 — Marius Orcsik 4 months ago 3bc467d
Reorganized how we load storage and urls
8 files changed, 191 insertions(+), 217 deletions(-)

M cmd/motley/main.go
M fedbox.go
M go.mod
M internal/cmd/cmd.go
M internal/config/config.go
M internal/config/config_test.go
R internal/{cmd/storage_all.go => storage/storage_all.go}
M ui.go
M cmd/motley/main.go => cmd/motley/main.go +15 -56
@@ 14,7 14,6 @@ import (
	"git.sr.ht/~marius/motley/internal/config"
	"git.sr.ht/~marius/motley/internal/env"
	"github.com/alecthomas/kong"
	"github.com/go-ap/fedbox"
	"github.com/sirupsen/logrus"
)



@@ 68,29 67,13 @@ func main() {
	ktx.Exit(0)
}

type multiError []error

func (m multiError) Error() string {
	s := strings.Builder{}
	for i, e := range m {
		s.WriteString(e.Error())
		if i < len(m)-1 {
			s.WriteString("\n")
		}
	}
	return s.String()
}

func loadArguments(conf *config.Options) ([]fedbox.FullStorage, error) {
func loadArguments(conf *config.Options) ([]config.FullStorage, error) {
	if len(Motley.Config)+len(Motley.Path) == 0 {
		return nil, fmt.Errorf("missing flags: you need to either pass a config path DSN or pairs of a storage DSN with an associated URL")
	}
	if len(Motley.URL)+len(Motley.Path) > 0 && len(Motley.URL) != len(Motley.Path) {
		return nil, fmt.Errorf("invalid flags: when passing storage DSN you need an associated URL for each of them")
	}

	m := make([]error, 0)
	stores := make([]fedbox.FullStorage, 0)
	stores := make([]config.FullStorage, 0)
	for _, dsnConfig := range Motley.Config {
		if dsnConfig == "" {
			continue


@@ 110,23 93,20 @@ func loadArguments(conf *config.Options) ([]fedbox.FullStorage, error) {
			m = append(m, fmt.Errorf("unable to load config file for environment %s: %s", c.Env, err))
			continue
		}
		db, err := cmd.Storage(c, l)
		if err != nil {
			m = append(m, fmt.Errorf("unable to access storage: %s", err))
		*conf = c
	}
	for _, u := range Motley.URL {
		if u == "" {
			continue
		}

		o, err := url.ParseRequestURI(c.BaseURL)
		_, err := url.ParseRequestURI(u)
		if err != nil {
			m = append(m, fmt.Errorf("invalid url passed: %s", err))
			continue
		}
		conf.Host = o.Host

		*conf = c
		stores = append(stores, db)
		conf.URLs = append(conf.URLs, u)
	}
	for i, sto := range Motley.Path {
	for _, sto := range Motley.Path {
		if sto == "" {
			continue
		}


@@ 135,36 115,15 @@ func loadArguments(conf *config.Options) ([]fedbox.FullStorage, error) {
			m = append(m, fmt.Errorf("invalid storage value, expected DSN of type:/path/to/storage"))
			continue
		}

		conf.Storage = config.StorageType(typ)
		conf.StoragePath = path

		if !validStorageType(conf.Storage) {
			m = append(m, fmt.Errorf("invalid storage type value %s", conf.Storage))
			continue
		}
		if u := Motley.URL[i]; u != "" {
			o, err := url.ParseRequestURI(u)
			if err != nil {
				m = append(m, fmt.Errorf("invalid url passed: %s", err))
				continue
			}
			conf.Host = o.Host
			conf.BaseURL = u
			conf.StoragePath = filepath.Clean(strings.Replace(conf.StoragePath, conf.Host, "", -1))
		}

		db, err := cmd.StorageFromDirectPath(*conf, l)
		if err != nil {
			m = append(m, fmt.Errorf("unable to access storage: %s", err))
			continue
		st := config.Storage{
			Type: config.StorageType(typ),
			Path: path,
		}

		if err != nil {
			m = append(m, fmt.Errorf("unable to initialize storage backend: %s", err))
		if !validStorageType(st.Type) {
			m = append(m, fmt.Errorf("invalid storage type value %s", conf.Storage))
			continue
		}
		stores = append(stores, db)
		conf.Storage = append(conf.Storage, st)
	}
	if len(m) > 0 {
		return nil, xerrors.Join(m...)

M fedbox.go => fedbox.go +60 -24
@@ 7,11 7,13 @@ import (
	"path"
	"path/filepath"

	"git.sr.ht/~marius/motley/internal/config"
	"git.sr.ht/~marius/motley/internal/env"
	"git.sr.ht/~marius/motley/internal/storage"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
	pub "github.com/go-ap/activitypub"
	"github.com/go-ap/errors"
	f "github.com/go-ap/fedbox"
	"github.com/go-ap/filters"
	tree "github.com/mariusor/bubbles-tree"
	"github.com/mariusor/qstring"


@@ 40,38 42,72 @@ type loggerFn func(string, ...interface{})

var logFn = func(string, ...interface{}) {}

type store struct {
	root pub.Item
	s    config.FullStorage
}

type fedbox struct {
	tree  map[pub.IRI]pub.Item
	iri   pub.IRI
	s     f.FullStorage
	logFn loggerFn
	tree   map[pub.IRI]pub.Item
	items  pub.IRIs
	stores []store
	logFn  loggerFn
}

func FedBOX(base pub.IRI, r f.FullStorage, l *logrus.Logger) *fedbox {
func FedBOX(rootIRIs []string, st []config.Storage, e env.Type, l *logrus.Logger) *fedbox {
	logFn = l.Infof
	return &fedbox{tree: make(map[pub.IRI]pub.Item), iri: base, s: r, logFn: l.Infof}
	stores := make([]store, 0)
	for _, s := range st {
		for _, iri := range rootIRIs {
			db, err := storage.Storage(s, e, iri, l)
			if err != nil {
				l.Debugf("unable to initialize storage %v: %s", s, err)
				continue
			}
			col, err := db.Load(pub.IRI(iri))
			if err != nil {
				l.Debugf("unable to load %s from storage %v: %s", iri, s, err)
				continue
			}
			pub.OnObject(col, func(o *pub.Object) error {
				stores = append(stores, store{
					root: o,
					s:    db,
				})
				return nil
			})
		}
	}
	return &fedbox{tree: make(map[pub.IRI]pub.Item), stores: stores, logFn: l.Infof}
}

func (f *fedbox) getService() pub.Item {
	col, err := f.s.Load(f.iri)
	if err != nil {
		return nil
func (f *fedbox) Load(iri pub.IRI, ff ...filters.Check) (pub.Item, error) {
	for _, st := range f.stores {
		col, err := st.s.Load(iri, ff...)
		if err == nil {
			return col, nil
		}
	}
	var service pub.Item
	pub.OnActor(col, func(o *pub.Actor) error {
		service = o
		return nil
	})
	return service
	return nil, errors.NotFoundf("unable to load %s in any storage", iri)
}

func initNodes(f *fedbox) tree.Nodes {
	n := node(
		f.getService(),
		withState(tree.NodeLastChild|tree.NodeSelected),
	)
func (f *fedbox) getRootNodes() pub.ItemCollection {
	rootNodes := make(pub.ItemCollection, len(f.stores))
	for i, st := range f.stores {
		rootNodes[i] = st.root
	}
	return rootNodes
}

	return tree.Nodes{n}
func initNodes(f *fedbox) tree.Nodes {
	nodes := make(tree.Nodes, 0)
	for _, root := range f.getRootNodes() {
		nodes = append(nodes, node(
			root,
			withState(tree.NodeLastChild|tree.NodeSelected),
		))
	}
	return nodes
}

type n struct {


@@ 569,7 605,7 @@ type accumFn func(context.Context, pub.CollectionInterface) error

func (f *fedbox) searchFn(ctx context.Context, g *errgroup.Group, loadIRI pub.IRI, accumFn accumFn) func() error {
	return func() error {
		col, err := f.s.Load(loadIRI)
		col, err := f.Load(loadIRI)
		if err != nil {
			return errors.Annotatef(err, "failed to load search: %s", loadIRI)
		}

M go.mod => go.mod +3 -3
@@ 11,6 11,7 @@ require (
	github.com/go-ap/errors v0.0.0-20231003111023-183eef4b31b7
	github.com/go-ap/fedbox v0.0.0-20231114165015-41259eb71b90
	github.com/go-ap/filters v0.0.0-20231114163756-0a70c1a4a942
	github.com/go-ap/processing v0.0.0-20231114164044-596105c0aac5
	github.com/go-ap/storage-badger v0.0.0-20231114164254-83bfd520a801
	github.com/go-ap/storage-boltdb v0.0.0-20231114164236-78b8f85c6fda
	github.com/go-ap/storage-fs v0.0.0-20231114164216-ebc6b6d920df


@@ 21,8 22,10 @@ require (
	github.com/mattn/go-runewidth v0.0.15
	github.com/muesli/reflow v0.3.0
	github.com/muesli/termenv v0.15.2
	github.com/openshift/osin v1.0.2-0.20220317075346-0f4d38c6e53f
	github.com/sirupsen/logrus v1.9.3
	golang.org/x/sync v0.5.0
	golang.org/x/text v0.14.0
)

require (


@@ 40,7 43,6 @@ require (
	github.com/go-ap/cache v0.0.0-20231114162417-36177bcbd4a9 // indirect
	github.com/go-ap/client v0.0.0-20231114162455-f09cf9766e95 // indirect
	github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73 // indirect
	github.com/go-ap/processing v0.0.0-20231114164044-596105c0aac5 // indirect
	github.com/go-chi/chi v4.1.2+incompatible // indirect
	github.com/go-chi/chi/v5 v5.0.10 // indirect
	github.com/go-fed/httpsig v1.1.0 // indirect


@@ 61,7 63,6 @@ require (
	github.com/mattn/go-sqlite3 v1.14.18 // indirect
	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
	github.com/muesli/cancelreader v0.2.2 // indirect
	github.com/openshift/osin v1.0.2-0.20220317075346-0f4d38c6e53f // indirect
	github.com/pborman/uuid v1.2.1 // indirect
	github.com/pkg/errors v0.9.1 // indirect
	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect


@@ 77,7 78,6 @@ require (
	golang.org/x/oauth2 v0.14.0 // indirect
	golang.org/x/sys v0.14.0 // indirect
	golang.org/x/term v0.14.0 // indirect
	golang.org/x/text v0.14.0 // indirect
	golang.org/x/tools v0.15.0 // indirect
	google.golang.org/appengine v1.6.8 // indirect
	google.golang.org/protobuf v1.31.0 // indirect

M internal/cmd/cmd.go => internal/cmd/cmd.go +6 -12
@@ 3,7 3,6 @@ package cmd
import (
	tui "git.sr.ht/~marius/motley"
	"git.sr.ht/~marius/motley/internal/config"
	"github.com/go-ap/fedbox"
	"github.com/sirupsen/logrus"
)



@@ 12,19 11,14 @@ var (
)

type Control struct {
	Conf    config.Options
	Storage []fedbox.FullStorage
	Conf config.Options
}

func New(conf config.Options, db ...fedbox.FullStorage) *Control {
	return &Control{
		Conf:    conf,
		Storage: db,
	}
func New(conf config.Options) *Control {
	return &Control{Conf: conf}
}

func ShowTui(conf config.Options, l *logrus.Logger, stores ...fedbox.FullStorage) error {
	ctl = *New(conf, stores...)

	return tui.Launch(ctl.Conf, ctl.Storage[0], l)
func ShowTui(conf config.Options, l *logrus.Logger, stores ...config.FullStorage) error {
	ctl = *New(conf)
	return tui.Launch(ctl.Conf, l)
}

M internal/config/config.go => internal/config/config.go +47 -55
@@ 6,13 6,15 @@ import (
	"os"
	"os/user"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"git.sr.ht/~marius/motley/internal/env"
	"github.com/go-ap/errors"
	"github.com/go-ap/fedbox/storage"
	"github.com/go-ap/processing"
	"github.com/joho/godotenv"
	"github.com/openshift/osin"
	"github.com/sirupsen/logrus"
)



@@ 27,31 29,36 @@ type BackendConfig struct {
	Name    string
}

type FullStorage interface {
	ListClients() ([]osin.Client, error)
	GetClient(id string) (osin.Client, error)
	UpdateClient(c osin.Client) error
	CreateClient(c osin.Client) error
	RemoveClient(id string) error
	osin.Storage
	processing.Store
	processing.KeyLoader
	storage.PasswordChanger
}

type Storage struct {
	Type StorageType
	Path string
}

type Options struct {
	Env         env.Type
	LogLevel    logrus.Level
	TimeOut     time.Duration
	Secure      bool
	CertPath    string
	KeyPath     string
	Host        string
	Listen      string
	BaseURL     string
	Storage     StorageType
	StoragePath string
	Env      env.Type
	LogLevel logrus.Level
	URLs     []string
	Storage  []Storage
}

type StorageType string

const (
	KeyENV         = "ENV"
	KeyTimeOut     = "TIME_OUT"
	KeyLogLevel    = "LOG_LEVEL"
	KeyHostname    = "HOSTNAME"
	KeyHTTPS       = "HTTPS"
	KeyCertPath    = "CERT_PATH"
	KeyKeyPath     = "KEY_PATH"
	KeyListen      = "LISTEN"
	KeyDBHost      = "DB_HOST"
	KeyDBPort      = "DB_PORT"
	KeyDBName      = "DB_NAME"


@@ 91,32 98,35 @@ func FullStoragePath(dir string) (string, error) {
	return dir, nil
}

func (o Options) BaseStoragePath() (string, error) {
	return FullStoragePath(filepath.Join(o.StoragePath, string(o.Storage), string(o.Env), o.Host))
func (o Storage) BaseStoragePath(e env.Type, host string) (string, error) {
	if u, err := url.ParseRequestURI(host); err == nil {
		host = u.Host
	}
	return FullStoragePath(filepath.Join(o.Path, string(o.Type), string(e), host))
}

func (o Options) BoltDB() (string, error) {
	base, err := o.BaseStoragePath()
func (o Storage) BoltDB(e env.Type, host string) (string, error) {
	base, err := o.BaseStoragePath(e, host)
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("%s/fedbox.bdb", base), nil
}

func (o Options) BoltDBOAuth2() (string, error) {
	base, err := o.BaseStoragePath()
func (o Storage) BoltDBOAuth2(e env.Type, host string) (string, error) {
	base, err := o.BaseStoragePath(e, host)
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("%s/oauth.bdb", base), nil
}

func (o Options) Badger() (string, error) {
	return o.BaseStoragePath()
func (o Storage) Badger(e env.Type, host string) (string, error) {
	return o.BaseStoragePath(e, host)
}

func (o Options) BadgerOAuth2() (string, error) {
	return fmt.Sprintf("%s/%s/%s", o.StoragePath, o.Env, "oauth"), nil
func (o Storage) BadgerOAuth2(e env.Type, _ string) (string, error) {
	return fmt.Sprintf("%s/%s/%s", o.Path, e, "oauth"), nil
}

func prefKey(k string) string {


@@ 191,38 201,20 @@ func LoadFromEnv(base string, e env.Type, timeOut time.Duration) (Options, error
		e = env.Type(loadKeyFromEnv(KeyENV, "dev"))
	}
	conf.Env = e
	if conf.Host == "" {
		conf.Host = loadKeyFromEnv(KeyHostname, conf.Host)
	}
	conf.TimeOut = timeOut
	if to, _ := time.ParseDuration(loadKeyFromEnv(KeyTimeOut, "")); to > 0 {
		conf.TimeOut = to
	}
	if conf.Host != "" {
		if u, err := url.ParseRequestURI(conf.Host); err != nil {
			conf.Secure, _ = strconv.ParseBool(loadKeyFromEnv(KeyHTTPS, "true"))
			proto := "http"
			if conf.Secure {
				proto = "https"
			}
			conf.BaseURL = fmt.Sprintf("%s://%s", proto, conf.Host)
		} else {
			conf.Secure = u.Scheme == "https"
			conf.Host = u.Host
			conf.BaseURL = u.String()
		}
	if host := loadKeyFromEnv(KeyHostname, ""); host != "" {
		conf.URLs = append(conf.URLs, fmt.Sprintf("https://%s", host))
	}
	conf.KeyPath = loadKeyFromEnv(KeyKeyPath, "")
	conf.CertPath = loadKeyFromEnv(KeyCertPath, "")

	conf.Listen = loadKeyFromEnv(KeyListen, "")
	envStorage := loadKeyFromEnv(KeyStorage, string(StorageFS))
	conf.Storage = StorageType(strings.ToLower(envStorage))
	conf.StoragePath = loadKeyFromEnv(KeyStoragePath, "")
	if conf.StoragePath == "" {
		conf.StoragePath = base
	st := Storage{
		Type: StorageType(strings.ToLower(envStorage)),
		Path: loadKeyFromEnv(KeyStoragePath, ""),
	}
	if st.Path == "" {
		st.Path = base
	}
	conf.StoragePath = filepath.Clean(conf.StoragePath)
	st.Path = filepath.Clean(st.Path)
	conf.Storage = append(conf.Storage, st)

	return conf, nil
}

M internal/config/config_test.go => internal/config/config_test.go +20 -28
@@ 13,8 13,6 @@ import (
const (
	hostname = "testing.git"
	logLvl   = "panic"
	secure   = true
	listen   = "127.0.0.3:666"
	pgSQL    = "postgres"
	boltDB   = "boltdb"
	dbHost   = "127.0.0.6"


@@ 33,10 31,7 @@ func TestLoadFromEnv(t *testing.T) {
		os.Setenv(KeyDBUser, dbUser)
		os.Setenv(KeyDBPw, dbPw)

		os.Setenv(KeyHostname, hostname)
		os.Setenv(KeyLogLevel, logLvl)
		os.Setenv(KeyHTTPS, fmt.Sprintf("%t", secure))
		os.Setenv(KeyListen, listen)
		os.Setenv(KeyStorage, pgSQL)

		var baseURL = fmt.Sprintf("https://%s", hostname)


@@ 62,20 57,15 @@ func TestLoadFromEnv(t *testing.T) {
			t.Errorf("Invalid loaded value for %s: %s, expected %s", KeyDBPw, db.Pw, dbPw)
		}

		if c.Host != hostname {
			t.Errorf("Invalid loaded value for %s: %s, expected %s", KeyHostname, c.Host, hostname)
		for _, st := range c.Storage {
			if st.Type != pgSQL {
				t.Errorf("Invalid loaded value for %s: %s, expected %s", KeyStorage, st.Type, pgSQL)
			}
		}
		if c.Secure != secure {
			t.Errorf("Invalid loaded value for %s: %t, expected %t", KeyHTTPS, c.Secure, secure)
		}
		if c.Listen != listen {
			t.Errorf("Invalid loaded value for %s: %s, expected %s", KeyListen, c.Listen, listen)
		}
		if c.Storage != pgSQL {
			t.Errorf("Invalid loaded value for %s: %s, expected %s", KeyStorage, c.Storage, pgSQL)
		}
		if c.BaseURL != baseURL {
			t.Errorf("Invalid loaded BaseURL value: %s, expected %s", c.BaseURL, baseURL)
		for _, u := range c.URLs {
			if u != baseURL {
				t.Errorf("Invalid loaded BaseURL value: %s, expected %s", u, baseURL)
			}
		}
	}
	{


@@ 85,16 75,18 @@ func TestLoadFromEnv(t *testing.T) {
			t.Errorf("Error loading env: %s", err)
		}
		var tmp = strings.TrimRight(os.TempDir(), "/")
		if strings.TrimRight(c.StoragePath, "/") != tmp {
			t.Errorf("Invalid loaded boltdb dir value: %s, expected %s", c.StoragePath, tmp)
		}
		var expected = fmt.Sprintf("%s/%s-%s.bdb", tmp, strings.Replace(hostname, ".", "-", 1), env.TEST)
		p, err := c.BoltDB()
		if err != nil {
			t.Errorf("BoltDB() errored: %s", err)
		}
		if p != expected {
			t.Errorf("Invalid loaded boltdb file value: %s, expected %s", p, expected)
		for _, st := range c.Storage {
			if strings.TrimRight(st.Path, "/") != tmp {
				t.Errorf("Invalid loaded boltdb dir value: %s, expected %s", st.Path, tmp)
			}
			var expected = fmt.Sprintf("%s/%s-%s.bdb", tmp, strings.Replace(hostname, ".", "-", 1), env.TEST)
			p, err := st.BoltDB(c.Env, c.Host)
			if err != nil {
				t.Errorf("BoltDB() errored: %s", err)
			}
			if p != expected {
				t.Errorf("Invalid loaded boltdb file value: %s, expected %s", p, expected)
			}
		}
	}
}

R internal/cmd/storage_all.go => internal/storage/storage_all.go +37 -33
@@ 1,10 1,12 @@
package cmd
package storage

import (
	iofs "io/fs"
	"path/filepath"
	"net/url"
	"strings"

	"git.sr.ht/~marius/motley/internal/config"
	"git.sr.ht/~marius/motley/internal/env"
	"github.com/go-ap/errors"
	"github.com/go-ap/fedbox"
	badger "github.com/go-ap/storage-badger"


@@ 31,8 33,8 @@ var (
	}
)

func getBadgerStorage(c config.Options, l logrus.FieldLogger) (fedbox.FullStorage, error) {
	path, err := c.Badger()
func getBadgerStorage(c config.Storage, e env.Type, u string, l logrus.FieldLogger) (fedbox.FullStorage, error) {
	path, err := c.Badger(e, u)
	if err != nil {
		return nil, err
	}


@@ 56,13 58,13 @@ func getBoltStorageAtPath(dir, _ string, l logrus.FieldLogger) (fedbox.FullStora
	})
}

func getBoltStorage(c config.Options, l logrus.FieldLogger) (fedbox.FullStorage, error) {
	path, err := c.BoltDB()
func getBoltStorage(c config.Storage, e env.Type, u string, l logrus.FieldLogger) (fedbox.FullStorage, error) {
	path, err := c.BoltDB(e, u)
	if err != nil {
		return nil, err
	}
	l.Debugf("Initializing boltdb storage at %s", path)
	db, err := getBoltStorageAtPath(path, c.BaseURL, l)
	db, err := getBoltStorageAtPath(path, u, l)
	if err != nil {
		return nil, err
	}


@@ 70,37 72,39 @@ func getBoltStorage(c config.Options, l logrus.FieldLogger) (fedbox.FullStorage,
	return db, nil
}

func getFsStorageAtPath(dir, url string, l logrus.FieldLogger) (fedbox.FullStorage, error) {
	if dir, err := config.FullStoragePath(dir); err != nil {
		return nil, err
	} else {
		return fs.New(fs.Config{
			Path:  dir,
			LogFn: InfoLogFn(l),
			ErrFn: ErrLogFn(l),
		})
func getFsStorageAtPath(dir string, e env.Type, rawURL string, l logrus.FieldLogger) (fedbox.FullStorage, error) {
	if u, err := url.ParseRequestURI(rawURL); err == nil {
		rawURL = u.Host
	}
	if strings.Contains(dir, rawURL) {
		dir = strings.Replace(dir, rawURL, "", 1)
	}
	return fs.New(fs.Config{
		Path:  dir,
		LogFn: InfoLogFn(l),
		ErrFn: ErrLogFn(l),
	})
}

func getFsStorage(c config.Options, l logrus.FieldLogger) (fedbox.FullStorage, error) {
	path, err := c.BaseStoragePath()
func getFsStorage(c config.Storage, e env.Type, u string, l logrus.FieldLogger) (fedbox.FullStorage, error) {
	path, err := c.BaseStoragePath(e, u)
	if err != nil {
		var pathError *iofs.PathError
		if !errors.As(err, &pathError) {
			return nil, err
		}
		path = c.StoragePath
		path = c.Path
	}
	l.Debugf("Initializing fs storage at %s", path)
	db, err := getFsStorageAtPath(filepath.Dir(path), c.BaseURL, l)
	db, err := getFsStorageAtPath(path, e, u, l)
	if err != nil {
		return nil, err
	}
	return db, nil
}

func getSqliteStorage(c config.Options, l logrus.FieldLogger) (fedbox.FullStorage, error) {
	l.Debugf("Initializing sqlite storage at %s", c.StoragePath)
func getSqliteStorage(c config.Storage, e env.Type, u string, l logrus.FieldLogger) (fedbox.FullStorage, error) {
	l.Debugf("Initializing sqlite storage at %s", c.Path)
	db, err := sqlite.New(sqlite.Config{})
	if err != nil {
		return nil, err


@@ 109,25 113,25 @@ func getSqliteStorage(c config.Options, l logrus.FieldLogger) (fedbox.FullStorag

}

func Storage(c config.Options, l logrus.FieldLogger) (fedbox.FullStorage, error) {
	switch c.Storage {
func Storage(c config.Storage, e env.Type, u string, l logrus.FieldLogger) (fedbox.FullStorage, error) {
	switch c.Type {
	case config.StorageBoltDB:
		return getBoltStorage(c, l)
		return getBoltStorage(c, e, u, l)
	case config.StorageBadger:
		return getBadgerStorage(c, l)
		return getBadgerStorage(c, e, u, l)
	case config.StorageSqlite:
		return getSqliteStorage(c, l)
		return getSqliteStorage(c, e, u, l)
	case config.StorageFS:
		return getFsStorage(c, l)
		return getFsStorage(c, e, u, l)
	}
	return nil, errors.NotImplementedf("Invalid storage type %s", c.Storage)
	return nil, errors.NotImplementedf("Invalid storage type %s", c.Type)
}

func StorageFromDirectPath(c config.Options, l logrus.FieldLogger) (fedbox.FullStorage, error) {
	switch c.Storage {
func StorageFromDirectPath(c config.Storage, e env.Type, u string, l logrus.FieldLogger) (fedbox.FullStorage, error) {
	switch c.Type {
	case config.StorageFS:
		db, err := getFsStorageAtPath(c.StoragePath, c.BaseURL, l)
		db, err := getFsStorageAtPath(c.Path, e, u, l)
		return db, err
	}
	return nil, errors.NotImplementedf("Invalid storage type %s", c.Storage)
	return nil, errors.NotImplementedf("Invalid storage type %s", c.Type)
}

M ui.go => ui.go +3 -6
@@ 3,7 3,6 @@ package motley
import (
	"context"
	"fmt"
	f "github.com/go-ap/fedbox"
	"time"

	"git.sr.ht/~marius/motley/internal/config"


@@ 11,7 10,6 @@ import (
	"github.com/charmbracelet/bubbles/key"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
	pub "github.com/go-ap/activitypub"
	tree "github.com/mariusor/bubbles-tree"
	"github.com/muesli/reflow/wordwrap"
	"github.com/sirupsen/logrus"


@@ 92,9 90,8 @@ var (
	helpViewStyle         = newStyle(statusBarNoteFg, NewColorPair("#1B1B1B", "#f2f2f2"), false)
)

func Launch(conf config.Options, r f.FullStorage, l *logrus.Logger) error {
	base := pub.IRI(conf.BaseURL)
	mm := newModel(FedBOX(base, r, l), conf.Env, l)
func Launch(conf config.Options, l *logrus.Logger) error {
	mm := newModel(FedBOX(conf.URLs, conf.Storage, conf.Env, l), conf.Env, l)
	_, err := tea.NewProgram(mm, tea.WithAltScreen(), tea.WithMouseCellMotion()).Run()
	return err
}


@@ 115,7 112,7 @@ func newModel(ff *fedbox, env env.Type, l *logrus.Logger) *model {
	m.tree = newTreeModel(m.commonModel, initNodes(m.f))
	m.pager = newItemModel(m.commonModel)
	m.status = newStatusModel(m.commonModel)
	m.status.logo = logoView(pubUrl(ff.getService()), env)
	m.status.logo = logoView(pubUrl(ff.getRootNodes()), env)
	return m
}