lens @ 024da3e546e98cbaeea5f7bc86af12b671996f41

ref: Refactor how repository is define

To make things easier and reduce the number of package I'll move all
repository to one folder, starting with auth and user repository.

Also implements all testing on top of the repository interface with a im
memory implementation. This will later make mescling unit and
integration easier.
diff --git a/pkg/components/auth/controller.go b/pkg/components/auth/controller.go
index a33d9b37458ee677835aff5a26543fe84a3ac6c6..0b08fccedac11e0cc5970a746e46432b2390134f 100644
--- a/pkg/components/auth/controller.go
+++ b/pkg/components/auth/controller.go
@@ -6,35 +6,35 @@
 	"golang.org/x/crypto/bcrypt"
 
 	"git.sr.ht/~gabrielgio/img/pkg/components"
-	"git.sr.ht/~gabrielgio/img/pkg/components/user"
+	"git.sr.ht/~gabrielgio/img/pkg/database/repository"
 	"git.sr.ht/~gabrielgio/img/pkg/ext"
 )
 
 type Controller struct {
-	repository     Repository
-	userRepository user.Repository
+	authRepository repository.AuthRepository
+	userRepository repository.UserRepository
 	key            []byte
 }
 
 func NewController(
-	repository Repository,
-	userRepository user.Repository,
+	authRepository repository.AuthRepository,
+	userRepository repository.UserRepository,
 	key []byte,
 ) *Controller {
 	return &Controller{
-		repository:     repository,
+		authRepository: authRepository,
 		userRepository: userRepository,
 		key:            key,
 	}
 }
 
 func (c *Controller) Login(ctx context.Context, username, password []byte) ([]byte, error) {
-	id, err := c.repository.GetIDByUsername(ctx, string(username))
+	id, err := c.authRepository.GetIDByUsername(ctx, string(username))
 	if err != nil {
 		return nil, err
 	}
 
-	hashedPassword, err := c.repository.GetPassword(ctx, id)
+	hashedPassword, err := c.authRepository.GetPassword(ctx, id)
 	if err != nil {
 		return nil, err
 	}
@@ -67,7 +67,7 @@ 	if err != nil {
 		return err
 	}
 
-	_, err = c.userRepository.Create(ctx, &user.CreateUser{
+	_, err = c.userRepository.Create(ctx, &repository.CreateUser{
 		Username: string(username),
 		Password: hash,
 		Path:     string(path),
diff --git a/pkg/components/auth/controller_test.go b/pkg/components/auth/controller_test.go
index 50bf69b08d8dea4e8d4d5e3dbf4e706701984139..b1ca065d481107931ccca4ee3cc48d581472c5f5 100644
--- a/pkg/components/auth/controller_test.go
+++ b/pkg/components/auth/controller_test.go
@@ -4,12 +4,9 @@ package auth
 
 import (
 	"context"
-	"errors"
 	"testing"
 
-	"github.com/samber/lo"
-
-	"git.sr.ht/~gabrielgio/img/pkg/components/user"
+	"git.sr.ht/~gabrielgio/img/pkg/database/repository"
 	"git.sr.ht/~gabrielgio/img/pkg/ext"
 	"git.sr.ht/~gabrielgio/img/pkg/testkit"
 )
@@ -17,41 +14,23 @@
 type (
 	scene struct {
 		ctx            context.Context
-		mockRepository *MockAuthRepository
-		controller     Controller
-	}
-
-	mockUser struct {
-		id       uint
-		username string
-		password []byte
-	}
-
-	MockAuthRepository struct {
-		index uint
-		users []*mockUser
-		err   error
-	}
-
-	MockUserRepository struct {
-		index uint
-		users []*mockUser
-		err   error
+		authRepository repository.AuthRepository
+		userRepository repository.UserRepository
+		controller     *Controller
 	}
 )
 
 var (
-	_   Repository = &MockAuthRepository{}
-	key            = []byte("6368616e676520746869732070617373")
+	key = []byte("6368616e676520746869732070617373")
 )
 
 func setUp() *scene {
-	mockAuthRepository := &MockAuthRepository{}
-	mockUserRepository := &MockUserRepository{}
+	userRepository := NewUserRepository()
 	return &scene{
 		ctx:            context.Background(),
-		mockRepository: mockAuthRepository,
-		controller:     *NewController(mockAuthRepository, mockUserRepository, key),
+		authRepository: userRepository,
+		userRepository: userRepository,
+		controller:     NewController(userRepository, userRepository, key),
 	}
 }
 
@@ -64,7 +43,7 @@ 	}{
 		{
 			name:     "Normal register",
 			username: "username",
-			password: []byte("password"),
+			password: []byte("this is an password"),
 		},
 	}
 
@@ -75,9 +54,10 @@
 			err := scene.controller.InitialRegister(scene.ctx, []byte(tc.username), tc.password, []byte("/"))
 			testkit.TestFatalError(t, "Register", err)
 
-			userID := scene.mockRepository.GetLastId()
+			users, err := scene.userRepository.List(scene.ctx)
+			userID := users[0].ID
 
-			user, err := scene.mockRepository.Get(scene.ctx, userID)
+			user, err := scene.userRepository.Get(scene.ctx, userID)
 			testkit.TestFatalError(t, "Get", err)
 			testkit.TestValue(t, "Register", tc.username, user.Username)
 
@@ -93,122 +73,6 @@ 		})
 	}
 }
 
