ref(server): use more interfaces

Ayman Bagabas created

* implement a fakedb for testing

Change summary

.gitignore                       |   2 
proto/provider.go                |  12 ++
proto/repo.go                    |  56 +++++++++-
server/cmd/cat.go                |  25 +++-
server/cmd/cmd.go                |   8 
server/cmd/git.go                |  24 +++-
server/cmd/list.go               |  22 ++-
server/cmd/reload.go             |   6 
server/config/access.go          |  63 ++----------
server/config/config.go          |   1 
server/config/repo.go            | 170 ++++++++++++++++++++++++++++++++++
server/config/user.go            |   3 
server/db/db.go                  |  15 ---
server/db/fakedb/db.go           |  73 ++++++-------
server/db/sqlite/repo.go         |  58 -----------
server/db/sqlite/sql.go          |  20 ----
server/db/sqlite/sqlite.go       |  82 ++-------------
server/file/file.go              | 108 ---------------------
server/git/daemon/daemon_test.go |   2 
server/server.go                 |  13 -
server/session.go                |  12 +-
server/session_test.go           |  12 +-
22 files changed, 361 insertions(+), 426 deletions(-)

Detailed changes

.gitignore 🔗

@@ -1,5 +1,5 @@
 soft
-soft-serve
+data
 dist
 testdata
 completions/

proto/provider.go 🔗

@@ -1,7 +1,15 @@
 package proto
 
-// Provider is a repository provider.
+// Provider is a Git repository provider.
 type Provider interface {
 	// Open opens a repository.
-	Open(name string) (RepositoryService, error)
+	Open(name string) (Repository, error)
+	// ListRepos lists all repositories.
+	ListRepos() ([]Metadata, error)
+}
+
+// MetadataProvider is a Git repository metadata provider.
+type MetadataProvider interface {
+	// Metadata gets a repository's metadata.
+	Metadata(name string) (Metadata, error)
 }

proto/repo.go 🔗

@@ -1,17 +1,57 @@
 package proto
 
