From 3b9d27649a31e5af3fb137ff5b3378e7b8f7b9ae Mon Sep 17 00:00:00 2001 From: Gabriel Arakaki Giovanini Date: Sat, 22 Jul 2023 18:45:59 +0200 Subject: feat: Add user management As many things it is on crude state. The settings.go has become a big mess, but I have achieve MVP, so from now one things shall improve as I'll spent more time on refactoring. --- Makefile | 3 +- cmd/server/main.go | 2 +- pkg/database/repository/user.go | 5 ++- pkg/database/sql/user.go | 28 +++++++++--- pkg/service/auth.go | 58 ++++++++++++++++++++++++ pkg/service/main_test.go | 13 ++++-- pkg/view/settings.go | 99 +++++++++++++++++++++++++++++++++++++++-- templates/base.qtpl | 12 +++++ templates/settings.qtpl | 5 ++- templates/user.qtpl | 54 ++++++++++++++++++++++ 10 files changed, 261 insertions(+), 18 deletions(-) create mode 100644 templates/user.qtpl diff --git a/Makefile b/Makefile index bd7d016..d9e89cc 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,8 @@ sass: --style compressed tmpl: - qtc -dir=./templates + cd ./templates && \ + qtc * test: test.unit test.integration diff --git a/cmd/server/main.go b/cmd/server/main.go index c7f9019..385c54f 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -107,7 +107,7 @@ func main() { for _, v := range []view.View{ view.NewAuthView(userController), view.NewFileSystemView(*fileSystemController, settingsRepository), - view.NewSettingsView(settingsRepository, userRepository), + view.NewSettingsView(settingsRepository, userController), view.NewMediaView(mediaRepository, userRepository, settingsRepository), } { v.SetMyselfIn(extRouter) diff --git a/pkg/database/repository/user.go b/pkg/database/repository/user.go index 3589007..5b2e386 100644 --- a/pkg/database/repository/user.go +++ b/pkg/database/repository/user.go @@ -14,7 +14,8 @@ type ( UpdateUser struct { Username string Name string - Password string + IsAdmin bool + Path string } CreateUser struct { @@ -31,6 +32,8 @@ type ( List(ctx context.Context) ([]*User, error) Create(ctx context.Context, createUser *CreateUser) (uint, error) Update(ctx context.Context, id uint, updateUser *UpdateUser) error + Delete(ctx context.Context, id uint) error + UpdatePassword(ctx context.Context, id uint, password []byte) error Any(ctx context.Context) (bool, error) } ) diff --git a/pkg/database/sql/user.go b/pkg/database/sql/user.go index 15dbe72..6b1cf0f 100644 --- a/pkg/database/sql/user.go +++ b/pkg/database/sql/user.go @@ -27,6 +27,7 @@ type ( ) var _ repository.UserRepository = &UserRepository{} + var _ repository.AuthRepository = &UserRepository{} func NewUserRepository(db *gorm.DB) *UserRepository { @@ -141,6 +142,7 @@ func (self *UserRepository) Create(ctx context.Context, createUser *repository.C Username: createUser.Username, Name: createUser.Name, Path: createUser.Path, + IsAdmin: createUser.IsAdmin, Password: string(createUser.Password), } @@ -161,11 +163,14 @@ func (self *UserRepository) Update(ctx context.Context, id uint, update *reposit }, Username: update.Username, Name: update.Name, + IsAdmin: update.IsAdmin, + Path: update.Path, } result := self.db. WithContext(ctx). - Save(user) + Omit("password"). + Updates(user) if result.Error != nil { return result.Error } @@ -174,14 +179,15 @@ func (self *UserRepository) Update(ctx context.Context, id uint, update *reposit } func (self *UserRepository) Delete(ctx context.Context, id uint) error { - userID := struct { - ID uint - }{ - ID: id, + user := &User{ + Model: gorm.Model{ + ID: id, + }, } + result := self.db. WithContext(ctx). - Delete(userID) + Delete(user) if result.Error != nil { return result.Error } @@ -219,3 +225,13 @@ func (u *UserRepository) GetPathFromUserID(ctx context.Context, id uint) (string return userPath, nil } + +func (u *UserRepository) UpdatePassword(ctx context.Context, id uint, password []byte) error { + result := u.db. + WithContext(ctx). + Model(&User{}). + Where("id = ?", id). + Update("password", password) + + return result.Error +} diff --git a/pkg/service/auth.go b/pkg/service/auth.go index 1966e70..f27cf88 100644 --- a/pkg/service/auth.go +++ b/pkg/service/auth.go @@ -82,6 +82,64 @@ func (c *AuthController) InitialRegister(ctx context.Context, username, password return err } +func (u *AuthController) List(ctx context.Context) ([]*repository.User, error) { + return u.userRepository.List(ctx) +} + +func (u *AuthController) Get(ctx context.Context, id uint) (*repository.User, error) { + return u.userRepository.Get(ctx, id) +} + +func (u *AuthController) Delete(ctx context.Context, id uint) error { + return u.userRepository.Delete(ctx, id) +} + +func (u *AuthController) Upsert( + ctx context.Context, + id *uint, + username string, + name string, + password []byte, + isAdmin bool, + path string, +) error { + if id != nil { + if err := u.userRepository.Update(ctx, *id, &repository.UpdateUser{ + Username: string(username), + Name: name, + IsAdmin: isAdmin, + Path: path, + }); err != nil { + return err + } + + if len(password) > 0 { + hash, err := bcrypt.GenerateFromPassword(password, bcrypt.MinCost) + if err != nil { + return err + } + + return u.userRepository.UpdatePassword(ctx, *id, hash) + } + return nil + } + + hash, err := bcrypt.GenerateFromPassword(password, bcrypt.MinCost) + if err != nil { + return err + } + + _, err = u.userRepository.Create(ctx, &repository.CreateUser{ + Username: username, + Name: name, + Password: hash, + IsAdmin: isAdmin, + Path: path, + }) + + return err +} + type Token struct { UserID uint Username string diff --git a/pkg/service/main_test.go b/pkg/service/main_test.go index e1214dc..8cef985 100644 --- a/pkg/service/main_test.go +++ b/pkg/service/main_test.go @@ -28,6 +28,7 @@ type ( ) var _ repository.UserRepository = &UserRepository{} + var _ repository.AuthRepository = &UserRepository{} func NewUserRepository() *UserRepository { @@ -86,10 +87,6 @@ func (u *UserRepository) Update(_ context.Context, id uint, updateUser *reposito user.Name = updateUser.Name user.Username = updateUser.Username - if updateUser.Password != "" { - user.Password = []byte(updateUser.Password) - } - return nil } @@ -127,3 +124,11 @@ func (u *UserRepository) GetPathFromUserID(ctx context.Context, id uint) (string return "", errors.New("Not Found") } + +func (u *UserRepository) UpdatePassword(ctx context.Context, id uint, password []byte) error { + panic("not implemented") // TODO: Implement +} + +func (u *UserRepository) Delete(ctx context.Context, id uint) error { + panic("not implemented") // TODO: Implement +} diff --git a/pkg/view/settings.go b/pkg/view/settings.go index ffce86b..5131362 100644 --- a/pkg/view/settings.go +++ b/pkg/view/settings.go @@ -1,10 +1,13 @@ package view import ( + "strconv" + "github.com/valyala/fasthttp" "git.sr.ht/~gabrielgio/img/pkg/database/repository" "git.sr.ht/~gabrielgio/img/pkg/ext" + "git.sr.ht/~gabrielgio/img/pkg/service" "git.sr.ht/~gabrielgio/img/templates" ) @@ -12,17 +15,17 @@ type ( SettingsView struct { // there is not need to create a controller for this settingsRepository repository.SettingsRepository - userRepository repository.UserRepository + userController *service.AuthController } ) func NewSettingsView( settingsRespository repository.SettingsRepository, - userRepository repository.UserRepository, + userController *service.AuthController, ) *SettingsView { return &SettingsView{ settingsRepository: settingsRespository, - userRepository: userRepository, + userController: userController, } } @@ -32,7 +35,7 @@ func (self *SettingsView) Index(ctx *fasthttp.RequestCtx) error { return err } - users, err := self.userRepository.List(ctx) + users, err := self.userController.List(ctx) if err != nil { return err } @@ -45,6 +48,76 @@ func (self *SettingsView) Index(ctx *fasthttp.RequestCtx) error { return nil } +func (self *SettingsView) User(ctx *fasthttp.RequestCtx) error { + id := string(ctx.FormValue("userId")) + idValue, err := ParseUint(id) + if err != nil { + return err + } + + if idValue == nil { + templates.WritePageTemplate(ctx, &templates.UserPage{}) + } else { + user, err := self.userController.Get(ctx, *idValue) + if err != nil { + return err + } + + templates.WritePageTemplate(ctx, &templates.UserPage{ + ID: idValue, + Username: user.Username, + Path: user.Path, + IsAdmin: user.IsAdmin, + }) + } + + return nil +} + +func (self *SettingsView) UpsertUser(ctx *fasthttp.RequestCtx) error { + var ( + username = string(ctx.FormValue("username")) + password = ctx.FormValue("password") + path = string(ctx.FormValue("path")) + isAdmin = string(ctx.FormValue("isAdmin")) == "on" + id = string(ctx.FormValue("userId")) + ) + + idValue, err := ParseUint(id) + if err != nil { + return err + } + + err = self.userController.Upsert(ctx, idValue, username, "", password, isAdmin, path) + if err != nil { + return err + } + + ctx.Redirect("/settings", 307) + return nil +} + +func (self *SettingsView) Delete(ctx *fasthttp.RequestCtx) error { + var ( + id = string(ctx.FormValue("userId")) + ) + + idValue, err := ParseUint(id) + if err != nil { + return err + } + + if idValue != nil { + err = self.userController.Delete(ctx, *idValue) + if err != nil { + return err + } + } + + ctx.Redirect("/settings", 307) + return nil +} + func (self *SettingsView) Save(ctx *fasthttp.RequestCtx) error { var ( showMode = string(ctx.FormValue("showMode")) == "on" @@ -67,4 +140,22 @@ func (self *SettingsView) Save(ctx *fasthttp.RequestCtx) error { func (self *SettingsView) SetMyselfIn(r *ext.Router) { r.GET("/settings/", self.Index) r.POST("/settings/", self.Save) + + r.GET("/users/", self.User) + r.GET("/users/delete", self.Delete) + r.POST("/users/", self.UpsertUser) +} + +func ParseUint(id string) (*uint, error) { + var idValue *uint + if id != "" { + v, err := strconv.Atoi(id) + if err != nil { + return nil, err + } + + u := uint(v) + idValue = &u + } + return idValue, nil } diff --git a/templates/base.qtpl b/templates/base.qtpl index 0c05782..5a7c3b7 100644 --- a/templates/base.qtpl +++ b/templates/base.qtpl @@ -1,11 +1,23 @@ This is a base page template. All the other template pages implement this interface. +{% import "strconv" %} + {% interface Page { Title() Content() Script() } + +%} + +{% code + func FromUInttoString(u *uint) string { + if u != nil { + return strconv.FormatUint(uint64(*u), 10) + } + return "" + } %} diff --git a/templates/settings.qtpl b/templates/settings.qtpl index 6eee1ab..4207439 100644 --- a/templates/settings.qtpl +++ b/templates/settings.qtpl @@ -52,10 +52,13 @@ type SettingsPage struct {
{%s user.Path %}
-
Edit / Delete
+
Edit Delete
{% endfor %} +
+ Create +
{% endfunc %} diff --git a/templates/user.qtpl b/templates/user.qtpl new file mode 100644 index 0000000..ba2f071 --- /dev/null +++ b/templates/user.qtpl @@ -0,0 +1,54 @@ + +{% code +type UserPage struct { + ID *uint + Username string + Path string + IsAdmin bool +} + +%} + +{% func (p *UserPage) Title() %}Login{% endfunc %} + +{% func (p *UserPage) Content() %} +

Initial Setup

+
+ {% if p.ID != nil %} + + {% endif %} +
+ +
+ +
+
+ + {% if p.ID == nil %} +
+ +
+ +
+
+ {% endif %} +
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+{% endfunc %} + +{% func (p *UserPage) Script() %} +{% endfunc %} -- cgit v1.2.3