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.
  1diff --git a/pkg/components/auth/controller.go b/pkg/components/auth/controller.go
  2index a33d9b37458ee677835aff5a26543fe84a3ac6c6..0b08fccedac11e0cc5970a746e46432b2390134f 100644
  3--- a/pkg/components/auth/controller.go
  4+++ b/pkg/components/auth/controller.go
  5@@ -6,35 +6,35 @@
  6 	"golang.org/x/crypto/bcrypt"
  7 
  8 	"git.sr.ht/~gabrielgio/img/pkg/components"
  9-	"git.sr.ht/~gabrielgio/img/pkg/components/user"
 10+	"git.sr.ht/~gabrielgio/img/pkg/database/repository"
 11 	"git.sr.ht/~gabrielgio/img/pkg/ext"
 12 )
 13 
 14 type Controller struct {
 15-	repository     Repository
 16-	userRepository user.Repository
 17+	authRepository repository.AuthRepository
 18+	userRepository repository.UserRepository
 19 	key            []byte
 20 }
 21 
 22 func NewController(
 23-	repository Repository,
 24-	userRepository user.Repository,
 25+	authRepository repository.AuthRepository,
 26+	userRepository repository.UserRepository,
 27 	key []byte,
 28 ) *Controller {
 29 	return &Controller{
 30-		repository:     repository,
 31+		authRepository: authRepository,
 32 		userRepository: userRepository,
 33 		key:            key,
 34 	}
 35 }
 36 
 37 func (c *Controller) Login(ctx context.Context, username, password []byte) ([]byte, error) {
 38-	id, err := c.repository.GetIDByUsername(ctx, string(username))
 39+	id, err := c.authRepository.GetIDByUsername(ctx, string(username))
 40 	if err != nil {
 41 		return nil, err
 42 	}
 43 
 44-	hashedPassword, err := c.repository.GetPassword(ctx, id)
 45+	hashedPassword, err := c.authRepository.GetPassword(ctx, id)
 46 	if err != nil {
 47 		return nil, err
 48 	}
 49@@ -67,7 +67,7 @@ 	if err != nil {
 50 		return err
 51 	}
 52 
 53-	_, err = c.userRepository.Create(ctx, &user.CreateUser{
 54+	_, err = c.userRepository.Create(ctx, &repository.CreateUser{
 55 		Username: string(username),
 56 		Password: hash,
 57 		Path:     string(path),
 58diff --git a/pkg/components/auth/controller_test.go b/pkg/components/auth/controller_test.go
 59index 50bf69b08d8dea4e8d4d5e3dbf4e706701984139..b1ca065d481107931ccca4ee3cc48d581472c5f5 100644
 60--- a/pkg/components/auth/controller_test.go
 61+++ b/pkg/components/auth/controller_test.go
 62@@ -4,12 +4,9 @@ package auth
 63 
 64 import (
 65 	"context"
 66-	"errors"
 67 	"testing"
 68 
 69-	"github.com/samber/lo"
 70-
 71-	"git.sr.ht/~gabrielgio/img/pkg/components/user"
 72+	"git.sr.ht/~gabrielgio/img/pkg/database/repository"
 73 	"git.sr.ht/~gabrielgio/img/pkg/ext"
 74 	"git.sr.ht/~gabrielgio/img/pkg/testkit"
 75 )
 76@@ -17,41 +14,23 @@
 77 type (
 78 	scene struct {
 79 		ctx            context.Context
 80-		mockRepository *MockAuthRepository
 81-		controller     Controller
 82-	}
 83-
 84-	mockUser struct {
 85-		id       uint
 86-		username string
 87-		password []byte
 88-	}
 89-
 90-	MockAuthRepository struct {
 91-		index uint
 92-		users []*mockUser
 93-		err   error
 94-	}
 95-
 96-	MockUserRepository struct {
 97-		index uint
 98-		users []*mockUser
 99-		err   error
100+		authRepository repository.AuthRepository
101+		userRepository repository.UserRepository
102+		controller     *Controller
103 	}
104 )
105 
106 var (
107-	_   Repository = &MockAuthRepository{}
108-	key            = []byte("6368616e676520746869732070617373")
109+	key = []byte("6368616e676520746869732070617373")
110 )
111 
112 func setUp() *scene {
113-	mockAuthRepository := &MockAuthRepository{}
114-	mockUserRepository := &MockUserRepository{}
115+	userRepository := NewUserRepository()
116 	return &scene{
117 		ctx:            context.Background(),
118-		mockRepository: mockAuthRepository,
119-		controller:     *NewController(mockAuthRepository, mockUserRepository, key),
120+		authRepository: userRepository,
121+		userRepository: userRepository,
122+		controller:     NewController(userRepository, userRepository, key),
123 	}
124 }
125 
126@@ -64,7 +43,7 @@ 	}{
127 		{
128 			name:     "Normal register",
129 			username: "username",
130-			password: []byte("password"),
131+			password: []byte("this is an password"),
132 		},
133 	}
134 
135@@ -75,9 +54,10 @@
136 			err := scene.controller.InitialRegister(scene.ctx, []byte(tc.username), tc.password, []byte("/"))
137 			testkit.TestFatalError(t, "Register", err)
138 
139-			userID := scene.mockRepository.GetLastId()
140+			users, err := scene.userRepository.List(scene.ctx)
141+			userID := users[0].ID
142 
143-			user, err := scene.mockRepository.Get(scene.ctx, userID)
144+			user, err := scene.userRepository.Get(scene.ctx, userID)
145 			testkit.TestFatalError(t, "Get", err)
146 			testkit.TestValue(t, "Register", tc.username, user.Username)
147 
148@@ -93,122 +73,6 @@ 		})
149 	}
150 }
151 
152-func toUser(m *mockUser, _ int) *user.User {
153-	return &user.User{
154-		ID:       m.id,
155-		Username: m.username,
156-	}
157-}
158-
159-func (m *MockAuthRepository) GetLastId() uint {
160-	return m.index
161-}
162-
163-func (m *MockAuthRepository) List(ctx context.Context) ([]*user.User, error) {
164-	if m.err != nil {
165-		return nil, m.err
166-	}
167-
168-	return lo.Map(m.users, toUser), nil
169-}
170-
171-func (m *MockAuthRepository) Get(ctx context.Context, id uint) (*user.User, error) {
172-	if m.err != nil {
173-		return nil, m.err
174-	}
175-
176-	for _, m := range m.users {
177-		if m.id == id {
178-			return toUser(m, 0), nil
179-		}
180-	}
181-	return nil, errors.New("Item not found")
182-}
183-
184-func (m *MockAuthRepository) GetIDByUsername(ctx context.Context, username string) (uint, error) {
185-	if m.err != nil {
186-		return 0, m.err
187-	}
188-
189-	for _, m := range m.users {
190-		if m.username == username {
191-			return m.id, nil
192-		}
193-	}
194-	return 0, errors.New("Item not found")
195-}
196-
197-func (m *MockAuthRepository) GetPassword(ctx context.Context, id uint) ([]byte, error) {
198-	if m.err != nil {
199-		return nil, m.err
200-	}
201-
202-	for _, m := range m.users {
203-		if m.id == id {
204-			return m.password, nil
205-		}
206-	}
207-	return nil, errors.New("Item not found")
208-}
209-
210-func (m *MockAuthRepository) Create(ctx context.Context, createUser *user.CreateUser) (uint, error) {
211-	if m.err != nil {
212-		return 0, m.err
213-	}
214-
215-	m.index++
216-
217-	m.users = append(m.users, &mockUser{
218-		id:       m.index,
219-		username: createUser.Username,
220-		password: createUser.Password,
221-	})
222-
223-	return m.index, nil
224-}
225-
226-func (m *MockAuthRepository) Update(ctx context.Context, id uint, update *user.UpdateUser) error {
227-	if m.err != nil {
228-		return m.err
229-	}
230-
231-	for _, m := range m.users {
232-		if m.id == id {
233-			m.username = update.Username
234-		}
235-	}
236-	return nil
237-}
238-
239 func remove[T any](slice []T, s int) []T {
240 	return append(slice[:s], slice[s+1:]...)
241 }
242-
243-func (r *MockAuthRepository) Delete(ctx context.Context, id uint) error {
244-	if r.err != nil {
245-		return r.err
246-	}
247-
248-	for i, m := range r.users {
249-		if m.id == id {
250-			r.users = remove(r.users, i)
251-		}
252-	}
253-	return nil
254-}
255-
256-func (m *MockUserRepository) List(ctx context.Context) ([]*user.User, error) {
257-	panic("not implemented") // TODO: Implement
258-}
259-
260-func (m *MockUserRepository) Create(ctx context.Context, createUser *user.CreateUser) (uint, error) {
261-	panic("not implemented") // TODO: Implement
262-}
263-
264-func (m *MockUserRepository) Update(ctx context.Context, id uint, updateUser *user.UpdateUser) error {
265-	panic("not implemented") // TODO: Implement
266-}
267-
268-func (m *MockUserRepository) Any(ctx context.Context) (bool, error) {
269-	panic("not implemented") // TODO: Implement
270-}
271diff --git a/pkg/components/auth/mock_test.go b/pkg/components/auth/mock_test.go
272new file mode 100644
273index 0000000000000000000000000000000000000000..885f64316d474bdf213965a55660ee94fd6bd5b2
274--- /dev/null
275+++ b/pkg/components/auth/mock_test.go
276@@ -0,0 +1,121 @@
277+//go:build unit
278+
279+package auth
280+
281+import (
282+	"context"
283+	"errors"
284+
285+	"git.sr.ht/~gabrielgio/img/pkg/database/repository"
286+)
287+
288+type (
289+	User struct {
290+		ID       uint
291+		Username string
292+		Name     string
293+		Password []byte
294+		IsAdmin  bool
295+		Path     string
296+	}
297+
298+	Users map[uint]*User
299+
300+	UserRepository struct {
301+		icount uint
302+		users  Users
303+	}
304+)
305+
306+var _ repository.UserRepository = &UserRepository{}
307+var _ repository.AuthRepository = &UserRepository{}
308+
309+func NewUserRepository() *UserRepository {
310+	return &UserRepository{
311+		users: make(map[uint]*User),
312+	}
313+}
314+
315+func (u *User) ToModel() *repository.User {
316+	return &repository.User{
317+		ID:       u.ID,
318+		Username: u.Username,
319+		Name:     u.Name,
320+		IsAdmin:  u.IsAdmin,
321+		Path:     u.Path,
322+	}
323+}
324+
325+func (u Users) ToModels() []*repository.User {
326+	users := make([]*repository.User, 0, len(u))
327+	for _, i := range u {
328+		users = append(users, i.ToModel())
329+	}
330+	return users
331+}
332+
333+func (u *UserRepository) Get(ctx context.Context, id uint) (*repository.User, error) {
334+	if user, ok := u.users[id]; ok {
335+		return user.ToModel(), nil
336+	}
337+
338+	return nil, errors.New("Not Found")
339+}
340+
341+func (u *UserRepository) List(_ context.Context) ([]*repository.User, error) {
342+	return u.users.ToModels(), nil
343+}
344+
345+func (u *UserRepository) Create(_ context.Context, createUser *repository.CreateUser) (uint, error) {
346+	id := u.furtherID()
347+	u.users[id] = &User{
348+		ID:       id,
349+		Name:     createUser.Name,
350+		Username: createUser.Username,
351+		Path:     createUser.Path,
352+		Password: createUser.Password,
353+	}
354+	return id, nil
355+}
356+
357+func (u *UserRepository) Update(_ context.Context, id uint, updateUser *repository.UpdateUser) error {
358+	user, ok := u.users[id]
359+	if !ok {
360+		return errors.New("Invalid ID")
361+	}
362+
363+	user.Name = updateUser.Name
364+	user.Username = updateUser.Username
365+	if updateUser.Password != "" {
366+		user.Password = []byte(updateUser.Password)
367+	}
368+
369+	return nil
370+}
371+
372+func (u *UserRepository) Any(_ context.Context) (bool, error) {
373+	return len(u.users) > 0, nil
374+}
375+
376+func (u *UserRepository) GetIDByUsername(ctx context.Context, username string) (uint, error) {
377+	for id, u := range u.users {
378+		if u.Username == username {
379+			return id, nil
380+		}
381+	}
382+
383+	return 0, errors.New("Not Found")
384+}
385+
386+func (u *UserRepository) GetPassword(ctx context.Context, id uint) ([]byte, error) {
387+	if user, ok := u.users[id]; ok {
388+		return []byte(user.Password), nil
389+	}
390+
391+	return nil, errors.New("Not Found")
392+}
393+
394+func (u *UserRepository) furtherID() uint {
395+	u.icount++
396+	return u.icount
397+}
398diff --git a/pkg/components/auth/model.go b/pkg/database/repository/auth.go
399rename from pkg/components/auth/model.go
400rename to pkg/database/repository/auth.go
401index dd6ce5036b91127e13e37c4e57b7baa13b486a1a..b3194956c9c9b0dcefda0d4cc64c297f900479f8 100644
402--- a/pkg/components/auth/model.go
403+++ b/pkg/database/repository/auth.go
404@@ -1,9 +1,9 @@
405-package auth
406+package repository
407 
408 import "context"
409 
410 type (
411-	Repository interface {
412+	AuthRepository interface {
413 		GetIDByUsername(ctx context.Context, username string) (uint, error)
414 		GetPassword(ctx context.Context, id uint) ([]byte, error)
415 	}
416diff --git a/pkg/components/user/model.go b/pkg/database/repository/user.go
417rename from pkg/components/user/model.go
418rename to pkg/database/repository/user.go
419index 0ff6d0ab9a5b77cbfef5fab9f5b717d6e066119e..f8bd71921567ddf5ed5bdd3ed15e4e526f3bad64 100644
420--- a/pkg/components/user/model.go
421+++ b/pkg/database/repository/user.go
422@@ -1,4 +1,4 @@
423-package user
424+package repository
425 
426 import "context"
427 
428@@ -14,7 +14,7 @@
429 	UpdateUser struct {
430 		Username string
431 		Name     string
432-		Password *string
433+		Password string
434 	}
435 
436 	CreateUser struct {
437@@ -25,7 +25,8 @@ 		IsAdmin  bool
438 		Path     string
439 	}
440 
441-	Repository interface {
442+	UserRepository interface {
443+		Get(ctx context.Context, id uint) (*User, error)
444 		List(ctx context.Context) ([]*User, error)
445 		Create(ctx context.Context, createUser *CreateUser) (uint, error)
446 		Update(ctx context.Context, id uint, updateUser *UpdateUser) error
447diff --git a/pkg/database/sql/user.go b/pkg/database/sql/user.go
448index a0884f4f6e1db95c98c490fbb6ca6e10ac214de4..479a9c59d36cb407f4693b663c409eb71701ee6b 100644
449--- a/pkg/database/sql/user.go
450+++ b/pkg/database/sql/user.go
451@@ -6,8 +6,7 @@
452 	"golang.org/x/crypto/bcrypt"
453 	"gorm.io/gorm"
454 
455-	"git.sr.ht/~gabrielgio/img/pkg/components/auth"
456-	"git.sr.ht/~gabrielgio/img/pkg/components/user"
457+	"git.sr.ht/~gabrielgio/img/pkg/database/repository"
458 )
459 
460 type (
461@@ -27,8 +26,8 @@ 		db *gorm.DB
462 	}
463 )
464 
465-var _ auth.Repository = &UserRepository{}
466-var _ user.Repository = &UserRepository{}
467+var _ repository.UserRepository = &UserRepository{}
468+var _ repository.AuthRepository = &UserRepository{}
469 
470 func NewUserRepository(db *gorm.DB) *UserRepository {
471 	return &UserRepository{
472@@ -36,8 +35,8 @@ 		db: db,
473 	}
474 }
475 
476-func (self *User) ToModel() *user.User {
477-	return &user.User{
478+func (self *User) ToModel() *repository.User {
479+	return &repository.User{
480 		ID:       self.Model.ID,
481 		Name:     self.Name,
482 		Username: self.Username,
483@@ -46,7 +45,7 @@ 		IsAdmin:  self.IsAdmin,
484 	}
485 }
486 
487-func (self Users) ToModel() (users []*user.User) {
488+func (self Users) ToModel() (users []*repository.User) {
489 	for _, user := range self {
490 		users = append(users, user.ToModel())
491 	}
492@@ -75,7 +74,7 @@ 		})
493 	}
494 }
495 
496-func (self *UserRepository) List(ctx context.Context) ([]*user.User, error) {
497+func (self *UserRepository) List(ctx context.Context) ([]*repository.User, error) {
498 	users := Users{}
499 	result := self.db.
500 		WithContext(ctx).
501@@ -88,8 +87,8 @@
502 	return users.ToModel(), nil
503 }
504 
505-func (self *UserRepository) Get(ctx context.Context, id uint) (*user.User, error) {
506-	var user = &user.User{ID: id}
507+func (self *UserRepository) Get(ctx context.Context, id uint) (*repository.User, error) {
508+	var user = &repository.User{ID: id}
509 	result := self.db.
510 		WithContext(ctx).
511 		First(user)
512@@ -137,7 +136,7 @@
513 	return userPassword.Password, nil
514 }
515 
516-func (self *UserRepository) Create(ctx context.Context, createUser *user.CreateUser) (uint, error) {
517+func (self *UserRepository) Create(ctx context.Context, createUser *repository.CreateUser) (uint, error) {
518 	user := &User{
519 		Username: createUser.Username,
520 		Name:     createUser.Name,
521@@ -154,7 +153,7 @@
522 	return user.Model.ID, nil
523 }
524 
525-func (self *UserRepository) Update(ctx context.Context, id uint, update *user.UpdateUser) error {
526+func (self *UserRepository) Update(ctx context.Context, id uint, update *repository.UpdateUser) error {
527 	user := &User{
528 		Model: gorm.Model{
529 			ID: id,
530diff --git a/pkg/ext/middleware.go b/pkg/ext/middleware.go
531index bc23b9087ca3fab75190d884ec56b4d6472297f0..d255c6d70f398251ed0e7a7340bb9169fad7bf9a 100644
532--- a/pkg/ext/middleware.go
533+++ b/pkg/ext/middleware.go
534@@ -7,7 +7,7 @@
535 	"github.com/sirupsen/logrus"
536 	"github.com/valyala/fasthttp"
537 
538-	"git.sr.ht/~gabrielgio/img/pkg/components/user"
539+	"git.sr.ht/~gabrielgio/img/pkg/database/repository"
540 )
541 
542 func HTML(next fasthttp.RequestHandler) fasthttp.RequestHandler {
543@@ -91,10 +91,10 @@ 	}
544 }
545 
546 type InitialSetupMiddleware struct {
547-	userRepository user.Repository
548+	userRepository repository.UserRepository
549 }
550 
551-func NewInitialSetupMiddleware(userRepository user.Repository) *InitialSetupMiddleware {
552+func NewInitialSetupMiddleware(userRepository repository.UserRepository) *InitialSetupMiddleware {
553 	return &InitialSetupMiddleware{
554 		userRepository: userRepository,
555 	}
556diff --git a/pkg/testkit/testkit.go b/pkg/testkit/testkit.go
557index 526e1b3b4ef1003bc6f35969befb9d3dc4fb24e7..3cc4afd4fa9f0ae6330b5aed8f1db23cdb7070c8 100644
558--- a/pkg/testkit/testkit.go
559+++ b/pkg/testkit/testkit.go
560@@ -9,18 +9,21 @@ 	"github.com/google/go-cmp/cmp"
561 )
562 
563 func TestValue[T any](t *testing.T, method string, want, got T) {
564+	t.Helper()
565 	if diff := cmp.Diff(want, got); diff != "" {
566 		t.Errorf("%s() mismatch (-want +got):\n%s", method, diff)
567 	}
568 }
569 
570 func TestFatalError(t *testing.T, method string, err error) {
571+	t.Helper()
572 	if err != nil {
573 		t.Fatalf("%s() fatal error : %+v", method, err)
574 	}
575 }
576 
577 func TestError(t *testing.T, method string, want, got error) {
578+	t.Helper()
579 	if !equalError(want, got) {
580 		t.Errorf("%s() err mismatch want: %+v got %+v", method, want, got)
581 	}
582diff --git a/pkg/view/media.go b/pkg/view/media.go
583index 22f950d8050cba87ad40c33a420cb80e29625dc0..66e302013f11217f069046868bc5c707b9a87cf9 100644
584--- a/pkg/view/media.go
585+++ b/pkg/view/media.go
586@@ -89,7 +89,7 @@ 		return err
587 	}
588 
589 	ctx.Response.Header.SetContentType(media.MIMEType)
590-	ctx.SendFile(media.Path)
591+	fasthttp.ServeFileUncompressed(ctx, media.Path)
592 	return nil
593 }
594 
595diff --git a/pkg/view/settings.go b/pkg/view/settings.go
596index e5acb1bbc57cfcb0325646114b7e95342e21c53a..954cc986086e6ba0d9429c32c800d9c91fbe6b90 100644
597--- a/pkg/view/settings.go
598+++ b/pkg/view/settings.go
599@@ -5,7 +5,7 @@ 	"github.com/valyala/fasthttp"
600 
601 	"git.sr.ht/~gabrielgio/img"
602 	"git.sr.ht/~gabrielgio/img/pkg/components/settings"
603-	"git.sr.ht/~gabrielgio/img/pkg/components/user"
604+	"git.sr.ht/~gabrielgio/img/pkg/database/repository"
605 	"git.sr.ht/~gabrielgio/img/pkg/ext"
606 )
607 
608@@ -13,18 +13,18 @@ type (
609 	SettingsView struct {
610 		// there is not need to create a controller for this
611 		settingsRepository settings.Repository
612-		userRepository     user.Repository
613+		userRepository     repository.UserRepository
614 	}
615 
616 	SettingsPage struct {
617 		Settings *settings.Settings
618-		Users    []*user.User
619+		Users    []*repository.User
620 	}
621 )
622 
623 func NewSettingsView(
624 	settingsRespository settings.Repository,
625-	userRepository user.Repository,
626+	userRepository repository.UserRepository,
627 ) *SettingsView {
628 	return &SettingsView{
629 		settingsRepository: settingsRespository,