-// Repository is Git repository.
-type Repository interface {
+import (
+	"path/filepath"
+
+	"github.com/charmbracelet/soft-serve/git"
+	"github.com/gobwas/glob"
+)
+
+// Metadata is a repository's metadata.
+type Metadata interface {
 	Name() string
 	ProjectName() string
 	Description() string
 	IsPrivate() bool
+	Collabs() []User
+	Open() (Repository, error)
+}
+
+// Repository is Git repository.
+type Repository interface {
+	Name() string
+	Repository() *git.Repository
 }
 
-// RepositoryService is a service for managing repositories metadata.
-type RepositoryService interface {
-	Repository
-	SetProjectName(string) error
-	SetDescription(string) error
-	SetPrivate(bool) error
+// LatestFile returns the contents of the latest file at the specified path in
+// the repository and its file path.
+func LatestFile(r Repository, pattern string) (string, string, error) {
+	g := glob.MustCompile(pattern)
+	dir := filepath.Dir(pattern)
+	head, err := r.Repository().HEAD()
+	if err != nil {
+		return "", "", err
+	}
+	t, err := r.Repository().TreePath(head, dir)
+	if err != nil {
+		return "", "", err
+	}
+	ents, err := t.Entries()
+	if err != nil {
+		return "", "", err
+	}
+	for _, e := range ents {
+		fp := filepath.Join(dir, e.Name())
+		if e.IsTree() {
+			continue
+		}
+		if g.Match(fp) {
+			bts, err := e.Contents()
+			if err != nil {
+				return "", "", err
+			}
+			return string(bts), fp, nil
+		}
+	}
+	return "", "", git.ErrFileNotFound
 }

server/cmd/cat.go 🔗

@@ -7,7 +7,6 @@ import (
 	"github.com/alecthomas/chroma/lexers"
 	gansi "github.com/charmbracelet/glamour/ansi"
 	"github.com/charmbracelet/lipgloss"
-	"github.com/charmbracelet/soft-serve/config"
 	"github.com/charmbracelet/soft-serve/proto"
 	"github.com/charmbracelet/soft-serve/ui/common"
 	"github.com/muesli/termenv"
@@ -32,27 +31,35 @@ func CatCommand() *cobra.Command {
 		Short: "Outputs the contents of the file at path.",
 		Args:  cobra.ExactArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			ac, s := fromContext(cmd)
+			cfg, s := fromContext(cmd)
 			ps := strings.Split(args[0], "/")
-			rn := ps[0]
+			rn := strings.TrimSuffix(ps[0], ".git")
 			fp := strings.Join(ps[1:], "/")
-			auth := ac.AuthRepo(rn, s.PublicKey())
+			auth := cfg.AuthRepo(rn, s.PublicKey())
 			if auth < proto.ReadOnlyAccess {
 				return ErrUnauthorized
 			}
-			var repo *config.Repo
+			var repo proto.Repository
 			repoExists := false
-			for _, rp := range ac.Source.AllRepos() {
-				if rp.Repo() == rn {
+			repos, err := cfg.ListRepos()
+			if err != nil {
+				return err
+			}
+			for _, rp := range repos {
+				if rp.Name() == rn {
+					re, err := rp.Open()
+					if err != nil {
+						continue
+					}
 					repoExists = true
-					repo = rp
+					repo = re
 					break
 				}
 			}
 			if !repoExists {
 				return ErrRepoNotFound
 			}
-			c, _, err := repo.LatestFile(fp)
+			c, _, err := proto.LatestFile(repo, fp)
 			if err != nil {
 				return err
 			}

server/cmd/cmd.go 🔗

@@ -3,7 +3,7 @@ package cmd
 import (
 	"fmt"
 
-	appCfg "github.com/charmbracelet/soft-serve/config"
+	"github.com/charmbracelet/soft-serve/server/config"
 	"github.com/gliderlabs/ssh"
 	"github.com/spf13/cobra"
 )
@@ -77,9 +77,9 @@ func RootCommand() *cobra.Command {
 	return rootCmd
 }
 
-func fromContext(cmd *cobra.Command) (*appCfg.Config, ssh.Session) {
+func fromContext(cmd *cobra.Command) (*config.Config, ssh.Session) {
 	ctx := cmd.Context()
-	ac := ctx.Value(ConfigCtxKey).(*appCfg.Config)
+	cfg := ctx.Value(ConfigCtxKey).(*config.Config)
 	s := ctx.Value(SessionCtxKey).(ssh.Session)
-	return ac, s
+	return cfg, s
 }

server/cmd/git.go 🔗

@@ -4,39 +4,47 @@ import (
 	"io"
 	"os/exec"
 
-	"github.com/charmbracelet/soft-serve/config"
 	"github.com/charmbracelet/soft-serve/proto"
 	"github.com/spf13/cobra"
 )
 
+// TODO: remove this command.
 // GitCommand returns a command that handles Git operations.
 func GitCommand() *cobra.Command {
 	gitCmd := &cobra.Command{
 		Use:   "git REPO COMMAND",
 		Short: "Perform Git operations on a repository.",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			ac, s := fromContext(cmd)
-			auth := ac.AuthRepo("config", s.PublicKey())
+			cfg, s := fromContext(cmd)
+			auth := cfg.AuthRepo("config", s.PublicKey())
 			if auth < proto.AdminAccess {
 				return ErrUnauthorized
 			}
 			if len(args) < 1 {
 				return runGit(nil, s, s, "")
 			}
-			var repo *config.Repo
+			var repo proto.Repository
 			rn := args[0]
 			repoExists := false
-			for _, rp := range ac.Source.AllRepos() {
-				if rp.Repo() == rn {
+			repos, err := cfg.ListRepos()
+			if err != nil {
+				return err
+			}
+			for _, rp := range repos {
+				if rp.Name() == rn {
+					re, err := rp.Open()
+					if err != nil {
+						continue
+					}
 					repoExists = true
-					repo = rp
+					repo = re
 					break
 				}
 			}
 			if !repoExists {
 				return ErrRepoNotFound
 			}
-			return runGit(nil, s, s, repo.Path(), args[1:]...)
+			return runGit(nil, s, s, repo.Repository().Path, args[1:]...)
 		},
 	}
 	gitCmd.Flags().SetInterspersed(false)

server/cmd/list.go 🔗

@@ -18,36 +18,40 @@ func ListCommand() *cobra.Command {
 		Short:   "List file or directory at path.",
 		Args:    cobra.RangeArgs(0, 1),
 		RunE: func(cmd *cobra.Command, args []string) error {
-			ac, s := fromContext(cmd)
+			cfg, s := fromContext(cmd)
 			rn := ""
 			path := ""
 			ps := []string{}
 			if len(args) > 0 {
 				path = filepath.Clean(args[0])
 				ps = strings.Split(path, "/")
-				rn = ps[0]
-				auth := ac.AuthRepo(rn, s.PublicKey())
+				rn = strings.TrimSuffix(ps[0], ".git")
+				auth := cfg.AuthRepo(rn, s.PublicKey())
 				if auth < proto.ReadOnlyAccess {
 					return ErrUnauthorized
 				}
 			}
 			if path == "" || path == "." || path == "/" {
-				for _, r := range ac.Source.AllRepos() {
-					if ac.AuthRepo(r.Repo(), s.PublicKey()) >= proto.ReadOnlyAccess {
-						fmt.Fprintln(s, r.Repo())
+				repos, err := cfg.ListRepos()
+				if err != nil {
+					return err
+				}
+				for _, r := range repos {
+					if cfg.AuthRepo(r.Name(), s.PublicKey()) >= proto.ReadOnlyAccess {
+						fmt.Fprintln(s, r.Name())
 					}
 				}
 				return nil
 			}
-			r, err := ac.Source.GetRepo(rn)
+			r, err := cfg.Open(rn)
 			if err != nil {
 				return err
 			}
-			head, err := r.HEAD()
+			head, err := r.Repository().HEAD()
 			if err != nil {
 				return err
 			}
-			tree, err := r.Tree(head, "")
+			tree, err := r.Repository().TreePath(head, "")
 			if err != nil {
 				return err
 			}

server/cmd/reload.go 🔗

@@ -11,12 +11,12 @@ func ReloadCommand() *cobra.Command {
 		Use:   "reload",
 		Short: "Reloads the configuration",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			ac, s := fromContext(cmd)
-			auth := ac.AuthRepo("config", s.PublicKey())
+			cfg, s := fromContext(cmd)
+			auth := cfg.AuthRepo("config", s.PublicKey())
 			if auth < proto.AdminAccess {
 				return ErrUnauthorized
 			}
-			return ac.Reload()
+			return nil
 		},
 	}
 	return reloadCmd

server/config/access.go 🔗

@@ -1,8 +1,6 @@
 package config
 
 import (
-	"strings"
-
 	"github.com/charmbracelet/soft-serve/proto"
 	"github.com/gliderlabs/ssh"
 	gossh "golang.org/x/crypto/ssh"
@@ -40,18 +38,24 @@ func (c *Config) PublicKeyHandler(ctx ssh.Context, pk ssh.PublicKey) bool {
 // If repo exists, and not private, then access is based on config.AnonAccess.
 func (c *Config) accessForKey(repo string, pk ssh.PublicKey) proto.AccessLevel {
 	anon := c.AnonAccess
-	private := c.isPrivate(repo)
-	// Find user
+	info, err := c.Metadata(repo)
+	if err != nil || info == nil {
+		return anon
+	}
+	private := info.IsPrivate()
+	collabs := info.Collabs()
 	if pk != nil {
-		if u := c.findUser(pk); u != nil {
+		for _, u := range collabs {
 			if u.IsAdmin() {
 				return proto.AdminAccess
 			}
-			if c.isCollab(repo, pk) {
-				if anon > proto.ReadWriteAccess {
-					return anon
+			for _, k := range u.PublicKeys() {
+				if ssh.KeysEqual(pk, k) {
+					if anon > proto.ReadWriteAccess {
+						return anon
+					}
+					return proto.ReadWriteAccess
 				}
-				return proto.ReadWriteAccess
 			}
 			if !private {
 				if anon > proto.ReadOnlyAccess {
@@ -76,44 +80,3 @@ func (c *Config) countUsers() int {
 	}
 	return count
 }
-
-func (c *Config) findUser(pk ssh.PublicKey) proto.User {
-	k := strings.TrimSpace(string(gossh.MarshalAuthorizedKey(pk)))
-	u, err := c.DB().GetUserByPublicKey(k)
-	if err != nil {
-		return nil
-	}
-	ks, err := c.DB().GetUserPublicKeys(u)
-	if err != nil {
-		return nil
-	}
-	return &user{user: u, keys: ks}
-}
-
-func (c *Config) findRepo(repo string) proto.Repository {
-	r, err := c.DB().Open(repo)
-	if err != nil {
-		return nil
-	}
-	return r
-}
-
-func (c *Config) isPrivate(repo string) bool {
-	if r := c.findRepo(repo); r != nil {
-		return r.IsPrivate()
-	}
-	return false
-}
-
-func (c *Config) isCollab(repo string, pk ssh.PublicKey) bool {
-	pks, err := c.DB().ListRepoPublicKeys(repo)
-	if err != nil {
-		return false
-	}
-	for _, k := range pks {
-		if ssh.KeysEqual(pk, k) {
-			return true
-		}
-	}
-	return false
-}

server/config/config.go 🔗

@@ -86,6 +86,7 @@ type Config struct {
 	Git  GitConfig `env:"GIT" envPrefix:"GIT_"`
 	Db   DBConfig  `env:"DB" envPrefix:"DB_"`
 
+	ServerName string            `env:"SERVER_NAME" envDefault:"Soft Serve"`
 	AnonAccess proto.AccessLevel `env:"ANON_ACCESS" envDefault:"read-only"`
 	DataPath   string            `env:"DATA_PATH" envDefault:"data"`
 

server/config/repo.go 🔗

@@ -0,0 +1,170 @@
+package config
+
+import (
+	"log"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/charmbracelet/soft-serve/git"
+	"github.com/charmbracelet/soft-serve/proto"
+	"github.com/charmbracelet/soft-serve/server/db/types"
+)
+
+var _ proto.Provider = &Config{}
+var _ proto.MetadataProvider = &Config{}
+
+// Metadata returns the repository's metadata.
+func (c *Config) Metadata(name string) (proto.Metadata, error) {
+	i, err := c.db.GetRepo(name)
+	if err != nil {
+		return nil, err
+	}
+	return &repo{
+		cfg:  c,
+		info: i,
+	}, nil
+}
+
+// Open opens a repository.
+func (c *Config) Open(name string) (proto.Repository, error) {
+	if name == "" {
+		return nil, os.ErrNotExist
+	}
+	r, err := git.Open(filepath.Join(c.RepoPath(), name+".git"))
+	if err != nil {
+		log.Printf("error opening repository %q: %v", name, err)
+		return nil, err
+	}
+	return &repo{
+		cfg:  c,
+		repo: r,
+	}, nil
+}
+
+// ListRepos lists all repositories metadata.
+func (c *Config) ListRepos() ([]proto.Metadata, error) {
+	md := make([]proto.Metadata, 0)
+	ds, err := os.ReadDir(c.RepoPath())
+	if err != nil {
+		return nil, err
+	}
+	for _, d := range ds {
+		name := strings.TrimSuffix(d.Name(), ".git")
+		r, err := c.db.GetRepo(name)
+		if err != nil || r == nil {
+			md = append(md, &emptyMetadata{
+				name: name,
+				cfg:  c,
+			})
+		} else {
+			md = append(md, &repo{
+				cfg:  c,
+				info: r,
+			})
+		}
+	}
+	return md, nil
+}
+
+var _ proto.Metadata = emptyMetadata{}
+
+type emptyMetadata struct {
+	name string
+	cfg  *Config
+}
+
+// Collabs implements proto.Metadata.
+func (emptyMetadata) Collabs() []proto.User {
+	return []proto.User{}
+}
+
+// Description implements proto.Metadata.
+func (emptyMetadata) Description() string {
+	return ""
+}
+
+// IsPrivate implements proto.Metadata.
+func (emptyMetadata) IsPrivate() bool {
+	return false
+}
+
+// Name implements proto.Metadata.
+func (e emptyMetadata) Name() string {
+	return e.name
+}
+
+// Open implements proto.Metadata.
+func (e emptyMetadata) Open() (proto.Repository, error) {
+	return e.cfg.Open(e.Name())
+}
+
+// ProjectName implements proto.Metadata.
+func (emptyMetadata) ProjectName() string {
+	return ""
+}
+
+var _ proto.Metadata = &repo{}
+var _ proto.Repository = &repo{}
+
+// repo represents a Git repository.
+type repo struct {
+	cfg  *Config
+	repo *git.Repository
+	info *types.Repo
+}
+
+// Open opens the underlying Repository.
+func (r *repo) Open() (proto.Repository, error) {
+	return r.cfg.Open(r.Name())
+}
+
+// Name returns the name of the repository.
+func (r *repo) Name() string {
+	if r.repo != nil {
+		strings.TrimSuffix(filepath.Base(r.repo.Path), ".git")
+	}
+	return r.info.Name
+}
+
+// ProjectName returns the repository's project name.
+func (r *repo) ProjectName() string {
+	return r.info.ProjectName
+}
+
+// Description returns the repository's description.
+func (r *repo) Description() string {
+	return r.info.Description
+}
+
+// IsPrivate returns true if the repository is private.
+func (r *repo) IsPrivate() bool {
+	return r.info.Private
+}
+
+// Collabs returns the repository's collaborators.
+func (r *repo) Collabs() []proto.User {
+	collabs := make([]proto.User, 0)
+	cs, err := r.cfg.db.ListRepoCollabs(r.Name())
+	if err != nil {
+		return collabs
+	}
+	for i, c := range cs {
+		ks, err := r.cfg.db.GetUserPublicKeys(c)
+		if err != nil {
+			log.Printf("error getting public keys for %q: %v", c.Name, err)
+			continue
+		}
+		u := &user{
+			user: c,
+			keys: ks,
+		}
+		collabs[i] = u
+	}
+	return collabs
+}
+
+// Repository returns the underlying git.Repository.
+func (r *repo) Repository() *git.Repository {
+	return r.repo
+}

server/config/user.go 🔗

@@ -3,10 +3,13 @@ package config
 import (
 	"net/mail"
 
+	"github.com/charmbracelet/soft-serve/proto"
 	"github.com/charmbracelet/soft-serve/server/db/types"
 	"golang.org/x/crypto/ssh"
 )
 
+var _ proto.User = &user{}
+
 type user struct {
 	user *types.User
 	keys []*types.PublicKey

server/db/db.go 🔗

@@ -1,21 +1,9 @@
 package db
 
 import (
-	"github.com/charmbracelet/soft-serve/proto"
 	"github.com/charmbracelet/soft-serve/server/db/types"
 )
 
-// ConfigStore is a configuration database storage.
-type ConfigStore interface {
-	// Config
-	GetConfig() (*types.Config, error)
-	SetConfigName(string) error
-	SetConfigHost(string) error
-	SetConfigPort(int) error
-	SetConfigAnonAccess(string) error
-	SetConfigAllowKeyless(bool) error
-}
-
 // UserStore is a user database storage.
 type UserStore interface {
 	// Users
@@ -63,9 +51,6 @@ type CollabStore interface {
 
 // Store is a database.
 type Store interface {
-	proto.Provider
-
-	ConfigStore
 	UserStore
 	PublicKeyStore
 	RepoStore

server/db/fakedb/db.go 🔗

@@ -1,181 +1,176 @@
 package fakedb
 
 import (
-	"github.com/charmbracelet/soft-serve/proto"
 	"github.com/charmbracelet/soft-serve/server/db"
 	"github.com/charmbracelet/soft-serve/server/db/types"
 )
 
 var _ db.Store = &FakeDB{}
 
+// FakeDB is a fake database for testing.
 type FakeDB struct{}
 
-// Open implements db.Store
-func (*FakeDB) Open(name string) (proto.RepositoryService, error) {
-	return nil, nil
-}
-
-// GetConfig implements db.Store
+// GetConfig implements db.Store.
 func (*FakeDB) GetConfig() (*types.Config, error) {
 	return nil, nil
 }
 
-// SetConfigAllowKeyless implements db.Store
+// SetConfigAllowKeyless implements db.Store.
 func (*FakeDB) SetConfigAllowKeyless(bool) error {
 	return nil
 }
 
-// SetConfigAnonAccess implements db.Store
+// SetConfigAnonAccess implements db.Store.
 func (*FakeDB) SetConfigAnonAccess(string) error {
 	return nil
 }
 
-// SetConfigHost implements db.Store
+// SetConfigHost implements db.Store.
 func (*FakeDB) SetConfigHost(string) error {
 	return nil
 }
 
-// SetConfigName implements db.Store
+// SetConfigName implements db.Store.
 func (*FakeDB) SetConfigName(string) error {
 	return nil
 }
 
-// SetConfigPort implements db.Store
+// SetConfigPort implements db.Store.
 func (*FakeDB) SetConfigPort(int) error {
 	return nil
 }
 
-// AddUser implements db.Store
+// AddUser implements db.Store.
 func (*FakeDB) AddUser(name string, login string, email string, password string, isAdmin bool) error {
 	return nil
 }
 
-// CountUsers implements db.Store
+// CountUsers implements db.Store.
 func (*FakeDB) CountUsers() (int, error) {
 	return 0, nil
 }
 
-// DeleteUser implements db.Store
+// DeleteUser implements db.Store.
 func (*FakeDB) DeleteUser(int) error {
 	return nil
 }
 
-// GetUser implements db.Store
+// GetUser implements db.Store.
 func (*FakeDB) GetUser(int) (*types.User, error) {
 	return nil, nil
 }
 
-// GetUserByEmail implements db.Store
+// GetUserByEmail implements db.Store.
 func (*FakeDB) GetUserByEmail(string) (*types.User, error) {
 	return nil, nil
 }
 
-// GetUserByLogin implements db.Store
+// GetUserByLogin implements db.Store.
 func (*FakeDB) GetUserByLogin(string) (*types.User, error) {
 	return nil, nil
 }
 
-// GetUserByPublicKey implements db.Store
+// GetUserByPublicKey implements db.Store.
 func (*FakeDB) GetUserByPublicKey(string) (*types.User, error) {
 	return nil, nil
 }
 
-// SetUserAdmin implements db.Store
+// SetUserAdmin implements db.Store.
 func (*FakeDB) SetUserAdmin(*types.User, bool) error {
 	return nil
 }
 
-// SetUserEmail implements db.Store
+// SetUserEmail implements db.Store.
 func (*FakeDB) SetUserEmail(*types.User, string) error {
 	return nil
 }
 
-// SetUserLogin implements db.Store
+// SetUserLogin implements db.Store.
 func (*FakeDB) SetUserLogin(*types.User, string) error {
 	return nil
 }
 
-// SetUserName implements db.Store
+// SetUserName implements db.Store.
 func (*FakeDB) SetUserName(*types.User, string) error {
 	return nil
 }
 
-// SetUserPassword implements db.Store
+// SetUserPassword implements db.Store.
 func (*FakeDB) SetUserPassword(*types.User, string) error {
 	return nil
 }
 
-// AddUserPublicKey implements db.Store
+// AddUserPublicKey implements db.Store.
 func (*FakeDB) AddUserPublicKey(*types.User, string) error {
 	return nil
 }
 
-// DeleteUserPublicKey implements db.Store
+// DeleteUserPublicKey implements db.Store.
 func (*FakeDB) DeleteUserPublicKey(int) error {
 	return nil
 }
 
-// GetUserPublicKeys implements db.Store
+// GetUserPublicKeys implements db.Store.
 func (*FakeDB) GetUserPublicKeys(*types.User) ([]*types.PublicKey, error) {
 	return nil, nil
 }
 
-// AddRepo implements db.Store
+// AddRepo implements db.Store.
 func (*FakeDB) AddRepo(name string, projectName string, description string, isPrivate bool) error {
 	return nil
 }
 
-// DeleteRepo implements db.Store
+// DeleteRepo implements db.Store.
 func (*FakeDB) DeleteRepo(string) error {
 	return nil
 }
 
-// GetRepo implements db.Store
+// GetRepo implements db.Store.
 func (*FakeDB) GetRepo(string) (*types.Repo, error) {
 	return nil, nil
 }
 
-// SetRepoDescription implements db.Store
+// SetRepoDescription implements db.Store.
 func (*FakeDB) SetRepoDescription(string, string) error {
 	return nil
 }
 
-// SetRepoPrivate implements db.Store
+// SetRepoPrivate implements db.Store.
 func (*FakeDB) SetRepoPrivate(string, bool) error {
 	return nil
 }
 
-// SetRepoProjectName implements db.Store
+// SetRepoProjectName implements db.Store.
 func (*FakeDB) SetRepoProjectName(string, string) error {
 	return nil
 }
 
-// AddRepoCollab implements db.Store
+// AddRepoCollab implements db.Store.
 func (*FakeDB) AddRepoCollab(string, *types.User) error {
 	return nil
 }
 
-// DeleteRepoCollab implements db.Store
+// DeleteRepoCollab implements db.Store.
 func (*FakeDB) DeleteRepoCollab(int, int) error {
 	return nil
 }
 
-// ListRepoCollabs implements db.Store
+// ListRepoCollabs implements db.Store.
 func (*FakeDB) ListRepoCollabs(string) ([]*types.User, error) {
 	return nil, nil
 }
 
-// ListRepoPublicKeys implements db.Store
+// ListRepoPublicKeys implements db.Store.
 func (*FakeDB) ListRepoPublicKeys(string) ([]*types.PublicKey, error) {
 	return nil, nil
 }
 
-// Close implements db.Store
+// Close implements db.Store.
 func (*FakeDB) Close() error {
 	return nil
 }
 
-// CreateDB implements db.Store
+// CreateDB implements db.Store.
 func (*FakeDB) CreateDB() error {
 	return nil
 }

server/db/sqlite/repo.go 🔗

@@ -1,58 +0,0 @@
-package sqlite
-
-import (
-	"github.com/charmbracelet/soft-serve/proto"
-	"github.com/charmbracelet/soft-serve/server/db/types"
-)
-
-// Open opens a repository.
-func (d *Sqlite) Open(name string) (proto.RepositoryService, error) {
-	r, err := d.GetRepo(name)
-	if err != nil {
-		return nil, err
-	}
-	return &repository{
-		repo: r,
-		db:   d,
-	}, nil
-}
-
-type repository struct {
-	repo *types.Repo
-	db   *Sqlite
-}
-
-// Name returns the repository's name.
-func (r *repository) Name() string {
-	return r.repo.Name
-}
-
-// ProjectName returns the repository's project name.
-func (r *repository) ProjectName() string {
-	return r.repo.ProjectName
-}
-
-// SetProjectName sets the repository's project name.
-func (r *repository) SetProjectName(name string) error {
-	return r.db.SetRepoProjectName(r.repo.Name, name)
-}
-
-// Description returns the repository's description.
-func (r *repository) Description() string {
-	return r.repo.Description
-}
-
-// SetDescription sets the repository's description.
-func (r *repository) SetDescription(desc string) error {
-	return r.db.SetRepoDescription(r.repo.Name, desc)
-}
-
-// IsPrivate returns whether the repository is private.
-func (r *repository) IsPrivate() bool {
-	return r.repo.Private
-}
-
-// SetPrivate sets whether the repository is private.
-func (r *repository) SetPrivate(p bool) error {
-	return r.db.SetRepoPrivate(r.repo.Name, p)
-}

server/db/sqlite/sql.go 🔗

@@ -1,17 +1,6 @@
 package sqlite
 
 var (
-	sqlCreateConfigTable = `CREATE TABLE IF NOT EXISTS config (
-		id INTEGER PRIMARY KEY AUTOINCREMENT,
-		name TEXT NOT NULL,
-		host TEXT NOT NULL,
-		port INTEGER NOT NULL,
-		anon_access TEXT NOT NULL,
-		allow_keyless BOOLEAN NOT NULL,
-		created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
-		updated_at DATETIME NOT NULL
-	);`
-
 	sqlCreateUserTable = `CREATE TABLE IF NOT EXISTS user (
 		id INTEGER PRIMARY KEY AUTOINCREMENT,
 		name TEXT NOT NULL,
@@ -63,15 +52,6 @@ var (
 		ON UPDATE CASCADE
 	);`
 
-	// Config.
-	sqlInsertConfig        = `INSERT INTO config (name, host, port, anon_access, allow_keyless, updated_at) VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP);`
-	sqlSelectConfig        = `SELECT id, name, host, port, anon_access, allow_keyless, created_at, updated_at FROM config WHERE id = ?;`
-	sqlUpdateConfigName    = `UPDATE config SET name = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?;`
-	sqlUpdateConfigHost    = `UPDATE config SET host = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?;`
-	sqlUpdateConfigPort    = `UPDATE config SET port = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?;`
-	sqlUpdateConfigAnon    = `UPDATE config SET anon_access = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?;`
-	sqlUpdateConfigKeyless = `UPDATE config SET allow_keyless = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?;`
-
 	// User.
 	sqlInsertUser            = `INSERT INTO user (name, login, email, password, admin, updated_at) VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP);`
 	sqlDeleteUser            = `DELETE FROM user WHERE id = ?;`

server/db/sqlite/sqlite.go 🔗

@@ -50,9 +50,6 @@ func (d *Sqlite) Close() error {
 // CreateDB creates the database and tables.
 func (d *Sqlite) CreateDB() error {
 	return d.wrapTransaction(func(tx *sql.Tx) error {
-		if _, err := tx.Exec(sqlCreateConfigTable); err != nil {
-			return err
-		}
 		if _, err := tx.Exec(sqlCreateUserTable); err != nil {
 			return err
 		}
@@ -69,63 +66,6 @@ func (d *Sqlite) CreateDB() error {
 	})
 }
 
-const defaultConfigID = 1
-
-// GetConfig returns the server config.
-func (d *Sqlite) GetConfig() (*types.Config, error) {
-	var c types.Config
-	if err := d.wrapTransaction(func(tx *sql.Tx) error {
-		r := tx.QueryRow(sqlSelectConfig, defaultConfigID)
-		if err := r.Scan(&c.ID, &c.Name, &c.Host, &c.Port, &c.AnonAccess, &c.AllowKeyless, &c.CreatedAt, &c.UpdatedAt); err != nil {
-			return err
-		}
-		return nil
-	}); err != nil {
-		return nil, err
-	}
-	return &c, nil
-}
-
-// SetConfigName sets the server config name.
-func (d *Sqlite) SetConfigName(name string) error {
-	return d.wrapTransaction(func(tx *sql.Tx) error {
-		_, err := tx.Exec(sqlUpdateConfigName, name, defaultConfigID)
-		return err
-	})
-}
-
-// SetConfigHost sets the server config host.
-func (d *Sqlite) SetConfigHost(host string) error {
-	return d.wrapTransaction(func(tx *sql.Tx) error {
-		_, err := tx.Exec(sqlUpdateConfigHost, host, defaultConfigID)
-		return err
-	})
-}
-
-// SetConfigPort sets the server config port.
-func (d *Sqlite) SetConfigPort(port int) error {
-	return d.wrapTransaction(func(tx *sql.Tx) error {
-		_, err := tx.Exec(sqlUpdateConfigPort, port, defaultConfigID)
-		return err
-	})
-}
-
-// SetConfigAnonAccess sets the server config anon access.
-func (d *Sqlite) SetConfigAnonAccess(access string) error {
-	return d.wrapTransaction(func(tx *sql.Tx) error {
-		_, err := tx.Exec(sqlUpdateConfigAnon, access, defaultConfigID)
-		return err
-	})
-}
-
-// SetConfigAllowKeyless sets the server config allow keyless.
-func (d *Sqlite) SetConfigAllowKeyless(allow bool) error {
-	return d.wrapTransaction(func(tx *sql.Tx) error {
-		_, err := tx.Exec(sqlUpdateConfigKeyless, allow, defaultConfigID)
-		return err
-	})
-}
-
 // AddUser adds a new user.
 func (d *Sqlite) AddUser(name, login, email, password string, isAdmin bool) error {
 	var l *string
@@ -475,16 +415,20 @@ func (d *Sqlite) wrapTransaction(f func(tx *sql.Tx) error) error {
 	}
 	for {
 		err = f(tx)
-		if err != nil && !errors.Is(err, sql.ErrNoRows) {
-			serr, ok := err.(*sqlite.Error)
-			if ok {
-				switch serr.Code() {
-				case sqlitelib.SQLITE_BUSY:
-					continue
+		if err != nil {
+			switch {
+			case errors.Is(err, sql.ErrNoRows):
+			default:
+				serr, ok := err.(*sqlite.Error)
+				if ok {
+					switch serr.Code() {
+					case sqlitelib.SQLITE_BUSY:
+						continue
+					}
+					log.Printf("error in transaction: %d: %s", serr.Code(), serr)
+				} else {
+					log.Printf("error in transaction: %s", err)
 				}
-				log.Printf("error in transaction: %d: %s", serr.Code(), serr)
-			} else {
-				log.Printf("error in transaction: %s", err)
 			}
 			return err
 		}

server/file/file.go 🔗

@@ -1,108 +0,0 @@
-package file
-
-import (
-	"errors"
-	"os"
-	"path/filepath"
-	"strconv"
-	"strings"
-
-	"github.com/charmbracelet/soft-serve/git"
-	"github.com/charmbracelet/soft-serve/proto"
-)
-
-type key int
-
-const (
-	projectName key = iota
-	description
-	private
-)
-
-var keys = map[key]string{
-	projectName: "soft-serve.projectName",
-	description: "soft-serve.description",
-	private:     "soft-serve.private",
-}
-
-var _ proto.Provider = &File{}
-
-// File is a file-based repository provider.
-type File struct {
-	repoPath string
-}
-
-// New returns a new File provider.
-func New(repoPath string) *File {
-	f := &File{
-		repoPath: repoPath,
-	}
-	return f
-}
-
-// Open opens a new repository and returns a new FileRepo.
-func (f *File) Open(name string) (proto.RepositoryService, error) {
-	fp := filepath.Join(f.repoPath, name)
-	r, err := git.Open(fp)
-	if errors.Is(err, os.ErrNotExist) {
-		r, err = git.Open(fp + ".git")
-	}
-	if err != nil {
-		return nil, err
-	}
-	return &FileRepo{r}, nil
-}
-
-var _ proto.Repository = &FileRepo{}
-
-// FileRepo is a file-based repository.
-type FileRepo struct { // nolint:revive
-	repo *git.Repository
-}
-
-// Name returns the name of the repository.
-func (r *FileRepo) Name() string {
-	return strings.TrimSuffix(r.repo.Name(), ".git")
-}
-
-// ProjectName returns the project name of the repository.
-func (r *FileRepo) ProjectName() string {
-	pn, err := r.repo.Config(keys[projectName])
-	if err != nil {
-		return ""
-	}
-	return pn
-}
-
-// SetProjectName sets the project name of the repository.
-func (r *FileRepo) SetProjectName(name string) error {
-	return r.repo.SetConfig(keys[projectName], name)
-}
-
-// Description returns the description of the repository.
-func (r *FileRepo) Description() string {
-	desc, err := r.repo.Config(keys[description])
-	if err != nil {
-		return ""
-	}
-	return desc
-}
-
-// SetDescription sets the description of the repository.
-func (r *FileRepo) SetDescription(desc string) error {
-	return r.repo.SetConfig(keys[description], desc)
-}
-
-// IsPrivate returns whether the repository is private.
-func (r *FileRepo) IsPrivate() bool {
-	p, err := r.repo.Config(keys[private])
-	if err != nil {
-		return false
-	}
-	return p == "true"
-}
-
-// SetPrivate sets whether the repository is private.
-func (r *FileRepo) SetPrivate(p bool) error {
-	return r.repo.SetConfig(keys[private], strconv.FormatBool(p))
-}

server/git/daemon/daemon_test.go 🔗

@@ -28,7 +28,7 @@ func TestMain(m *testing.M) {
 		DataPath:   tmp,
 		AnonAccess: proto.ReadOnlyAccess,
 		Git: config.GitConfig{
-			// Reduce the max read timeout to 1 second so we can test the timeout.
+			// Reduce the max read timeout to 3 second so we can test the timeout.
 			IdleTimeout: 3,
 			// Reduce the max timeout to 100 second so we can test the timeout.
 			MaxTimeout: 100,

server/server.go 🔗

@@ -6,7 +6,6 @@ import (
 	"log"
 	"time"
 
-	appCfg "github.com/charmbracelet/soft-serve/config"
 	cm "github.com/charmbracelet/soft-serve/server/cmd"
 	"github.com/charmbracelet/soft-serve/server/config"
 	"github.com/charmbracelet/soft-serve/server/git/daemon"
@@ -25,7 +24,6 @@ type Server struct {
 	SSHServer *ssh.Server
 	GitServer *daemon.Daemon
 	Config    *config.Config
-	config    *appCfg.Config
 }
 
 // NewServer returns a new *ssh.Server configured to serve Soft Serve. The SSH
@@ -35,19 +33,15 @@ type Server struct {
 // publicly writable until configured otherwise by cloning the `config` repo.
 func NewServer(cfg *config.Config) *Server {
 	s := &Server{Config: cfg}
-	ac, err := appCfg.NewConfig(cfg)
-	if err != nil {
-		log.Fatal(err)
-	}
 	mw := []wish.Middleware{
 		rm.MiddlewareWithLogger(
 			cfg.ErrorLog,
 			// BubbleTea middleware.
-			bm.MiddlewareWithProgramHandler(SessionHandler(ac), termenv.ANSI256),
+			bm.MiddlewareWithProgramHandler(SessionHandler(cfg), termenv.ANSI256),
 			// Command middleware must come after the git middleware.
 			cm.Middleware(cfg),
 			// Git middleware.
-			gm.Middleware(cfg.RepoPath(), ac),
+			gm.Middleware(cfg.RepoPath(), cfg),
 			// Logging middleware must be last to be executed first.
 			lm.Middleware(),
 		),
@@ -86,7 +80,8 @@ func NewServer(cfg *config.Config) *Server {
 
 // Reload reloads the server configuration.
 func (s *Server) Reload() error {
-	return s.config.Reload()
+	return nil
+	// return s.config.Reload()
 }
 
 // Start starts the SSH server.

server/session.go 🔗

@@ -5,9 +5,9 @@ import (
 
 	"github.com/aymanbagabas/go-osc52"
 	tea "github.com/charmbracelet/bubbletea"
-	appCfg "github.com/charmbracelet/soft-serve/config"
 	"github.com/charmbracelet/soft-serve/proto"
 	cm "github.com/charmbracelet/soft-serve/server/cmd"
+	"github.com/charmbracelet/soft-serve/server/config"
 	"github.com/charmbracelet/soft-serve/ui"
 	"github.com/charmbracelet/soft-serve/ui/common"
 	"github.com/charmbracelet/soft-serve/ui/keymap"
@@ -19,7 +19,7 @@ import (
 )
 
 // SessionHandler is the soft-serve bubbletea ssh session handler.
-func SessionHandler(ac *appCfg.Config) bm.ProgramHandler {
+func SessionHandler(cfg *config.Config) bm.ProgramHandler {
 	return func(s ssh.Session) *tea.Program {
 		pty, _, active := s.Pty()
 		if !active {
@@ -29,14 +29,14 @@ func SessionHandler(ac *appCfg.Config) bm.ProgramHandler {
 		initialRepo := ""
 		if len(cmd) == 1 {
 			initialRepo = cmd[0]
-			auth := ac.AuthRepo(initialRepo, s.PublicKey())
+			auth := cfg.AuthRepo(initialRepo, s.PublicKey())
 			if auth < proto.ReadOnlyAccess {
 				wish.Fatalln(s, cm.ErrUnauthorized)
 				return nil
 			}
 		}
-		if ac.Cfg.Callbacks != nil {
-			ac.Cfg.Callbacks.Tui("new session")
+		if cfg.Callbacks != nil {
+			cfg.Callbacks.Tui("new session")
 		}
 		envs := s.Environ()
 		envs = append(envs, fmt.Sprintf("TERM=%s", pty.Term))
@@ -50,7 +50,7 @@ func SessionHandler(ac *appCfg.Config) bm.ProgramHandler {
 			Zone:   zone.New(),
 		}
 		m := ui.New(
-			ac,
+			cfg,
 			s,
 			c,
 			initialRepo,

server/session_test.go 🔗

@@ -8,7 +8,7 @@ import (
 	"testing"
 	"time"
 
-	appCfg "github.com/charmbracelet/soft-serve/config"
+	"github.com/charmbracelet/soft-serve/proto"
 	cm "github.com/charmbracelet/soft-serve/server/cmd"
 	"github.com/charmbracelet/soft-serve/server/config"
 	bm "github.com/charmbracelet/wish/bubbletea"
@@ -53,9 +53,8 @@ func TestSession(t *testing.T) {
 }
 
 func setup(tb testing.TB) *gossh.Session {
-	is := is.New(tb)
 	tb.Helper()
-	ac, err := appCfg.NewConfig(&config.Config{
+	cfg := &config.Config{
 		Host:     "",
 		SSH:      config.SSHConfig{Port: randomPort()},
 		Git:      config.GitConfig{Port: 9418},
@@ -63,11 +62,10 @@ func setup(tb testing.TB) *gossh.Session {
 		InitialAdminKeys: []string{
 			"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMJlb/qf2B2kMNdBxfpCQqI2ctPcsOkdZGVh5zTRhKtH",
 		},
-	})
-	ac.AnonAccess = "read-only"
-	is.NoErr(err)
+		AnonAccess: proto.ReadOnlyAccess,
+	}
 	return testsession.New(tb, &ssh.Server{
-		Handler: bm.MiddlewareWithProgramHandler(SessionHandler(ac), termenv.ANSI256)(func(s ssh.Session) {
+		Handler: bm.MiddlewareWithProgramHandler(SessionHandler(cfg), termenv.ANSI256)(func(s ssh.Session) {
 			_, _, active := s.Pty()
 			tb.Logf("PTY active %v", active)
 			tb.Log(s.Command())