This commit is contained in:
Simon Vieille 2024-09-14 23:37:03 +02:00
commit eaa1ecd55e
26 changed files with 1032 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/_data

0
Makefile Normal file
View file

78
cli/user/add/command.go Normal file
View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

37
cmd/cli/cli.go Normal file
View file

@ -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)
}
}

37
cmd/server/server.go Normal file
View file

@ -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)
}
}

12
config.ini Normal file
View file

@ -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"

55
config/config.go Normal file
View file

@ -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()
}

View file

@ -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
}

69
database/model/user.go Normal file
View file

@ -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
}

40
go.mod Normal file
View file

@ -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
)

90
go.sum Normal file
View file

@ -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=

View file

@ -0,0 +1,49 @@
package auth
import "gitnet.fr/deblan/budget/view/template"
templ Page(hasError bool) {
<!doctype html>
<html>
@template.Head("Login")
<body>
<section class="vh-100 gradient-custom">
<div class="container py-5 h-100">
<div class="row d-flex justify-content-center align-items-center h-100">
<div class="col-12 col-md-8 col-lg-6 col-xl-5">
<div class="card bg-dark text-white" style="border-radius: 1rem;">
<div class="card-body p-5">
<div class="mb-md-5 mt-md-4 pb-5">
<form action="/login" method="POST">
if hasError {
<div class="alert alert-danger">
Mauvais identifiants.
</div>
}
<div class="form-outline form-white mb-4">
<label class="form-label" for="username">Nom d'utilisateur</label>
<input type="text" name="username" id="username" class="form-control form-control-lg" />
</div>
<div class="form-outline form-white mb-4">
<label class="form-label" for="password">Mot de passe</label>
<input type="password" name="password" id="password" class="form-control form-control-lg" />
</div>
<div class="d-grid gap-2">
<input class="btn btn-lg btn-primary" type="submit" value="Login" />
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
@template.JS()
</body>
</html>
}

View file

@ -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
})
}

View file

@ -0,0 +1,5 @@
<!doctype html><html>
<body><section class=\"vh-100 gradient-custom\"><div class=\"container py-5 h-100\"><div class=\"row d-flex justify-content-center align-items-center h-100\"><div class=\"col-12 col-md-8 col-lg-6 col-xl-5\"><div class=\"card bg-dark text-white\" style=\"border-radius: 1rem;\"><div class=\"card-body p-5\"><div class=\"mb-md-5 mt-md-4 pb-5\"><form action=\"/login\" method=\"POST\">
<div class=\"alert alert-danger\">Mauvais identifiants.</div>
<div class=\"form-outline form-white mb-4\"><label class=\"form-label\" for=\"username\">Nom d'utilisateur</label> <input type=\"text\" name=\"username\" id=\"username\" class=\"form-control form-control-lg\"></div><div class=\"form-outline form-white mb-4\"><label class=\"form-label\" for=\"password\">Mot de passe</label> <input type=\"password\" name=\"password\" id=\"password\" class=\"form-control form-control-lg\"></div><div class=\"d-grid gap-2\"><input class=\"btn btn-lg btn-primary\" type=\"submit\" value=\"Login\"></div></form></div></div></div></div></div></div></section>
</body></html>

20
view/template/base.templ Normal file
View file

@ -0,0 +1,20 @@
package template
templ Head(title string) {
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{ title }</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<style>
.gradient-custom {
background: linear-gradient(to right, rgba(106, 17, 203, 1), rgba(37, 117, 252, 1))
}
</style>
</head>
}
templ JS() {
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
<script src="main.js"></script>
}

View file

@ -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
})
}

View file

@ -0,0 +1,3 @@
<head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>
</title><link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN\" crossorigin=\"anonymous\"><style>\n\t\t\t.gradient-custom {\n\t\t\t\tbackground: linear-gradient(to right, rgba(106, 17, 203, 1), rgba(37, 117, 252, 1))\n\t\t\t}\n\t\t</style></head>
<script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js\" integrity=\"sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL\" crossorigin=\"anonymous\"></script><script src=\"main.js\"></script>

View file

@ -0,0 +1,15 @@
package home
import "gitnet.fr/deblan/budget/view/template"
templ Page() {
<!doctype html>
<html>
@template.Head("Login")
<body>
Home!
@template.JS()
</body>
</html>
}

View file

@ -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
})
}

View file

@ -0,0 +1,3 @@
<!doctype html><html>
<body>Home!
</body></html>

17
view/view.go Normal file
View file

@ -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())
}

View file

@ -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!")
}

View file

@ -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())
}

12
web/router/router.go Normal file
View file

@ -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)
}