diff options
| author | Gabriel Arakaki Giovanini <mail@gabrielgio.me> | 2023-07-22 18:45:59 +0200 | 
|---|---|---|
| committer | Gabriel Arakaki Giovanini <mail@gabrielgio.me> | 2023-07-22 19:35:33 +0200 | 
| commit | 3b9d27649a31e5af3fb137ff5b3378e7b8f7b9ae (patch) | |
| tree | 0f43f30d824b8b805e4a72b66a0ee1bee7397803 | |
| parent | 1e4613aa1113b373a8d841c28e222599237a33c5 (diff) | |
| download | lens-3b9d27649a31e5af3fb137ff5b3378e7b8f7b9ae.tar.gz lens-3b9d27649a31e5af3fb137ff5b3378e7b8f7b9ae.tar.bz2 lens-3b9d27649a31e5af3fb137ff5b3378e7b8f7b9ae.zip | |
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.
| -rw-r--r-- | Makefile | 3 | ||||
| -rw-r--r-- | cmd/server/main.go | 2 | ||||
| -rw-r--r-- | pkg/database/repository/user.go | 5 | ||||
| -rw-r--r-- | pkg/database/sql/user.go | 28 | ||||
| -rw-r--r-- | pkg/service/auth.go | 58 | ||||
| -rw-r--r-- | pkg/service/main_test.go | 13 | ||||
| -rw-r--r-- | pkg/view/settings.go | 99 | ||||
| -rw-r--r-- | templates/base.qtpl | 12 | ||||
| -rw-r--r-- | templates/settings.qtpl | 5 | ||||
| -rw-r--r-- | templates/user.qtpl | 54 | 
10 files changed, 261 insertions, 18 deletions
| @@ -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 {                  <div class="column">                      {%s user.Path %}                  </div> -                <div class="column  has-text-right"><a href="#">Edit</a> / <a href="#" class="is-danger">Delete</a></div> +                <div class="column  has-text-right"><a href="/users?userId={%s FromUInttoString(&user.ID) %}">Edit</a> <a href="/users/delete?userId={%s FromUInttoString(&user.ID) %}" class="is-danger">Delete</a></div>              </div>          </div>      {% endfor %} +        <div class="field"> +            <a href="/users/" class="button">Create</a> +        </div>      </div>  </div>  {% 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() %} +<h1>Initial Setup</h1> +<form action="/users/" method="post"> +    {% if p.ID != nil %}  +    <input type="hidden" name="userId" value="{%s FromUInttoString(p.ID) %}" /> +    {% endif %} +    <div class="field"> +        <label class="label">Username</label> +        <div class="control"> +            <input class="input" name="username" type="text" value="{%s p.Username %}"> +        </div> +    </div> + +    {% if p.ID == nil %}  +    <div class="field"> +        <label class="label">Password</label> +        <div class="control"> +            <input class="input" name="password" type="password"> +        </div> +    </div> +    {% endif %} +    <div class="field"> +        <label class="label">Root folder</label> +        <div class="control"> +            <input class="input" name="path" type="text" value="{%s p.Path %}"> +        </div> +    </div> +    <div class="field"> +        <label class="label">Is Admin?</label> +        <div class="control"> +            <input type="checkbox" name="isAdmin" type="password" {% if p.IsAdmin %}checked{% endif %}> +        </div> +    </div> +    <div class="field"> +        <input class="button is-pulled-right" value="Save" type="submit"> +    </div> +</form> +{% endfunc %} + +{% func (p *UserPage) Script() %} +{% endfunc %} | 