-func toUser(m *mockUser, _ int) *user.User {
-	return &user.User{
-		ID:       m.id,
-		Username: m.username,
-	}
-}
-
-func (m *MockAuthRepository) GetLastId() uint {
-	return m.index
-}
-
-func (m *MockAuthRepository) List(ctx context.Context) ([]*user.User, error) {
-	if m.err != nil {
-		return nil, m.err
-	}
-
-	return lo.Map(m.users, toUser), nil
-}
-
-func (m *MockAuthRepository) Get(ctx context.Context, id uint) (*user.User, error) {
-	if m.err != nil {
-		return nil, m.err
-	}
-
-	for _, m := range m.users {
-		if m.id == id {
-			return toUser(m, 0), nil
-		}
-	}
-	return nil, errors.New("Item not found")
-}
-
-func (m *MockAuthRepository) GetIDByUsername(ctx context.Context, username string) (uint, error) {
-	if m.err != nil {
-		return 0, m.err
-	}
-
-	for _, m := range m.users {
-		if m.username == username {
-			return m.id, nil
-		}
-	}
-	return 0, errors.New("Item not found")
-}
-
-func (m *MockAuthRepository) GetPassword(ctx context.Context, id uint) ([]byte, error) {
-	if m.err != nil {
-		return nil, m.err
-	}
-
-	for _, m := range m.users {
-		if m.id == id {
-			return m.password, nil
-		}
-	}
-	return nil, errors.New("Item not found")
-}
-
-func (m *MockAuthRepository) Create(ctx context.Context, createUser *user.CreateUser) (uint, error) {
-	if m.err != nil {
-		return 0, m.err
-	}
-
-	m.index++
-
-	m.users = append(m.users, &mockUser{
-		id:       m.index,
-		username: createUser.Username,
-		password: createUser.Password,
-	})
-
-	return m.index, nil
-}
-
-func (m *MockAuthRepository) Update(ctx context.Context, id uint, update *user.UpdateUser) error {
-	if m.err != nil {
-		return m.err
-	}
-
-	for _, m := range m.users {
-		if m.id == id {
-			m.username = update.Username
-		}
-	}
-	return nil
-}
-
 func remove[T any](slice []T, s int) []T {
 	return append(slice[:s], slice[s+1:]...)
 }
