aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Arakaki Giovanini <mail@gabrielgio.me>2023-07-22 18:45:59 +0200
committerGabriel Arakaki Giovanini <mail@gabrielgio.me>2023-07-22 19:35:33 +0200
commit3b9d27649a31e5af3fb137ff5b3378e7b8f7b9ae (patch)
tree0f43f30d824b8b805e4a72b66a0ee1bee7397803
parent1e4613aa1113b373a8d841c28e222599237a33c5 (diff)
downloadlens-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--Makefile3
-rw-r--r--cmd/server/main.go2
-rw-r--r--pkg/database/repository/user.go5
-rw-r--r--pkg/database/sql/user.go28
-rw-r--r--pkg/service/auth.go58
-rw-r--r--pkg/service/main_test.go13
-rw-r--r--pkg/view/settings.go99
-rw-r--r--templates/base.qtpl12
-rw-r--r--templates/settings.qtpl5
-rw-r--r--templates/user.qtpl54
10 files changed, 261 insertions, 18 deletions
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 {
<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 %}