diff --git a/cmd/server/main.go b/cmd/server/main.go
index f58366ff418564305b29238e67f5b6314b10e249..4942ac31216717bc8c568944e409c69bb93ea521 100644
--- a/cmd/server/main.go
+++ b/cmd/server/main.go
@@ -72,16 +72,6 @@
r := router.New()
r.GET("/static/{filepath:*}", ext.FileServer(img.StaticFS, "static/"))
- authMiddleware := ext.NewAuthMiddleware(hexKey, logger.WithField("context", "auth"))
- logMiddleware := ext.NewLogMiddleare(logger.WithField("context", "http"))
-
- extRouter := ext.NewRouter(r)
- extRouter.AddMiddleware(logMiddleware.HTTP)
- extRouter.AddMiddleware(authMiddleware.LoggedIn)
- extRouter.AddMiddleware(ext.HTML)
-
- scheduler := worker.NewScheduler(*schedulerCount)
-
// repository
var (
userRepository = sql.NewUserRepository(db)
@@ -90,12 +80,24 @@ fileSystemRepository = localfs.NewFileSystemRepository(*root)
mediaRepository = sql.NewMediaRepository(db)
)
- //TODO: remove later
- userRepository.EnsureAdmin(context.Background())
+ // middleware
+ var (
+ authMiddleware = ext.NewAuthMiddleware(hexKey, logger.WithField("context", "auth"))
+ logMiddleware = ext.NewLogMiddleare(logger.WithField("context", "http"))
+ initialMiddleware = ext.NewInitialSetupMiddleware(userRepository)
+ )
+
+ extRouter := ext.NewRouter(r)
+ extRouter.AddMiddleware(ext.HTML)
+ extRouter.AddMiddleware(initialMiddleware.Check)
+ extRouter.AddMiddleware(authMiddleware.LoggedIn)
+ extRouter.AddMiddleware(logMiddleware.HTTP)
+
+ scheduler := worker.NewScheduler(*schedulerCount)
// controller
var (
- userController = auth.NewController(userRepository, hexKey)
+ userController = auth.NewController(userRepository, userRepository, hexKey)
fileSystemController = filesystem.NewController(fileSystemRepository)
)
diff --git a/pkg/components/auth/controller.go b/pkg/components/auth/controller.go
index a81a1c0f802acc5a4c90bcc400d2d9f913c73b9b..2f30fb5d1027286fc809f6a683bc42416ed86db3 100644
--- a/pkg/components/auth/controller.go
+++ b/pkg/components/auth/controller.go
@@ -5,18 +5,26 @@ "context"
"golang.org/x/crypto/bcrypt"
+ "git.sr.ht/~gabrielgio/img/pkg/components"
+ "git.sr.ht/~gabrielgio/img/pkg/components/user"
"git.sr.ht/~gabrielgio/img/pkg/ext"
)
type Controller struct {
- repository Repository
- key []byte
+ repository Repository
+ userRepository user.Repository
+ key []byte
}
-func NewController(repository Repository, key []byte) *Controller {
+func NewController(
+ repository Repository,
+ userRepository user.Repository,
+ key []byte,
+) *Controller {
return &Controller{
- repository: repository,
- key: key,
+ repository: repository,
+ userRepository: userRepository,
+ key: key,
}
}
@@ -41,3 +49,29 @@ 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 *Controller) InitialRegister(ctx context.Context, username, password []byte, path []byte) error {
+ exist, err := c.userRepository.Any(ctx)
+ if err != nil {
+ return err
+ }
+
+ if exist {
+ return components.InvlidaInput
+ }
+
+ hash, err := bcrypt.GenerateFromPassword(password, bcrypt.MinCost)
+ if err != nil {
+ return err
+ }
+
+ err = c.userRepository.Create(ctx, &user.CreateUser{
+ Username: string(username),
+ Password: hash,
+ Path: string(path),
+ })
+
+ return err
+}
diff --git a/pkg/components/auth/controller_test.go b/pkg/components/auth/controller_test.go
index 33aa901cf701dcc8675776c97f36b57e7a202882..6b4e3cd03ecdb792c56141c5e68ba1a53b343815 100644
--- a/pkg/components/auth/controller_test.go
+++ b/pkg/components/auth/controller_test.go
@@ -9,6 +9,7 @@ "testing"
"github.com/samber/lo"
+ "git.sr.ht/~gabrielgio/img/pkg/components/user"
"git.sr.ht/~gabrielgio/img/pkg/ext"
"git.sr.ht/~gabrielgio/img/pkg/testkit"
)
@@ -43,11 +44,11 @@ mockUserRepository := &MockUserRepository{}
return &scene{
ctx: context.Background(),
mockRepository: mockUserRepository,
- controller: *NewController(mockUserRepository, key),
+ controller: *NewController(mockUserRepository, nil, key),
}
}
-func TestRegisterAndLogin(t *testing.T) {
+func TestInitialRegisterAndLogin(t *testing.T) {
testCases := []struct {
name string
username string
@@ -64,7 +65,7 @@ for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
scene := setUp()
- err := scene.controller.Register(scene.ctx, []byte(tc.username), tc.password)
+ err := scene.controller.InitialRegister(scene.ctx, []byte(tc.username), tc.password, []byte("/"))
testkit.TestFatalError(t, "Register", err)
userID := scene.mockRepository.GetLastId()
@@ -85,8 +86,8 @@ })
}
}
-func toUser(m *mockUser, _ int) *User {
- return &User{
+func toUser(m *mockUser, _ int) *user.User {
+ return &user.User{
ID: m.id,
Username: m.username,
}
@@ -96,7 +97,7 @@ func (m *MockUserRepository) GetLastId() uint {
return m.index
}
-func (m *MockUserRepository) List(ctx context.Context) ([]*User, error) {
+func (m *MockUserRepository) List(ctx context.Context) ([]*user.User, error) {
if m.err != nil {
return nil, m.err
}
@@ -104,7 +105,7 @@
return lo.Map(m.users, toUser), nil
}
-func (m *MockUserRepository) Get(ctx context.Context, id uint) (*User, error) {
+func (m *MockUserRepository) Get(ctx context.Context, id uint) (*user.User, error) {
if m.err != nil {
return nil, m.err
}
@@ -143,7 +144,7 @@ }
return nil, errors.New("Item not found")
}
-func (m *MockUserRepository) Create(ctx context.Context, createUser *CreateUser) (uint, error) {
+func (m *MockUserRepository) Create(ctx context.Context, createUser *user.CreateUser) (uint, error) {
if m.err != nil {
return 0, m.err
}
@@ -159,7 +160,7 @@
return m.index, nil
}
-func (m *MockUserRepository) Update(ctx context.Context, id uint, update *UpdateUser) error {
+func (m *MockUserRepository) Update(ctx context.Context, id uint, update *user.UpdateUser) error {
if m.err != nil {
return m.err
}
diff --git a/pkg/components/errors.go b/pkg/components/errors.go
new file mode 100644
index 0000000000000000000000000000000000000000..aedbe889b5f1e2d54616f746b859da359a288be5
--- /dev/null
+++ b/pkg/components/errors.go
@@ -0,0 +1,8 @@
+package components
+
+import "errors"
+
+var (
+ NotFound = errors.New("Not found")
+ InvlidaInput = errors.New("Invalid Input")
+)
diff --git a/pkg/components/media/model.go b/pkg/components/media/model.go
index 0e17e9231f4f7ded99d4d6fa6e866f34756a5514..1962a23e2716418d3919b66d253cdbbfc46d2de3 100644
--- a/pkg/components/media/model.go
+++ b/pkg/components/media/model.go
@@ -2,6 +2,7 @@ package media
import (
"context"
+ "strings"
"time"
)
@@ -57,3 +58,7 @@ GetEXIF(context.Context, uint) (*MediaEXIF, error)
CreateEXIF(context.Context, uint, *MediaEXIF) error
}
)
+
+func (m *Media) IsVideo() bool {
+ return strings.HasPrefix(m.MIMEType, "video")
+}
diff --git a/pkg/components/user/model.go b/pkg/components/user/model.go
index f957c39a95fb49179c319674a6c31827bebb695c..ce1b3a5986e2782ab36ae08dd05f02862f2d01a4 100644
--- a/pkg/components/user/model.go
+++ b/pkg/components/user/model.go
@@ -20,7 +20,7 @@
CreateUser struct {
Username string
Name string
- Password string
+ Password []byte
IsAdmin bool
Path string
}
@@ -29,5 +29,6 @@ Repository interface {
List(ctx context.Context) ([]*User, error)
Create(ctx context.Context, createUser *CreateUser) error
Update(ctx context.Context, id uint, updateUser *UpdateUser) error
+ Any(ctx context.Context) (bool, error)
}
)
diff --git a/pkg/database/sql/user.go b/pkg/database/sql/user.go
index 2d74162713c10e1ed292a1dc116404e862f77bd3..a02b67b669b46c634051daf69000a5a9044a0336 100644
--- a/pkg/database/sql/user.go
+++ b/pkg/database/sql/user.go
@@ -187,3 +187,18 @@ return result.Error
}
return nil
}
+
+func (u *UserRepository) Any(ctx context.Context) (bool, error) {
+ var exists bool
+ result := u.db.
+ WithContext(ctx).
+ Model(&User{}).
+ Select("count(id) > 0").
+ Find(&exists)
+
+ if result.Error != nil {
+ return false, result.Error
+ }
+
+ return exists, nil
+}
diff --git a/pkg/database/sql/user_test.go b/pkg/database/sql/user_test.go
index 875b8e6022b0b97c1fbe0b6c0363a4beb12912df..473ce032516e298768da908b7e64f84e4f705bce 100644
--- a/pkg/database/sql/user_test.go
+++ b/pkg/database/sql/user_test.go
@@ -12,7 +12,7 @@ "gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
- "git.sr.ht/~gabrielgio/img/pkg/components/auth"
+ "git.sr.ht/~gabrielgio/img/pkg/components/user"
)
func setup(t *testing.T) (*gorm.DB, func()) {
@@ -48,7 +48,7 @@ defer tearDown()
repository := NewUserRepository(db)
- id, err := repository.Create(context.Background(), &auth.CreateUser{
+ err := repository.Create(context.Background(), &user.CreateUser{
Username: "new_username",
Name: "new_name",
})
@@ -56,12 +56,12 @@ if err != nil {
t.Fatalf("Error creating: %s", err.Error())
}
- got, err := repository.Get(context.Background(), id)
+ got, err := repository.Get(context.Background(), 1)
if err != nil {
t.Fatalf("Error getting: %s", err.Error())
}
- want := &auth.User{
- ID: id,
+ want := &user.User{
+ ID: 1,
Username: "new_username",
Name: "new_name",
}
@@ -78,7 +78,7 @@ defer tearDown()
repository := NewUserRepository(db)
- id, err := repository.Create(context.Background(), &auth.CreateUser{
+ err := repository.Create(context.Background(), &user.CreateUser{
Username: "username",
Name: "name",
})
@@ -86,7 +86,7 @@ if err != nil {
t.Fatalf("Error creating user: %s", err.Error())
}
- err = repository.Update(context.Background(), id, &auth.UpdateUser{
+ err = repository.Update(context.Background(), 1, &user.UpdateUser{
Username: "new_username",
Name: "new_name",
})
@@ -94,12 +94,12 @@ if err != nil {
t.Fatalf("Error update user: %s", err.Error())
}
- got, err := repository.Get(context.Background(), id)
+ got, err := repository.Get(context.Background(), 1)
if err != nil {
t.Fatalf("Error getting user: %s", err.Error())
}
- want := &auth.User{
- ID: id,
+ want := &user.User{
+ ID: 1,
Username: "new_username",
Name: "new_name",
}
diff --git a/pkg/ext/middleware.go b/pkg/ext/middleware.go
index 771c0ac6cf729661c1a6c33967910d908c1411bc..649272e6714bfd5f0f85355c4f6b439eb1e6620f 100644
--- a/pkg/ext/middleware.go
+++ b/pkg/ext/middleware.go
@@ -4,6 +4,7 @@ import (
"encoding/base64"
"time"
+ "git.sr.ht/~gabrielgio/img/pkg/components/user"
"github.com/sirupsen/logrus"
"github.com/valyala/fasthttp"
)
@@ -54,7 +55,7 @@
func (a *AuthMiddleware) LoggedIn(next fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
path := string(ctx.Path())
- if path == "/login" {
+ if path == "/login" || path == "/initial" {
next(ctx)
return
}
@@ -87,3 +88,42 @@ Info("user recognized")
next(ctx)
}
}
+
+type InitialSetupMiddleware struct {
+ userRepository user.Repository
+}
+
+func NewInitialSetupMiddleware(userRepository user.Repository) *InitialSetupMiddleware {
+ return &InitialSetupMiddleware{
+ userRepository: userRepository,
+ }
+}
+
+func (i *InitialSetupMiddleware) Check(next fasthttp.RequestHandler) fasthttp.RequestHandler {
+ return func(ctx *fasthttp.RequestCtx) {
+ // if user has been set to context it is logged in already
+ _, ok := ctx.UserValue("token").(*Token)
+ if ok {
+ next(ctx)
+ return
+ }
+
+ path := string(ctx.Path())
+ if path == "/initial" {
+ next(ctx)
+ return
+ }
+
+ exists, err := i.userRepository.Any(ctx)
+ if err != nil {
+ InternalServerError(ctx, err)
+ return
+ }
+
+ if exists {
+ next(ctx)
+ return
+ }
+ ctx.Redirect("/initial", 307)
+ }
+}
diff --git a/pkg/view/auth.go b/pkg/view/auth.go
index d44424d64290d5877761bcc1229b9244551ca08b..3f9e4140365dbed89f490c6d39e066670fc3d1b4 100644
--- a/pkg/view/auth.go
+++ b/pkg/view/auth.go
@@ -68,10 +68,31 @@ func Index(ctx *fasthttp.RequestCtx) {
ctx.Redirect("/login", 307)
}
+func (v *AuthView) InitialRegisterView(ctx *fasthttp.RequestCtx) error {
+ return img.Render[interface{}](ctx, "register.html", nil)
+}
+
+func (v *AuthView) InitialRegister(ctx *fasthttp.RequestCtx) error {
+ username := ctx.FormValue("username")
+ password := ctx.FormValue("password")
+ path := ctx.FormValue("path")
+
+ err := v.userController.InitialRegister(ctx, username, password, path)
+ if err != nil {
+ return err
+ }
+
+ ctx.Redirect("/login", 307)
+ return nil
+}
+
func (v *AuthView) SetMyselfIn(r *ext.Router) {
r.GET("/login", v.LoginView)
r.POST("/login", v.Login)
r.GET("/logout", v.Logout)
r.POST("/logout", v.Logout)
+
+ r.GET("/initial", v.InitialRegisterView)
+ r.POST("/initial", v.InitialRegister)
}
diff --git a/templates/login.html b/templates/login.html
index f71d9d3d20b0ed31661aa7ccffc1f047c69c17cd..607faa101d1594e66bbf7c729880e4e42e80d25d 100644
--- a/templates/login.html
+++ b/templates/login.html
@@ -1,5 +1,5 @@
{{template "layout.html" .}}
-{{define "title"}} Register {{end}}
+{{define "title"}} Login {{end}}
{{define "content"}}
<form action="/login" method="post">
<div class="field">
diff --git a/templates/media.html b/templates/media.html
index 478d8aee45db8dd3950fd6d9876aaef035dcd82d..6302a573451a953b2a02275611a63377930d0db3 100644
--- a/templates/media.html
+++ b/templates/media.html
@@ -5,9 +5,15 @@ <div class="columns is-multiline">
{{range .Data.Medias}}
<div class="card">
<div class="card-image">
+ {{ if .IsVideo }}
+ <video controls muted="true" preload="metadata">
+ <source src="/media/image?path_hash={{.PathHash}}" type="{{.MIMEType}}">
+ </video>
+ {{ else }}
<figure class="image is-fit">
- <img src="/media/image?path_hash={{.PathHash}}">
+ <img src="/media/image?path_hash={{.PathHash}}">
</figure>
+ {{ end }}
</div>
</div>
{{end}}
diff --git a/templates/register.html b/templates/register.html
new file mode 100644
index 0000000000000000000000000000000000000000..b026d33a60605574f7c71e79bd5fc8b555e94eb9
--- /dev/null
+++ b/templates/register.html
@@ -0,0 +1,28 @@
+{{template "layout.html" .}}
+{{define "title"}} Initial Setup {{end}}
+{{define "content"}}
+<h1>Initial Setup</h1>
+<form action="/initial" method="post">
+ <div class="field">
+ <label class="label">Username</label>
+ <div class="control">
+ <input class="input" name="username" type="text">
+ </div>
+ </div>
+ <div class="field">
+ <label class="label">Password</label>
+ <div class="control">
+ <input class="input" name="password" type="password">
+ </div>
+ </div>
+ <div class="field">
+ <label class="label">Root folder</label>
+ <div class="control">
+ <input class="input" name="path" type="text">
+ </div>
+ </div>
+ <div class="field">
+ <input class="button is-pulled-right" value="Save" type="submit">
+ </div>
+</form>
+{{end}}