diff options
Diffstat (limited to 'pkg/service')
-rw-r--r-- | pkg/service/auth.go | 76 | ||||
-rw-r--r-- | pkg/service/auth_test.go | 78 | ||||
-rw-r--r-- | pkg/service/errors.go | 8 | ||||
-rw-r--r-- | pkg/service/filesystem.go | 91 | ||||
-rw-r--r-- | pkg/service/main_test.go | 121 |
5 files changed, 374 insertions, 0 deletions
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 +} |