commit eaa1ecd55ee5c687a06723ccfd6b5485afb04d10 Author: Simon Vieille Date: Sat Sep 14 23:37:03 2024 +0200 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d7175f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/_data diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e69de29 diff --git a/cli/user/add/command.go b/cli/user/add/command.go new file mode 100644 index 0000000..dbc9cc5 --- /dev/null +++ b/cli/user/add/command.go @@ -0,0 +1,78 @@ +package add + +import ( + "errors" + "fmt" + "log" + "os" + "strings" + + "github.com/manifoldco/promptui" + "github.com/urfave/cli/v2" + "gitnet.fr/deblan/budget/database/manager" + "gitnet.fr/deblan/budget/database/model" +) + +type promptContent struct { + errorMsg string + label string +} + +func promptGetInput(pc promptContent) string { + validate := func(input string) error { + if len(input) <= 0 { + return errors.New(pc.errorMsg) + } + return nil + } + + templates := &promptui.PromptTemplates{ + Prompt: "{{ . }} ", + Valid: "{{ . | green }} ", + Invalid: "{{ . | red }} ", + Success: "{{ . | bold }} ", + } + + prompt := promptui.Prompt{ + Label: pc.label, + Templates: templates, + Validate: validate, + } + + result, err := prompt.Run() + if err != nil { + log.Fatal(err) + os.Exit(1) + } + + return strings.TrimSpace(result) +} + +func Command(cCtx *cli.Context) error { + username := promptGetInput(promptContent{ + "Please provide a username.", + "Username:", + }) + password := promptGetInput(promptContent{ + "Please provide a password.", + "Password:", + }) + displayName := promptGetInput(promptContent{ + "Please provide a display name.", + "Display name:", + }) + + var count int64 + db := manager.Get() + db.Db.Model(model.User{}).Where("username = ?", username).Count(&count) + + if count > 0 { + fmt.Print("User already exists!\n") + os.Exit(1) + } + + user := model.NewUser(username, password, displayName) + db.Db.Create(user) + + return nil +} diff --git a/cli/user/changepassword/command.go b/cli/user/changepassword/command.go new file mode 100644 index 0000000..0623809 --- /dev/null +++ b/cli/user/changepassword/command.go @@ -0,0 +1,78 @@ +package changepassword + +import ( + "errors" + "fmt" + "log" + "os" + "strings" + + "github.com/manifoldco/promptui" + "github.com/urfave/cli/v2" + "gitnet.fr/deblan/budget/database/manager" + "gitnet.fr/deblan/budget/database/model" +) + +type promptContent struct { + errorMsg string + label string +} + +func promptGetInput(pc promptContent) string { + validate := func(input string) error { + if len(input) <= 0 { + return errors.New(pc.errorMsg) + } + return nil + } + + templates := &promptui.PromptTemplates{ + Prompt: "{{ . }} ", + Valid: "{{ . | green }} ", + Invalid: "{{ . | red }} ", + Success: "{{ . | bold }} ", + } + + prompt := promptui.Prompt{ + Label: pc.label, + Templates: templates, + Validate: validate, + } + + result, err := prompt.Run() + if err != nil { + log.Fatal(err) + os.Exit(1) + } + + return strings.TrimSpace(result) +} + +func Command(cCtx *cli.Context) error { + username := promptGetInput(promptContent{ + "Please provide a username.", + "Username:", + }) + + var count int64 + db := manager.Get() + db.Db.Model(model.User{}).Where("username = ?", username).Count(&count) + + if count == 0 { + fmt.Printf("User does not exist!\n") + os.Exit(1) + } + + password := promptGetInput(promptContent{ + "Please provide a password.", + "Password:", + }) + + var user model.User + db.Db.Model(model.User{}).Where("username = ?", username).Find(&user) + + user.UpdatePassword(password) + db.Db.Save(user) + + return nil +} diff --git a/cli/user/delete/command.go b/cli/user/delete/command.go new file mode 100644 index 0000000..fd80cfe --- /dev/null +++ b/cli/user/delete/command.go @@ -0,0 +1,71 @@ +package delete + +import ( + "errors" + "fmt" + "log" + "os" + "strings" + + "github.com/manifoldco/promptui" + "github.com/urfave/cli/v2" + "gitnet.fr/deblan/budget/database/manager" + "gitnet.fr/deblan/budget/database/model" +) + +type promptContent struct { + errorMsg string + label string +} + +func promptGetInput(pc promptContent) string { + validate := func(input string) error { + if len(input) <= 0 { + return errors.New(pc.errorMsg) + } + return nil + } + + templates := &promptui.PromptTemplates{ + Prompt: "{{ . }} ", + Valid: "{{ . | green }} ", + Invalid: "{{ . | red }} ", + Success: "{{ . | bold }} ", + } + + prompt := promptui.Prompt{ + Label: pc.label, + Templates: templates, + Validate: validate, + } + + result, err := prompt.Run() + if err != nil { + log.Fatal(err) + os.Exit(1) + } + + return strings.TrimSpace(result) +} + +func Command(cCtx *cli.Context) error { + username := promptGetInput(promptContent{ + "Please provide a username.", + "Username:", + }) + + var count int64 + db := manager.Get() + db.Db.Model(model.User{}).Where("username = ?", username).Count(&count) + + if count == 0 { + fmt.Printf("User does not exist!\n") + os.Exit(1) + } + + var user model.User + db.Db.Model(model.User{}).Where("username = ?", username).Find(&user) + db.Db.Delete(user) + + return nil +} diff --git a/cmd/cli/cli.go b/cmd/cli/cli.go new file mode 100644 index 0000000..e6a19f9 --- /dev/null +++ b/cmd/cli/cli.go @@ -0,0 +1,37 @@ +package main + +import ( + "flag" + "log" + "os" + + "github.com/urfave/cli/v2" + userAdd "gitnet.fr/deblan/budget/cli/user/add" + userChangePassword "gitnet.fr/deblan/budget/cli/user/changepassword" + userDelete "gitnet.fr/deblan/budget/cli/user/delete" + "gitnet.fr/deblan/budget/config" + "gitnet.fr/deblan/budget/database/manager" +) + +func main() { + ini := flag.String("c", "config.ini", "Path to config.ini") + config.Get().Load(*ini) + manager.Get().AutoMigrate() + + app := &cli.App{ + Commands: []*cli.Command{ + { + Name: "user", + Subcommands: []*cli.Command{ + {Name: "add", Action: userAdd.Command}, + {Name: "delete", Action: userDelete.Command}, + {Name: "change-password", Action: userChangePassword.Command}, + }, + }, + }, + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} diff --git a/cmd/server/server.go b/cmd/server/server.go new file mode 100644 index 0000000..8b51a2e --- /dev/null +++ b/cmd/server/server.go @@ -0,0 +1,37 @@ +package main + +import ( + "errors" + "flag" + "fmt" + "log" + "net/http" + "text/template" + + "github.com/gorilla/sessions" + "github.com/labstack/echo-contrib/session" + "github.com/labstack/echo/v4" + "gitnet.fr/deblan/budget/config" + "gitnet.fr/deblan/budget/database/manager" + "gitnet.fr/deblan/budget/database/model" + "gitnet.fr/deblan/budget/web/router" +) + +type TemplateRenderer struct { + templates *template.Template +} + +func main() { + ini := flag.String("c", "config.ini", "Path to config.ini") + conf := config.Get() + conf.Load(*ini) + manager.Get().Db.AutoMigrate(&model.User{}) + + e := echo.New() + e.Use(session.Middleware(sessions.NewCookieStore([]byte("secret")))) + router.RegisterControllers(e) + + if err := e.Start(fmt.Sprintf("%s:%d", conf.Server.Address, conf.Server.Port)); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Fatal(err) + } +} diff --git a/config.ini b/config.ini new file mode 100644 index 0000000..bc856d3 --- /dev/null +++ b/config.ini @@ -0,0 +1,12 @@ +[server] +port = 1324 +address = "127.0.0.1" + +[security] +secret = "e93865c991358ff7a14f9781fa33ba4f28c33bb8d1cf3490ce6fd68521513536" + +[log] +level = "debug" + +[database] +dsn = "root:root@tcp(127.0.0.1:3306)/budget" diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..f9b1664 --- /dev/null +++ b/config/config.go @@ -0,0 +1,55 @@ +package config + +import ( + "log" + + "gopkg.in/ini.v1" +) + +type database struct { + Dsn string +} + +type logging struct { + Level string +} + +type security struct { + Secret string +} + +type server struct { + Address string + Port int +} + +type Config struct { + Database database + Log logging + Security security + Server server +} + +var config *Config + +func Get() *Config { + if config == nil { + config = &Config{} + } + + return config +} + +func (c *Config) Load(file string) { + cfg, err := ini.Load(file) + + if err != nil { + log.Fatal(err) + } + + config.Database.Dsn = cfg.Section("database").Key("dsn").String() + config.Log.Level = cfg.Section("log").Key("level").String() + config.Security.Secret = cfg.Section("security").Key("secret").String() + config.Server.Address = cfg.Section("server").Key("address").String() + config.Server.Port, _ = cfg.Section("server").Key("port").Int() +} diff --git a/database/manager/manager.go b/database/manager/manager.go new file mode 100644 index 0000000..6489fa0 --- /dev/null +++ b/database/manager/manager.go @@ -0,0 +1,60 @@ +package manager + +import ( + "log" + "os" + "time" + + "gitnet.fr/deblan/budget/config" + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/logger" + lg "gorm.io/gorm/logger" +) + +type Manager struct { + Db *gorm.DB +} + +var manager *Manager + +func Get() *Manager { + if manager == nil { + manager = &Manager{} + config := config.Get() + dsn := config.Database.Dsn + + var logLevel lg.LogLevel + + if config.Log.Level == "debug" { + logLevel = lg.Info + } else if config.Log.Level == "warning" { + logLevel = lg.Warn + } else if config.Log.Level == "quiet" { + logLevel = lg.Silent + } else { + logLevel = lg.Silent + } + + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ + Logger: logger.New( + log.New(os.Stdout, "\r\n", log.LstdFlags), + lg.Config{ + SlowThreshold: time.Second, + LogLevel: lg.LogLevel(logLevel), + IgnoreRecordNotFoundError: true, + ParameterizedQueries: true, + Colorful: true, + }, + ), + }) + + if err != nil { + log.Fatal(err) + } + + manager.Db = db + } + + return manager +} diff --git a/database/model/user.go b/database/model/user.go new file mode 100644 index 0000000..819a578 --- /dev/null +++ b/database/model/user.go @@ -0,0 +1,69 @@ +package model + +import ( + "time" + + "github.com/labstack/echo-contrib/session" + "github.com/labstack/echo/v4" + "gitnet.fr/deblan/budget/database/manager" + "golang.org/x/crypto/bcrypt" +) + +type User struct { + ID uint `gorm:"primaryKey"` + Username string `gorm:"unique"` + Password string + DisplayName string + CreatedAt time.Time + UpdatedAt time.Time +} + +func NewUser(username, password, displayName string) *User { + user := User{ + Username: username, + DisplayName: displayName, + } + + user.UpdatePassword(password) + + return &user +} + +func (u *User) UpdatePassword(password string) { + hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), 1000) + + u.Password = string(hashedPassword) +} + +func (u *User) HasPassword(password string) bool { + return bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)) == nil +} + +func LoadSessionUser(c echo.Context) *User { + sess, err := session.Get("session", c) + + if err != nil { + return nil + } + + value := sess.Values["user"] + + if value == nil { + return nil + } + + id := value.(uint) + + var count int64 + db := manager.Get() + db.Db.Model(User{}).Where("id = ?", id).Count(&count) + + if count == 0 { + return nil + } + + var user User + manager.Get().Db.Model(User{}).Where("id = ?", id).Find(&user) + + return &user +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8d6a2bc --- /dev/null +++ b/go.mod @@ -0,0 +1,40 @@ +module gitnet.fr/deblan/budget + +go 1.23.0 + +require ( + github.com/manifoldco/promptui v0.9.0 + github.com/urfave/cli/v2 v2.27.4 + golang.org/x/crypto v0.27.0 + gopkg.in/ini.v1 v1.67.0 + gorm.io/driver/mysql v1.5.7 + gorm.io/gorm v1.25.12 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/a-h/templ v0.2.778 // indirect + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/gorilla/context v1.1.2 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect + github.com/gorilla/sessions v1.4.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/labstack/echo-contrib v0.17.1 // indirect + github.com/labstack/echo/v4 v4.12.0 // indirect + github.com/labstack/gommon v0.4.2 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/stretchr/testify v1.9.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect + golang.org/x/time v0.5.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..804a522 --- /dev/null +++ b/go.sum @@ -0,0 +1,90 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/a-h/templ v0.2.778 h1:VzhOuvWECrwOec4790lcLlZpP4Iptt5Q4K9aFxQmtaM= +github.com/a-h/templ v0.2.778/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= +github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= +github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/labstack/echo-contrib v0.17.1 h1:7I/he7ylVKsDUieaGRZ9XxxTYOjfQwVzHzUYrNykfCU= +github.com/labstack/echo-contrib v0.17.1/go.mod h1:SnsCZtwHBAZm5uBSAtQtXQHI3wqEA73hvTn0bYMKnZA= +github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= +github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8= +github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo= +gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM= +gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= diff --git a/view/template/auth/login.templ b/view/template/auth/login.templ new file mode 100644 index 0000000..4c968a5 --- /dev/null +++ b/view/template/auth/login.templ @@ -0,0 +1,49 @@ +package auth + +import "gitnet.fr/deblan/budget/view/template" + +templ Page(hasError bool) { + + + @template.Head("Login") + +
+
+
+
+
+
+
+
+ if hasError { +
+ Mauvais identifiants. +
+ } + +
+ + +
+ +
+ + +
+ +
+ +
+
+
+
+
+
+
+
+
+ + @template.JS() + + +} diff --git a/view/template/auth/login_templ.go b/view/template/auth/login_templ.go new file mode 100644 index 0000000..e71a061 --- /dev/null +++ b/view/template/auth/login_templ.go @@ -0,0 +1,63 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.747 +package auth + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "gitnet.fr/deblan/budget/view/template" + +func Page(hasError bool) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = template.Head("Login").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if hasError { + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 4) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = template.JS().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 5) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} diff --git a/view/template/auth/login_templ.txt b/view/template/auth/login_templ.txt new file mode 100644 index 0000000..885bf91 --- /dev/null +++ b/view/template/auth/login_templ.txt @@ -0,0 +1,5 @@ + +
+
Mauvais identifiants.
+
+ diff --git a/view/template/base.templ b/view/template/base.templ new file mode 100644 index 0000000..d19fabc --- /dev/null +++ b/view/template/base.templ @@ -0,0 +1,20 @@ +package template + +templ Head(title string) { + + + + { title } + + + +} + +templ JS() { + + +} diff --git a/view/template/base_templ.go b/view/template/base_templ.go new file mode 100644 index 0000000..edb70f1 --- /dev/null +++ b/view/template/base_templ.go @@ -0,0 +1,74 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.747 +package template + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +func Head(title string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/template/base.templ`, Line: 7, Col: 16} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} + +func JS() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var3 := templ.GetChildren(ctx) + if templ_7745c5c3_Var3 == nil { + templ_7745c5c3_Var3 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} diff --git a/view/template/base_templ.txt b/view/template/base_templ.txt new file mode 100644 index 0000000..607eaeb --- /dev/null +++ b/view/template/base_templ.txt @@ -0,0 +1,3 @@ + + + diff --git a/view/template/home/page.templ b/view/template/home/page.templ new file mode 100644 index 0000000..759d656 --- /dev/null +++ b/view/template/home/page.templ @@ -0,0 +1,15 @@ +package home + +import "gitnet.fr/deblan/budget/view/template" + +templ Page() { + + + @template.Head("Login") + + Home! + + @template.JS() + + +} diff --git a/view/template/home/page_templ.go b/view/template/home/page_templ.go new file mode 100644 index 0000000..4382979 --- /dev/null +++ b/view/template/home/page_templ.go @@ -0,0 +1,53 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.747 +package home + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "gitnet.fr/deblan/budget/view/template" + +func Page() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 1) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = template.Head("Login").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 2) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = template.JS().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.WriteWatchModeString(templ_7745c5c3_Buffer, 3) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return templ_7745c5c3_Err + }) +} diff --git a/view/template/home/page_templ.txt b/view/template/home/page_templ.txt new file mode 100644 index 0000000..6b234e9 --- /dev/null +++ b/view/template/home/page_templ.txt @@ -0,0 +1,3 @@ + +Home! + diff --git a/view/view.go b/view/view.go new file mode 100644 index 0000000..a5f8f83 --- /dev/null +++ b/view/view.go @@ -0,0 +1,17 @@ +package view + +import ( + "github.com/a-h/templ" + "github.com/labstack/echo/v4" +) + +func Render(ctx echo.Context, statusCode int, t templ.Component) error { + buf := templ.GetBuffer() + defer templ.ReleaseBuffer(buf) + + if err := t.Render(ctx.Request().Context(), buf); err != nil { + return err + } + + return ctx.HTML(statusCode, buf.String()) +} diff --git a/web/controller/auth/controller.go b/web/controller/auth/controller.go new file mode 100644 index 0000000..c44069f --- /dev/null +++ b/web/controller/auth/controller.go @@ -0,0 +1,63 @@ +package auth + +import ( + "net/http" + + "github.com/gorilla/sessions" + "github.com/labstack/echo-contrib/session" + "github.com/labstack/echo/v4" + "gitnet.fr/deblan/budget/database/manager" + "gitnet.fr/deblan/budget/database/model" + "gitnet.fr/deblan/budget/view" + "gitnet.fr/deblan/budget/view/template/auth" +) + +type Controller struct { +} + +func New(e *echo.Echo) *Controller { + c := Controller{} + + e.GET("/login", c.LoginGet) + e.POST("/login", c.LoginPost) + e.GET("/logout", c.LogoutGet) + + return &c +} + +func (ctrl *Controller) LoginGet(c echo.Context) error { + return view.Render(c, 200, auth.Page(false)) +} + +func (ctrl *Controller) LoginPost(c echo.Context) error { + username := c.FormValue("username") + password := c.FormValue("password") + + var count int64 + db := manager.Get() + db.Db.Model(model.User{}).Where("username = ?", username).Count(&count) + + if count > 0 { + var user model.User + db.Db.Model(model.User{}).Where("username = ?", username).Find(&user) + + if user.HasPassword(password) { + sess, _ := session.Get("session", c) + sess.Options = &sessions.Options{ + Path: "/", + MaxAge: 86400 * 7, + HttpOnly: true, + } + sess.Values["user"] = user.ID + sess.Save(c.Request(), c.Response()) + + return c.Redirect(302, "/") + } + } + + return view.Render(c, 200, auth.Page(true)) +} + +func (ctrl *Controller) LogoutGet(c echo.Context) error { + return c.String(http.StatusOK, "Hello, World!") +} diff --git a/web/controller/home/controller.go b/web/controller/home/controller.go new file mode 100644 index 0000000..76dee51 --- /dev/null +++ b/web/controller/home/controller.go @@ -0,0 +1,27 @@ +package home + +import ( + "github.com/labstack/echo/v4" + "gitnet.fr/deblan/budget/database/model" + "gitnet.fr/deblan/budget/view" + "gitnet.fr/deblan/budget/view/template/home" +) + +type Controller struct { +} + +func New(e *echo.Echo) *Controller { + c := Controller{} + + e.GET("/", c.HomeGet) + + return &c +} + +func (ctrl *Controller) HomeGet(c echo.Context) error { + if nil == model.LoadSessionUser(c) { + return c.Redirect(302, "/login") + } + + return view.Render(c, 200, home.Page()) +} diff --git a/web/router/router.go b/web/router/router.go new file mode 100644 index 0000000..cd4c856 --- /dev/null +++ b/web/router/router.go @@ -0,0 +1,12 @@ +package router + +import ( + "github.com/labstack/echo/v4" + "gitnet.fr/deblan/budget/web/controller/auth" + "gitnet.fr/deblan/budget/web/controller/home" +) + +func RegisterControllers(e *echo.Echo) { + auth.New(e) + home.New(e) +}