aboutsummaryrefslogtreecommitdiff
path: root/pkg/service
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/service')
-rw-r--r--pkg/service/auth.go76
-rw-r--r--pkg/service/auth_test.go78
-rw-r--r--pkg/service/errors.go8
-rw-r--r--pkg/service/filesystem.go91
-rw-r--r--pkg/service/main_test.go121
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
+}