From 7a414da9a802d5eeee911b3536790a061e1d7503 Mon Sep 17 00:00:00 2001 From: Gabriel Arakaki Giovanini Date: Thu, 29 Jun 2023 23:33:02 +0200 Subject: ref: Move all controller under the same folder Move all controller to the same folder and rename them to service. Moving them to the same folder allow an easier setup for testing. --- cmd/server/main.go | 7 +- pkg/components/auth/controller.go | 77 -------------------- pkg/components/auth/controller_test.go | 78 -------------------- pkg/components/auth/mock_test.go | 121 -------------------------------- pkg/components/errors.go | 8 --- pkg/components/filesystem/controller.go | 91 ------------------------ pkg/components/user/controller.go | 1 - pkg/service/auth.go | 76 ++++++++++++++++++++ pkg/service/auth_test.go | 78 ++++++++++++++++++++ pkg/service/errors.go | 8 +++ pkg/service/filesystem.go | 91 ++++++++++++++++++++++++ pkg/service/main_test.go | 121 ++++++++++++++++++++++++++++++++ pkg/view/auth.go | 6 +- pkg/view/filesystem.go | 8 +-- 14 files changed, 384 insertions(+), 387 deletions(-) delete mode 100644 pkg/components/auth/controller.go delete mode 100644 pkg/components/auth/controller_test.go delete mode 100644 pkg/components/auth/mock_test.go delete mode 100644 pkg/components/errors.go delete mode 100644 pkg/components/filesystem/controller.go delete mode 100644 pkg/components/user/controller.go create mode 100644 pkg/service/auth.go create mode 100644 pkg/service/auth_test.go create mode 100644 pkg/service/errors.go create mode 100644 pkg/service/filesystem.go create mode 100644 pkg/service/main_test.go diff --git a/cmd/server/main.go b/cmd/server/main.go index 76cf0c0..702ca6e 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -17,12 +17,11 @@ import ( "gorm.io/gorm" "git.sr.ht/~gabrielgio/img" - "git.sr.ht/~gabrielgio/img/pkg/components/auth" - "git.sr.ht/~gabrielgio/img/pkg/components/filesystem" "git.sr.ht/~gabrielgio/img/pkg/database/localfs" "git.sr.ht/~gabrielgio/img/pkg/database/repository" "git.sr.ht/~gabrielgio/img/pkg/database/sql" "git.sr.ht/~gabrielgio/img/pkg/ext" + "git.sr.ht/~gabrielgio/img/pkg/service" "git.sr.ht/~gabrielgio/img/pkg/view" "git.sr.ht/~gabrielgio/img/pkg/worker" ) @@ -97,8 +96,8 @@ func main() { // controller var ( - userController = auth.NewController(userRepository, userRepository, hexKey) - fileSystemController = filesystem.NewController(fileSystemRepository) + userController = service.NewAuthController(userRepository, userRepository, hexKey) + fileSystemController = service.NewFileSystemController(fileSystemRepository) ) // view diff --git a/pkg/components/auth/controller.go b/pkg/components/auth/controller.go deleted file mode 100644 index 0b08fcc..0000000 --- a/pkg/components/auth/controller.go +++ /dev/null @@ -1,77 +0,0 @@ -package auth - -import ( - "context" - - "golang.org/x/crypto/bcrypt" - - "git.sr.ht/~gabrielgio/img/pkg/components" - "git.sr.ht/~gabrielgio/img/pkg/database/repository" - "git.sr.ht/~gabrielgio/img/pkg/ext" -) - -type Controller struct { - authRepository repository.AuthRepository - userRepository repository.UserRepository - key []byte -} - -func NewController( - authRepository repository.AuthRepository, - userRepository repository.UserRepository, - key []byte, -) *Controller { - return &Controller{ - authRepository: authRepository, - userRepository: userRepository, - key: key, - } -} - -func (c *Controller) Login(ctx context.Context, username, password []byte) ([]byte, error) { - id, err := c.authRepository.GetIDByUsername(ctx, string(username)) - if err != nil { - return nil, err - } - - hashedPassword, err := c.authRepository.GetPassword(ctx, id) - if err != nil { - return nil, err - } - - if err := bcrypt.CompareHashAndPassword(hashedPassword, password); err != nil { - return nil, err - } - - token := &ext.Token{ - UserID: id, - Username: string(username), - } - return ext.WriteToken(token, c.key) -} - -// InitialRegister register a initial user, it will validate if there is another -// user stored already. If so an error `InvlidaInput` will be returned -func (c *Controller) InitialRegister(ctx context.Context, username, password []byte, path []byte) error { - exist, err := c.userRepository.Any(ctx) - if err != nil { - return err - } - - if exist { - return components.InvlidaInput - } - - hash, err := bcrypt.GenerateFromPassword(password, bcrypt.MinCost) - if err != nil { - return err - } - - _, err = c.userRepository.Create(ctx, &repository.CreateUser{ - Username: string(username), - Password: hash, - Path: string(path), - }) - - return err -} diff --git a/pkg/components/auth/controller_test.go b/pkg/components/auth/controller_test.go deleted file mode 100644 index b1ca065..0000000 --- a/pkg/components/auth/controller_test.go +++ /dev/null @@ -1,78 +0,0 @@ -//go:build unit - -package auth - -import ( - "context" - "testing" - - "git.sr.ht/~gabrielgio/img/pkg/database/repository" - "git.sr.ht/~gabrielgio/img/pkg/ext" - "git.sr.ht/~gabrielgio/img/pkg/testkit" -) - -type ( - scene struct { - ctx context.Context - authRepository repository.AuthRepository - userRepository repository.UserRepository - controller *Controller - } -) - -var ( - key = []byte("6368616e676520746869732070617373") -) - -func setUp() *scene { - userRepository := NewUserRepository() - return &scene{ - ctx: context.Background(), - authRepository: userRepository, - userRepository: userRepository, - controller: NewController(userRepository, userRepository, key), - } -} - -func TestInitialRegisterAndLogin(t *testing.T) { - testCases := []struct { - name string - username string - password []byte - }{ - { - name: "Normal register", - username: "username", - password: []byte("this is an password"), - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - scene := setUp() - - err := scene.controller.InitialRegister(scene.ctx, []byte(tc.username), tc.password, []byte("/")) - testkit.TestFatalError(t, "Register", err) - - users, err := scene.userRepository.List(scene.ctx) - userID := users[0].ID - - user, err := scene.userRepository.Get(scene.ctx, userID) - testkit.TestFatalError(t, "Get", err) - testkit.TestValue(t, "Register", tc.username, user.Username) - - auth, err := scene.controller.Login(scene.ctx, []byte(tc.username), tc.password) - testkit.TestFatalError(t, "Login", err) - - token, err := ext.ReadToken(auth, key) - testkit.TestFatalError(t, "Login", err) - - testkit.TestValue(t, "Login", tc.username, token.Username) - testkit.TestValue(t, "Login", userID, token.UserID) - }) - } -} - -func remove[T any](slice []T, s int) []T { - return append(slice[:s], slice[s+1:]...) -} diff --git a/pkg/components/auth/mock_test.go b/pkg/components/auth/mock_test.go deleted file mode 100644 index 885f643..0000000 --- a/pkg/components/auth/mock_test.go +++ /dev/null @@ -1,121 +0,0 @@ -//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/errors.go b/pkg/components/errors.go deleted file mode 100644 index aedbe88..0000000 --- a/pkg/components/errors.go +++ /dev/null @@ -1,8 +0,0 @@ -package components - -import "errors" - -var ( - NotFound = errors.New("Not found") - InvlidaInput = errors.New("Invalid Input") -) diff --git a/pkg/components/filesystem/controller.go b/pkg/components/filesystem/controller.go deleted file mode 100644 index 6c613a3..0000000 --- a/pkg/components/filesystem/controller.go +++ /dev/null @@ -1,91 +0,0 @@ -package filesystem - -import ( - "io/fs" - "net/url" - "path" - "strings" - - "git.sr.ht/~gabrielgio/img/pkg/database/repository" -) - -type ( - Controller struct { - repository repository.FileSystemRepository - } - - DirectoryParam struct { - Name string - UrlEncodedPath string - } - - FileParam struct { - UrlEncodedPath string - Info fs.FileInfo - } - - Page struct { - History []*DirectoryParam - Files []*FileParam - } -) - -func NewController(repository repository.FileSystemRepository) *Controller { - return &Controller{ - repository: repository, - } -} - -func getHistory(filepath string) []*DirectoryParam { - var ( - paths = strings.Split(filepath, "/") - result = make([]*DirectoryParam, 0, len(paths)) - acc = "" - ) - - // add root folder - result = append(result, &DirectoryParam{ - Name: "...", - UrlEncodedPath: "", - }) - - if len(paths) == 1 && paths[0] == "" { - return result - } - - for _, p := range paths { - acc = path.Join(acc, p) - result = append(result, &DirectoryParam{ - Name: p, - UrlEncodedPath: url.QueryEscape(acc), - }) - } - return result -} - -func (self *Controller) GetPage(filepath string) (*Page, error) { - decodedPath, err := url.QueryUnescape(filepath) - if err != nil { - return nil, err - } - - files, err := self.repository.List(decodedPath) - if err != nil { - return nil, err - } - - params := make([]*FileParam, 0, len(files)) - for _, info := range files { - fullPath := path.Join(decodedPath, info.Name()) - scapedFullPath := url.QueryEscape(fullPath) - params = append(params, &FileParam{ - Info: info, - UrlEncodedPath: scapedFullPath, - }) - } - - return &Page{ - Files: params, - History: getHistory(decodedPath), - }, nil -} diff --git a/pkg/components/user/controller.go b/pkg/components/user/controller.go deleted file mode 100644 index a00006b..0000000 --- a/pkg/components/user/controller.go +++ /dev/null @@ -1 +0,0 @@ -package user diff --git a/pkg/service/auth.go b/pkg/service/auth.go new file mode 100644 index 0000000..4358a8a --- /dev/null +++ b/pkg/service/auth.go @@ -0,0 +1,76 @@ +package service + +import ( + "context" + + "golang.org/x/crypto/bcrypt" + + "git.sr.ht/~gabrielgio/img/pkg/database/repository" + "git.sr.ht/~gabrielgio/img/pkg/ext" +) + +type AuthController struct { + authRepository repository.AuthRepository + userRepository repository.UserRepository + key []byte +} + +func NewAuthController( + authRepository repository.AuthRepository, + userRepository repository.UserRepository, + key []byte, +) *AuthController { + return &AuthController{ + authRepository: authRepository, + userRepository: userRepository, + key: key, + } +} + +func (c *AuthController) Login(ctx context.Context, username, password []byte) ([]byte, error) { + id, err := c.authRepository.GetIDByUsername(ctx, string(username)) + if err != nil { + return nil, err + } + + hashedPassword, err := c.authRepository.GetPassword(ctx, id) + if err != nil { + return nil, err + } + + if err := bcrypt.CompareHashAndPassword(hashedPassword, password); err != nil { + return nil, err + } + + token := &ext.Token{ + UserID: id, + Username: string(username), + } + return ext.WriteToken(token, c.key) +} + +// InitialRegister register a initial user, it will validate if there is another +// user stored already. If so an error `InvlidaInput` will be returned +func (c *AuthController) InitialRegister(ctx context.Context, username, password []byte, path []byte) error { + exist, err := c.userRepository.Any(ctx) + if err != nil { + return err + } + + if exist { + return InvlidInput + } + + hash, err := bcrypt.GenerateFromPassword(password, bcrypt.MinCost) + if err != nil { + return err + } + + _, err = c.userRepository.Create(ctx, &repository.CreateUser{ + Username: string(username), + Password: hash, + Path: string(path), + }) + + return err +} diff --git a/pkg/service/auth_test.go b/pkg/service/auth_test.go new file mode 100644 index 0000000..35b2475 --- /dev/null +++ b/pkg/service/auth_test.go @@ -0,0 +1,78 @@ +//go:build unit + +package service + +import ( + "context" + "testing" + + "git.sr.ht/~gabrielgio/img/pkg/database/repository" + "git.sr.ht/~gabrielgio/img/pkg/ext" + "git.sr.ht/~gabrielgio/img/pkg/testkit" +) + +type ( + scene struct { + ctx context.Context + authRepository repository.AuthRepository + userRepository repository.UserRepository + controller *AuthController + } +) + +var ( + key = []byte("6368616e676520746869732070617373") +) + +func setUp() *scene { + userRepository := NewUserRepository() + return &scene{ + ctx: context.Background(), + authRepository: userRepository, + userRepository: userRepository, + controller: NewAuthController(userRepository, userRepository, key), + } +} + +func TestInitialRegisterAndLogin(t *testing.T) { + testCases := []struct { + name string + username string + password []byte + }{ + { + name: "Normal register", + username: "username", + password: []byte("this is an password"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + scene := setUp() + + err := scene.controller.InitialRegister(scene.ctx, []byte(tc.username), tc.password, []byte("/")) + testkit.TestFatalError(t, "Register", err) + + users, err := scene.userRepository.List(scene.ctx) + userID := users[0].ID + + user, err := scene.userRepository.Get(scene.ctx, userID) + testkit.TestFatalError(t, "Get", err) + testkit.TestValue(t, "Register", tc.username, user.Username) + + auth, err := scene.controller.Login(scene.ctx, []byte(tc.username), tc.password) + testkit.TestFatalError(t, "Login", err) + + token, err := ext.ReadToken(auth, key) + testkit.TestFatalError(t, "Login", err) + + testkit.TestValue(t, "Login", tc.username, token.Username) + testkit.TestValue(t, "Login", userID, token.UserID) + }) + } +} + +func remove[T any](slice []T, s int) []T { + return append(slice[:s], slice[s+1:]...) +} diff --git a/pkg/service/errors.go b/pkg/service/errors.go new file mode 100644 index 0000000..6598464 --- /dev/null +++ b/pkg/service/errors.go @@ -0,0 +1,8 @@ +package service + +import "errors" + +var ( + NotFound = errors.New("Not found") + InvlidInput = errors.New("Invalid Input") +) diff --git a/pkg/service/filesystem.go b/pkg/service/filesystem.go new file mode 100644 index 0000000..3516ce2 --- /dev/null +++ b/pkg/service/filesystem.go @@ -0,0 +1,91 @@ +package service + +import ( + "io/fs" + "net/url" + "path" + "strings" + + "git.sr.ht/~gabrielgio/img/pkg/database/repository" +) + +type ( + FileSystemController struct { + repository repository.FileSystemRepository + } + + DirectoryParam struct { + Name string + UrlEncodedPath string + } + + FileParam struct { + UrlEncodedPath string + Info fs.FileInfo + } + + Page struct { + History []*DirectoryParam + Files []*FileParam + } +) + +func NewFileSystemController(repository repository.FileSystemRepository) *FileSystemController { + return &FileSystemController{ + repository: repository, + } +} + +func getHistory(filepath string) []*DirectoryParam { + var ( + paths = strings.Split(filepath, "/") + result = make([]*DirectoryParam, 0, len(paths)) + acc = "" + ) + + // add root folder + result = append(result, &DirectoryParam{ + Name: "...", + UrlEncodedPath: "", + }) + + if len(paths) == 1 && paths[0] == "" { + return result + } + + for _, p := range paths { + acc = path.Join(acc, p) + result = append(result, &DirectoryParam{ + Name: p, + UrlEncodedPath: url.QueryEscape(acc), + }) + } + return result +} + +func (self *FileSystemController) GetPage(filepath string) (*Page, error) { + decodedPath, err := url.QueryUnescape(filepath) + if err != nil { + return nil, err + } + + files, err := self.repository.List(decodedPath) + if err != nil { + return nil, err + } + + params := make([]*FileParam, 0, len(files)) + for _, info := range files { + fullPath := path.Join(decodedPath, info.Name()) + scapedFullPath := url.QueryEscape(fullPath) + params = append(params, &FileParam{ + Info: info, + UrlEncodedPath: scapedFullPath, + }) + } + + return &Page{ + Files: params, + History: getHistory(decodedPath), + }, nil +} diff --git a/pkg/service/main_test.go b/pkg/service/main_test.go new file mode 100644 index 0000000..5c10ecd --- /dev/null +++ b/pkg/service/main_test.go @@ -0,0 +1,121 @@ +//go:build unit + +package service + +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/view/auth.go b/pkg/view/auth.go index 3f9e414..6b096d9 100644 --- a/pkg/view/auth.go +++ b/pkg/view/auth.go @@ -6,15 +6,15 @@ import ( "github.com/valyala/fasthttp" "git.sr.ht/~gabrielgio/img" - "git.sr.ht/~gabrielgio/img/pkg/components/auth" "git.sr.ht/~gabrielgio/img/pkg/ext" + "git.sr.ht/~gabrielgio/img/pkg/service" ) type AuthView struct { - userController *auth.Controller + userController *service.AuthController } -func NewAuthView(userController *auth.Controller) *AuthView { +func NewAuthView(userController *service.AuthController) *AuthView { return &AuthView{ userController: userController, } diff --git a/pkg/view/filesystem.go b/pkg/view/filesystem.go index d2ebff4..d598b88 100644 --- a/pkg/view/filesystem.go +++ b/pkg/view/filesystem.go @@ -4,25 +4,25 @@ import ( "github.com/valyala/fasthttp" "git.sr.ht/~gabrielgio/img" - "git.sr.ht/~gabrielgio/img/pkg/components/filesystem" "git.sr.ht/~gabrielgio/img/pkg/database/repository" "git.sr.ht/~gabrielgio/img/pkg/ext" + "git.sr.ht/~gabrielgio/img/pkg/service" ) type ( FileSystemView struct { - controller filesystem.Controller + controller service.FileSystemController settings repository.SettingsRepository } FilePage struct { - Page *filesystem.Page + Page *service.Page ShowMode bool ShowOwner bool } ) func NewFileSystemView( - controller filesystem.Controller, + controller service.FileSystemController, settingsRepository repository.SettingsRepository, ) *FileSystemView { return &FileSystemView{ -- cgit v1.2.3