-
-func (r *MockAuthRepository) Delete(ctx context.Context, id uint) error {
-	if r.err != nil {
-		return r.err
-	}
-
-	for i, m := range r.users {
-		if m.id == id {
-			r.users = remove(r.users, i)
-		}
-	}
-	return nil
-}
-
-func (m *MockUserRepository) List(ctx context.Context) ([]*user.User, error) {
-	panic("not implemented") // TODO: Implement
-}
-
-func (m *MockUserRepository) Create(ctx context.Context, createUser *user.CreateUser) (uint, error) {
-	panic("not implemented") // TODO: Implement
-}
-
-func (m *MockUserRepository) Update(ctx context.Context, id uint, updateUser *user.UpdateUser) error {
-	panic("not implemented") // TODO: Implement
-}
-
-func (m *MockUserRepository) Any(ctx context.Context) (bool, error) {
-	panic("not implemented") // TODO: Implement
-}
diff --git a/pkg/components/auth/mock_test.go b/pkg/components/auth/mock_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..885f64316d474bdf213965a55660ee94fd6bd5b2
--- /dev/null
+++ b/pkg/components/auth/mock_test.go
@@ -0,0 +1,121 @@
+//go:build unit
+
+package auth
+
+import (
+	"context"
+	"errors"
+
+	"git.sr.ht/~gabrielgio/img/pkg/database/repository"
+)
+
+type (
+	User struct {
+		ID       uint
+		Username string
+		Name     string
+		Password []byte
+		IsAdmin  bool
+		Path     string
+	}
+
+	Users map[uint]*User
+
+	UserRepository struct {
+		icount uint
+		users  Users
+	}
+)
+
+var _ repository.UserRepository = &UserRepository{}
+var _ repository.AuthRepository = &UserRepository{}
+
+func NewUserRepository() *UserRepository {
+	return &UserRepository{
+		users: make(map[uint]*User),
+	}
+}
+
+func (u *User) ToModel() *repository.User {
+	return &repository.User{
+		ID:       u.ID,
+		Username: u.Username,
+		Name:     u.Name,
+		IsAdmin:  u.IsAdmin,
+		Path:     u.Path,
+	}
+}
+
+func (u Users) ToModels() []*repository.User {
+	users := make([]*repository.User, 0, len(u))
+	for _, i := range u {
+		users = append(users, i.ToModel())
+	}
+	return users
+}
+
+func (u *UserRepository) Get(ctx context.Context, id uint) (*repository.User, error) {
+	if user, ok := u.users[id]; ok {
+		return user.ToModel(), nil
+	}
+
+	return nil, errors.New("Not Found")
+}
+
+func (u *UserRepository) List(_ context.Context) ([]*repository.User, error) {
+	return u.users.ToModels(), nil
+}
+
+func (u *UserRepository) Create(_ context.Context, createUser *repository.CreateUser) (uint, error) {
+	id := u.furtherID()
+	u.users[id] = &User{
+		ID:       id,
+		Name:     createUser.Name,
+		Username: createUser.Username,
+		Path:     createUser.Path,
+		Password: createUser.Password,
+	}
+	return id, nil
+}
+
+func (u *UserRepository) Update(_ context.Context, id uint, updateUser *repository.UpdateUser) error {
+	user, ok := u.users[id]
+	if !ok {
+		return errors.New("Invalid ID")
+	}
+
+	user.Name = updateUser.Name
+	user.Username = updateUser.Username
+	if updateUser.Password != "" {
+		user.Password = []byte(updateUser.Password)
+	}
+
+	return nil
+}
+
+func (u *UserRepository) Any(_ context.Context) (bool, error) {
+	return len(u.users) > 0, nil
+}
+
+func (u *UserRepository) GetIDByUsername(ctx context.Context, username string) (uint, error) {
+	for id, u := range u.users {
+		if u.Username == username {
+			return id, nil
+		}
+	}
+
+	return 0, errors.New("Not Found")
+}
+
+func (u *UserRepository) GetPassword(ctx context.Context, id uint) ([]byte, error) {
+	if user, ok := u.users[id]; ok {
+		return []byte(user.Password), nil
+	}
+
+	return nil, errors.New("Not Found")
+}
+
+func (u *UserRepository) furtherID() uint {
+	u.icount++
+	return u.icount
+}
diff --git a/pkg/components/auth/model.go b/pkg/database/repository/auth.go
rename from pkg/components/auth/model.go
rename to pkg/database/repository/auth.go
index dd6ce5036b91127e13e37c4e57b7baa13b486a1a..b3194956c9c9b0dcefda0d4cc64c297f900479f8 100644
--- a/pkg/components/auth/model.go
+++ b/pkg/database/repository/auth.go
@@ -1,9 +1,9 @@
-package auth
+package repository
 
 import "context"
 
 type (
-	Repository interface {
+	AuthRepository interface {
 		GetIDByUsername(ctx context.Context, username string) (uint, error)
 		GetPassword(ctx context.Context, id uint) ([]byte, error)
 	}
diff --git a/pkg/components/user/model.go b/pkg/database/repository/user.go
rename from pkg/components/user/model.go
rename to pkg/database/repository/user.go
index 0ff6d0ab9a5b77cbfef5fab9f5b717d6e066119e..f8bd71921567ddf5ed5bdd3ed15e4e526f3bad64 100644
--- a/pkg/components/user/model.go
+++ b/pkg/database/repository/user.go
@@ -1,4 +1,4 @@
-package user
+package repository
 
 import "context"
 
@@ -14,7 +14,7 @@
 	UpdateUser struct {
 		Username string
 		Name     string
-		Password *string
+		Password string
 	}
 
 	CreateUser struct {
@@ -25,7 +25,8 @@ 		IsAdmin  bool
 		Path     string
 	}
 
-	Repository interface {
+	UserRepository interface {
+		Get(ctx context.Context, id uint) (*User, error)
 		List(ctx context.Context) ([]*User, error)
 		Create(ctx context.Context, createUser *CreateUser) (uint, error)
 		Update(ctx context.Context, id uint, updateUser *UpdateUser) error
diff --git a/pkg/database/sql/user.go b/pkg/database/sql/user.go
index a0884f4f6e1db95c98c490fbb6ca6e10ac214de4..479a9c59d36cb407f4693b663c409eb71701ee6b 100644
--- a/pkg/database/sql/user.go
+++ b/pkg/database/sql/user.go
@@ -6,8 +6,7 @@
 	"golang.org/x/crypto/bcrypt"
 	"gorm.io/gorm"
 
-	"git.sr.ht/~gabrielgio/img/pkg/components/auth"
-	"git.sr.ht/~gabrielgio/img/pkg/components/user"
+	"git.sr.ht/~gabrielgio/img/pkg/database/repository"
 )
 
 type (
@@ -27,8 +26,8 @@ 		db *gorm.DB
 	}
 )
 
-var _ auth.Repository = &UserRepository{}
-var _ user.Repository = &UserRepository{}
+var _ repository.UserRepository = &UserRepository{}
+var _ repository.AuthRepository = &UserRepository{}
 
 func NewUserRepository(db *gorm.DB) *UserRepository {
 	return &UserRepository{
@@ -36,8 +35,8 @@ 		db: db,
 	}
 }
 
-func (self *User) ToModel() *user.User {
-	return &user.User{
+func (self *User) ToModel() *repository.User {
+	return &repository.User{
 		ID:       self.Model.ID,
 		Name:     self.Name,
 		Username: self.Username,
@@ -46,7 +45,7 @@ 		IsAdmin:  self.IsAdmin,
 	}
 }
 
-func (self Users) ToModel() (users []*user.User) {
+func (self Users) ToModel() (users []*repository.User) {
 	for _, user := range self {
 		users = append(users, user.ToModel())
 	}
@@ -75,7 +74,7 @@ 		})
 	}
 }
 
-func (self *UserRepository) List(ctx context.Context) ([]*user.User, error) {
+func (self *UserRepository) List(ctx context.Context) ([]*repository.User, error) {
 	users := Users{}
 	result := self.db.
 		WithContext(ctx).
@@ -88,8 +87,8 @@
 	return users.ToModel(), nil
 }
 
-func (self *UserRepository) Get(ctx context.Context, id uint) (*user.User, error) {
-	var user = &user.User{ID: id}
+func (self *UserRepository) Get(ctx context.Context, id uint) (*repository.User, error) {
+	var user = &repository.User{ID: id}
 	result := self.db.
 		WithContext(ctx).
 		First(user)
@@ -137,7 +136,7 @@
 	return userPassword.Password, nil
 }
 
-func (self *UserRepository) Create(ctx context.Context, createUser *user.CreateUser) (uint, error) {
+func (self *UserRepository) Create(ctx context.Context, createUser *repository.CreateUser) (uint, error) {
 	user := &User{
 		Username: createUser.Username,
 		Name:     createUser.Name,
@@ -154,7 +153,7 @@
 	return user.Model.ID, nil
 }
 
-func (self *UserRepository) Update(ctx context.Context, id uint, update *user.UpdateUser) error {
+func (self *UserRepository) Update(ctx context.Context, id uint, update *repository.UpdateUser) error {
 	user := &User{
 		Model: gorm.Model{
 			ID: id,
diff --git a/pkg/ext/middleware.go b/pkg/ext/middleware.go
index bc23b9087ca3fab75190d884ec56b4d6472297f0..d255c6d70f398251ed0e7a7340bb9169fad7bf9a 100644
--- a/pkg/ext/middleware.go
+++ b/pkg/ext/middleware.go
@@ -7,7 +7,7 @@
 	"github.com/sirupsen/logrus"
 	"github.com/valyala/fasthttp"
 
-	"git.sr.ht/~gabrielgio/img/pkg/components/user"
+	"git.sr.ht/~gabrielgio/img/pkg/database/repository"
 )
 
 func HTML(next fasthttp.RequestHandler) fasthttp.RequestHandler {
@@ -91,10 +91,10 @@ 	}
 }
 
 type InitialSetupMiddleware struct {
-	userRepository user.Repository
+	userRepository repository.UserRepository
 }
 
-func NewInitialSetupMiddleware(userRepository user.Repository) *InitialSetupMiddleware {
+func NewInitialSetupMiddleware(userRepository repository.UserRepository) *InitialSetupMiddleware {
 	return &InitialSetupMiddleware{
 		userRepository: userRepository,
 	}
diff --git a/pkg/testkit/testkit.go b/pkg/testkit/testkit.go
index 526e1b3b4ef1003bc6f35969befb9d3dc4fb24e7..3cc4afd4fa9f0ae6330b5aed8f1db23cdb7070c8 100644
--- a/pkg/testkit/testkit.go
+++ b/pkg/testkit/testkit.go
@@ -9,18 +9,21 @@ 	"github.com/google/go-cmp/cmp"
 )
 
 func TestValue[T any](t *testing.T, method string, want, got T) {
+	t.Helper()
 	if diff := cmp.Diff(want, got); diff != "" {
 		t.Errorf("%s() mismatch (-want +got):\n%s", method, diff)
 	}
 }
 
 func TestFatalError(t *testing.T, method string, err error) {
+	t.Helper()
 	if err != nil {
 		t.Fatalf("%s() fatal error : %+v", method, err)
 	}
 }
 
 func TestError(t *testing.T, method string, want, got error) {
+	t.Helper()
 	if !equalError(want, got) {
 		t.Errorf("%s() err mismatch want: %+v got %+v", method, want, got)
 	}
diff --git a/pkg/view/media.go b/pkg/view/media.go
index 22f950d8050cba87ad40c33a420cb80e29625dc0..66e302013f11217f069046868bc5c707b9a87cf9 100644
--- a/pkg/view/media.go
+++ b/pkg/view/media.go
@@ -89,7 +89,7 @@ 		return err
 	}
 
 	ctx.Response.Header.SetContentType(media.MIMEType)
-	ctx.SendFile(media.Path)
+	fasthttp.ServeFileUncompressed(ctx, media.Path)
 	return nil
 }
 
diff --git a/pkg/view/settings.go b/pkg/view/settings.go
index e5acb1bbc57cfcb0325646114b7e95342e21c53a..954cc986086e6ba0d9429c32c800d9c91fbe6b90 100644
--- a/pkg/view/settings.go
+++ b/pkg/view/settings.go
@@ -5,7 +5,7 @@ 	"github.com/valyala/fasthttp"
 
 	"git.sr.ht/~gabrielgio/img"
 	"git.sr.ht/~gabrielgio/img/pkg/components/settings"
-	"git.sr.ht/~gabrielgio/img/pkg/components/user"
+	"git.sr.ht/~gabrielgio/img/pkg/database/repository"
 	"git.sr.ht/~gabrielgio/img/pkg/ext"
 )
 
@@ -13,18 +13,18 @@ type (
 	SettingsView struct {
 		// there is not need to create a controller for this
 		settingsRepository settings.Repository
-		userRepository     user.Repository
+		userRepository     repository.UserRepository
 	}
 
 	SettingsPage struct {
 		Settings *settings.Settings
-		Users    []*user.User
+		Users    []*repository.User
 	}
 )
 
 func NewSettingsView(
 	settingsRespository settings.Repository,
-	userRepository user.Repository,
+	userRepository repository.UserRepository,
 ) *SettingsView {
 	return &SettingsView{
 		settingsRepository: settingsRespository,