Compare commits

...

6 Commits

Author SHA1 Message Date
bf6ced948a Tweak frontend 2023-10-26 22:36:20 +02:00
442b145711 Static serve 2023-10-26 22:08:14 +02:00
3290837555 Ajax user save 2023-10-26 21:58:15 +02:00
50187f5a34 Set user password 2023-10-26 17:40:05 +02:00
dd671d561c Restructure again 2023-10-26 16:49:24 +02:00
7512d75a4d Readme 2023-10-26 16:03:20 +02:00
22 changed files with 364 additions and 123 deletions

View File

@ -9,6 +9,7 @@
- [Templating Examples](https://github.com/kataras/iris/tree/main/_examples/view)
- [Jet]
- [Source](https://github.com/CloudyKit/jet)
- [Docs](https://github.com/CloudyKit/jet/wiki)
- [Syntax reference](https://github.com/CloudyKit/jet/blob/master/docs/syntax.md)
## Howto
@ -16,6 +17,11 @@
- [Password Hashing (bcrypt)](https://gowebexamples.com/password-hashing/)
## Tools
- [Bombardier benchmarking](https://github.com/codesenberg/bombardier)
## Bombardier benchmark
Pandora - Jet templating engine
@ -56,3 +62,5 @@ Statistics Avg Stdev Max
others - 0
Throughput: 19.91MB/s
```

1
app/lib/auth/auth.go Normal file
View File

@ -0,0 +1 @@
package auth

41
app/lib/auth/passwords.go Normal file
View File

@ -0,0 +1,41 @@
package auth
import (
"regexp"
"golang.org/x/crypto/bcrypt"
)
// about bcrypt cost: https://docs.laminas.dev/laminas-crypt/password/#bcrypt
// bcrypt cost benchmarks: https://github.com/nsmithuk/bcrypt-cost-go
const BCRYPT_COST = 10
const MIN_PASSWORD_LENGTH = 10
func IsPasswordGoodEnough(password string) bool {
var re *regexp.Regexp
passwordBytes := []byte(password)
if len(password) < MIN_PASSWORD_LENGTH {
return false
}
re, _ = regexp.Compile("[a-z]")
if re.Find(passwordBytes) == nil {
return false
}
re, _ = regexp.Compile("[A-Z]")
if re.Find(passwordBytes) == nil {
return false
}
re, _ = regexp.Compile("[0-9]")
//lint:ignore S1008 allow early exit instead optimization
if re.Find(passwordBytes) == nil {
return false
}
return true
}
func HashPassword(password string, secretKey string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password+secretKey), BCRYPT_COST)
return string(bytes), err
}

View File

@ -1,4 +1,4 @@
package common
package cfg
import (
"fmt"
@ -19,6 +19,7 @@ type configStruct struct {
DisableSendMail bool `yaml:"disableSendMail"`
IsProduction bool `yaml:"isProduction"`
LoopDelay uint32 `yaml:"loopDelay"`
StaticDir string `yaml:"staticDir"`
} `yaml:"application"`
Database struct {
Host string `yaml:"host"`

View File

@ -1,7 +1,9 @@
package common
package db
import (
"fmt"
"iris-test/app/lib/cfg"
"iris-test/app/lib/logging"
"iris-test/app/repository"
"strconv"
"strings"
@ -20,18 +22,18 @@ var DBConn *gorm.DB
func InitDB() *gorm.DB {
var connectionString = strings.Join([]string{
"postgres://",
Config.Database.Username, ":",
Config.Database.Password, "@",
Config.Database.Host, ":",
Config.Database.Port, "/",
Config.Database.Name,
cfg.Config.Database.Username, ":",
cfg.Config.Database.Password, "@",
cfg.Config.Database.Host, ":",
cfg.Config.Database.Port, "/",
cfg.Config.Database.Name,
"?sslmode=disable",
"&TimeZone=UTC",
"&connect_timeout=", strconv.Itoa(DB_CONNECTION_TIMEOUT),
}, "")
var logLevel = gormLogger.Silent
if Config.Application.DebugSQL {
if cfg.Config.Application.DebugSQL {
logLevel = gormLogger.Info
}
@ -41,7 +43,7 @@ func InitDB() *gorm.DB {
})
if err != nil {
msg := fmt.Sprintf("Error connecting to database: %s. Terminating!", err)
Log.Error(msg)
logging.Error(msg)
panic(msg)
}

View File

@ -1,9 +1,10 @@
package common
package logging
import (
"encoding/json"
"fmt"
"io"
"iris-test/app/lib/cfg"
"os"
"github.com/sirupsen/logrus"
@ -28,9 +29,9 @@ func Warn(message string) {
}
func InitLogging() {
logLevel, err := logrus.ParseLevel(Config.Application.LogLevel)
logLevel, err := logrus.ParseLevel(cfg.Config.Application.LogLevel)
if err != nil {
panic(fmt.Sprintf("Invalid configured logLevel: %s\n", Config.Application.LogLevel))
panic(fmt.Sprintf("Invalid configured logLevel: %s\n", cfg.Config.Application.LogLevel))
}
Log.SetLevel(logLevel)
@ -42,14 +43,14 @@ func InitLogging() {
DisableQuote: true,
})
LogFile := Config.Application.LogFile
LogFile := cfg.Config.Application.LogFile
file, err := os.OpenFile(
LogFile,
os.O_CREATE|os.O_WRONLY|os.O_APPEND,
0655,
)
if err != nil {
msg := fmt.Sprintf("Failed to log to file %s: %s", Config.Application.LogFile, err)
msg := fmt.Sprintf("Failed to log to file %s: %s", cfg.Config.Application.LogFile, err)
Log.Warning(msg)
panic(msg)
}
@ -57,7 +58,7 @@ func InitLogging() {
mw := io.MultiWriter(os.Stdout, file)
Log.SetOutput(mw)
configJson, err := json.Marshal(Config)
configJson, err := json.Marshal(cfg.Config)
if err == nil {
Info(fmt.Sprintf("Using config: %s", configJson))
}

View File

@ -2,7 +2,9 @@ package main
import (
"fmt"
"iris-test/app/common"
"iris-test/app/lib/cfg"
"iris-test/app/lib/db"
"iris-test/app/lib/logging"
"iris-test/app/views"
"os"
"time"
@ -16,17 +18,17 @@ import (
var redisDB *redis.Database
func createSessionEngine() *sessions.Sessions {
redisAddr := fmt.Sprintf("%s:%d", common.Config.Redis.Host, common.Config.Redis.Port)
redisAddr := fmt.Sprintf("%s:%d", cfg.Config.Redis.Host, cfg.Config.Redis.Port)
redisDB = redis.New(redis.Config{
Network: "tcp",
Addr: redisAddr,
Timeout: time.Duration(30) * time.Second,
MaxActive: 10,
Username: common.Config.Redis.Username,
Password: common.Config.Redis.Password,
Database: common.Config.Redis.Database,
Prefix: common.Config.Redis.Prefix,
Username: cfg.Config.Redis.Username,
Password: cfg.Config.Redis.Password,
Database: cfg.Config.Redis.Database,
Prefix: cfg.Config.Redis.Prefix,
Driver: redis.GoRedis(), // defaults to this driver.
// To set a custom, existing go-redis client, use the "SetClient" method:
// Driver: redis.GoRedis().SetClient(customGoRedisClient)
@ -84,18 +86,23 @@ func createApp() *iris.Application {
accessLog := createAccessLog()
app := iris.New()
app.Logger().SetLevel(common.Config.Application.LogLevel)
app.Logger().SetLevel(cfg.Config.Application.LogLevel)
app.Use(sessionsEngine.Handler())
app.UseRouter(accessLog.Handler)
app.RegisterView(iris.Jet("./app/templates", ".jet").Reload(true))
views.CreateRouter(app)
if len(cfg.Config.Application.StaticDir) > 0 {
app.HandleDir("/static", iris.Dir(cfg.Config.Application.StaticDir))
}
return app
}
func main() {
common.InitCfg()
common.InitLogging()
common.InitDB()
cfg.InitCfg()
logging.InitLogging()
db.InitDB()
app := createApp()
defer redisDB.Close()

View File

@ -1,6 +1,8 @@
package models
import (
"iris-test/app/lib/auth"
"iris-test/app/lib/cfg"
"time"
)
@ -19,8 +21,12 @@ func (u *User) TableName() string {
return "users"
}
// func (u *User) SetPassword(password string) (string, error) {
// secretKey := Config.Application.SecretKey
// bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
// return string(bytes), err
// }
func (u *User) SetPassword(password string) error {
secretKey := cfg.Config.Application.SecretKey
hashedPassword, err := auth.HashPassword(password, secretKey)
if err != nil {
return err
}
u.Password = hashedPassword
return nil
}

View File

@ -2,32 +2,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/styles.css">
</head>
<body>
<div class="container mt-3">
<h1>{{ title }}</h1>
<ul class="nav">
<body>
<header class="navbar">
<div class="container">
<nav class="main navbar navbar-expand-lg">
<div class="container-fluid">
<a class="navbar-brand" href="/">
<img src="/static/example.png" alt="">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a href="/" class="nav-link">Frontpage</a>
<a href="/" class='nav-link {{ if .activePage == "index" }}active{{ end }}'>
Frontpage
</a>
</li>
<li class="nav-item">
<a href="/users" class="nav-link">Users</a>
<a href="/users" class='nav-link {{ if .activePage == "users" }}active{{ end }}'>
Users
</a>
</li>
<li class="nav-item">
<a href="/about" class="nav-link">About</a>
<a href="/about" class='nav-link {{ if .activePage == "about" }}active{{ end }}'>
About
</a>
</li>
</ul>
</div>
</div>
</nav>
</div>
</header>
<div class="container mt-3">
<main>
{{ yield mainContent() }}
</main>
</div>
{{ block jsBlock() }}{{ end }}
</body>
</html>

View File

@ -2,14 +2,48 @@
{{ block mainContent() }}
<p>Bacon ipsum dolor amet meatball ground round swine salami, drumstick alcatra ribeye pork loin corned beef tongue sausage tail jerky buffalo. Bacon capicola fatback turducken. Drumstick picanha jowl pork loin, meatball tri-tip doner pastrami beef ribs shankle chicken chuck turkey. Flank pig turducken hamburger shoulder, tongue alcatra prosciutto buffalo jowl. Frankfurter brisket ball tip, leberkas strip steak meatball burgdoggen jowl rump. Porchetta drumstick frankfurter pig cow spare ribs rump sausage chuck fatback andouille filet mignon jerky.</p>
<p>Chuck swine drumstick, fatback alcatra burgdoggen ball tip meatball andouille shoulder brisket turducken cupim spare ribs. Spare ribs strip steak alcatra tri-tip pork chop bresaola, t-bone cupim cow. Ball tip pork corned beef chislic buffalo. Cupim bacon doner turkey ball tip prosciutto andouille fatback corned beef. Meatball drumstick venison landjaeger filet mignon biltong, frankfurter chicken shank bacon flank strip steak ground round t-bone.</p>
<p>Meatloaf tri-tip biltong porchetta corned beef alcatra. Capicola chicken pastrami swine tri-tip, flank corned beef landjaeger picanha spare ribs drumstick chislic pork chop sirloin. Capicola landjaeger kevin prosciutto boudin. Porchetta shoulder ball tip, strip steak drumstick ham hock tri-tip frankfurter landjaeger. Cupim biltong bacon meatloaf chicken, porchetta picanha brisket strip steak andouille hamburger cow jowl shank fatback.</p>
<p>Turducken bacon pork loin capicola hamburger jerky tri-tip pork belly. Pork loin chicken turkey, pork chop filet mignon tri-tip prosciutto tenderloin beef ham hock chuck bacon. Kevin venison frankfurter porchetta ribeye, landjaeger pork loin shank. Salami andouille landjaeger pork loin pork tail filet mignon venison cow bresaola jerky alcatra boudin t-bone. Beef fatback ribeye turkey ground round corned beef. Beef alcatra flank hamburger.</p>
<p>Landjaeger shankle flank, cow hamburger biltong capicola pastrami drumstick. Boudin ball tip shank ground round, porchetta shankle spare ribs chuck chicken sirloin meatloaf. Pork loin pork frankfurter shank, capicola swine chicken strip steak prosciutto kevin burgdoggen beef pig. Sirloin leberkas andouille cow ham tongue drumstick jowl sausage t-bone pancetta turducken bacon. Jerky ham hock turducken pork belly, corned beef ribeye tri-tip andouille beef ribs pastrami filet mignon meatball shank rump salami. Spare ribs porchetta salami short ribs, ball tip cow pig ribeye corned beef venison tongue. Shoulder jowl capicola, strip steak prosciutto cow burgdoggen spare ribs.</p>
<p>Shoulder shankle t-bone, buffalo ribeye beef tail drumstick sausage pork belly pig landjaeger kevin. Ground round shoulder venison, chicken t-bone corned beef tongue flank filet mignon jowl drumstick tenderloin bresaola short loin pig. Swine flank beef corned beef ham hock boudin doner pig. Pork belly short ribs buffalo ham salami kielbasa.</p>
<p>Burgdoggen ground round sausage andouille chicken strip steak porchetta picanha. Pork t-bone shank porchetta leberkas capicola corned beef bresaola. Swine tenderloin beef ribs sirloin. Burgdoggen buffalo frankfurter, salami turkey biltong chislic bacon bresaola. Ground round turkey tri-tip flank tail buffalo tenderloin. Picanha turkey shankle jerky. Brisket beef ribs corned beef kielbasa buffalo.</p>
<p>Shoulder pastrami chislic picanha pork belly, tail pork venison pork loin jerky pig beef pancetta bacon. Venison beef t-bone, meatball strip steak cow ball tip short ribs flank. Burgdoggen capicola venison pork pancetta alcatra ham hock doner flank fatback cow. Strip steak hamburger landjaeger jowl burgdoggen. Pastrami strip steak jerky, flank tri-tip t-bone capicola ham brisket. Buffalo salami fatback, bresaola venison chuck turducken kielbasa tail kevin short loin. Drumstick bresaola shank fatback tri-tip burgdoggen, ball tip chislic ribeye.</p>
<p>Tenderloin rump shank, boudin ribeye spare ribs drumstick. Frankfurter tri-tip ribeye ground round. T-bone chuck spare ribs shankle, short ribs biltong ham hock beef burgdoggen hamburger doner bresaola tongue. Salami doner strip steak, pig swine bacon chicken pastrami ground round pancetta sausage short ribs ball tip. Chislic rump prosciutto frankfurter beef ribs pork drumstick alcatra sirloin andouille brisket capicola.</p>
<p>Rump ground round porchetta chislic, burgdoggen jerky frankfurter flank strip steak bacon shankle tongue. Shoulder strip steak biltong tri-tip, beef ribs shankle shank venison landjaeger pork. Capicola short loin picanha flank bacon shank. Strip steak ribeye swine, salami kevin landjaeger brisket.</p>
<p>Bacon ipsum dolor amet meatball ground round swine salami, drumstick alcatra ribeye pork loin corned beef tongue
sausage tail jerky buffalo. Bacon capicola fatback turducken. Drumstick picanha jowl pork loin, meatball tri-tip
doner pastrami beef ribs shankle chicken chuck turkey. Flank pig turducken hamburger shoulder, tongue alcatra
prosciutto buffalo jowl. Frankfurter brisket ball tip, leberkas strip steak meatball burgdoggen jowl rump. Porchetta
drumstick frankfurter pig cow spare ribs rump sausage chuck fatback andouille filet mignon jerky.</p>
<p>Chuck swine drumstick, fatback alcatra burgdoggen ball tip meatball andouille shoulder brisket turducken cupim spare
ribs. Spare ribs strip steak alcatra tri-tip pork chop bresaola, t-bone cupim cow. Ball tip pork corned beef chislic
buffalo. Cupim bacon doner turkey ball tip prosciutto andouille fatback corned beef. Meatball drumstick venison
landjaeger filet mignon biltong, frankfurter chicken shank bacon flank strip steak ground round t-bone.</p>
<p>Meatloaf tri-tip biltong porchetta corned beef alcatra. Capicola chicken pastrami swine tri-tip, flank corned beef
landjaeger picanha spare ribs drumstick chislic pork chop sirloin. Capicola landjaeger kevin prosciutto boudin.
Porchetta shoulder ball tip, strip steak drumstick ham hock tri-tip frankfurter landjaeger. Cupim biltong bacon
meatloaf chicken, porchetta picanha brisket strip steak andouille hamburger cow jowl shank fatback.</p>
<p>Turducken bacon pork loin capicola hamburger jerky tri-tip pork belly. Pork loin chicken turkey, pork chop filet
mignon tri-tip prosciutto tenderloin beef ham hock chuck bacon. Kevin venison frankfurter porchetta ribeye,
landjaeger pork loin shank. Salami andouille landjaeger pork loin pork tail filet mignon venison cow bresaola jerky
alcatra boudin t-bone. Beef fatback ribeye turkey ground round corned beef. Beef alcatra flank hamburger.</p>
<p>Landjaeger shankle flank, cow hamburger biltong capicola pastrami drumstick. Boudin ball tip shank ground round,
porchetta shankle spare ribs chuck chicken sirloin meatloaf. Pork loin pork frankfurter shank, capicola swine
chicken strip steak prosciutto kevin burgdoggen beef pig. Sirloin leberkas andouille cow ham tongue drumstick jowl
sausage t-bone pancetta turducken bacon. Jerky ham hock turducken pork belly, corned beef ribeye tri-tip andouille
beef ribs pastrami filet mignon meatball shank rump salami. Spare ribs porchetta salami short ribs, ball tip cow pig
ribeye corned beef venison tongue. Shoulder jowl capicola, strip steak prosciutto cow burgdoggen spare ribs.</p>
<p>Shoulder shankle t-bone, buffalo ribeye beef tail drumstick sausage pork belly pig landjaeger kevin. Ground round
shoulder venison, chicken t-bone corned beef tongue flank filet mignon jowl drumstick tenderloin bresaola short loin
pig. Swine flank beef corned beef ham hock boudin doner pig. Pork belly short ribs buffalo ham salami kielbasa.</p>
<p>Burgdoggen ground round sausage andouille chicken strip steak porchetta picanha. Pork t-bone shank porchetta leberkas
capicola corned beef bresaola. Swine tenderloin beef ribs sirloin. Burgdoggen buffalo frankfurter, salami turkey
biltong chislic bacon bresaola. Ground round turkey tri-tip flank tail buffalo tenderloin. Picanha turkey shankle
jerky. Brisket beef ribs corned beef kielbasa buffalo.</p>
<p>Shoulder pastrami chislic picanha pork belly, tail pork venison pork loin jerky pig beef pancetta bacon. Venison beef
t-bone, meatball strip steak cow ball tip short ribs flank. Burgdoggen capicola venison pork pancetta alcatra ham
hock doner flank fatback cow. Strip steak hamburger landjaeger jowl burgdoggen. Pastrami strip steak jerky, flank
tri-tip t-bone capicola ham brisket. Buffalo salami fatback, bresaola venison chuck turducken kielbasa tail kevin
short loin. Drumstick bresaola shank fatback tri-tip burgdoggen, ball tip chislic ribeye.</p>
<p>Tenderloin rump shank, boudin ribeye spare ribs drumstick. Frankfurter tri-tip ribeye ground round. T-bone chuck
spare ribs shankle, short ribs biltong ham hock beef burgdoggen hamburger doner bresaola tongue. Salami doner strip
steak, pig swine bacon chicken pastrami ground round pancetta sausage short ribs ball tip. Chislic rump prosciutto
frankfurter beef ribs pork drumstick alcatra sirloin andouille brisket capicola.</p>
<p>Rump ground round porchetta chislic, burgdoggen jerky frankfurter flank strip steak bacon shankle tongue. Shoulder
strip steak biltong tri-tip, beef ribs shankle shank venison landjaeger pork. Capicola short loin picanha flank
bacon shank. Strip steak ribeye swine, salami kevin landjaeger brisket.</p>
{{ end }}

View File

@ -4,8 +4,8 @@
{{ block mainContent() }}
<div class="row">
<form class="mb-5 col-4 ms-auto me-auto" method="post" action="/">
<div class="row mt-5 mb-5">
<form class="col-6" method="post" action="/">
<div class="mb-3">
<label class="form-label">Email address</label>
<input type="email" name="email" class="form-control" value="edkirin@gmail.com">
@ -19,11 +19,19 @@
Submit
</button>
</form>
</div>
</div>
<div class="lead">
<p>Bacon ipsum dolor amet leberkas kevin meatball pork loin beef ribs prosciutto, turducken bacon bresaola tri-tip. Strip steak flank shankle, sirloin short ribs shoulder meatball pork chop kevin ribeye jowl ham pork belly turducken jerky. Flank tongue short loin ham hock brisket turducken tail filet mignon cupim. Pork capicola buffalo kevin jowl chicken. Filet mignon brisket pig, landjaeger sausage cow fatback drumstick chicken buffalo tenderloin spare ribs.</p>
<p>Swine shankle porchetta pancetta. Buffalo chicken turducken ground round kevin shoulder, salami pig t-bone beef ribs tri-tip tongue pork belly doner. Landjaeger meatloaf short loin biltong. Alcatra tongue shankle, tri-tip pancetta porchetta tenderloin corned beef pastrami rump. Bresaola chislic beef kielbasa sausage, ball tip burgdoggen boudin capicola short loin tenderloin buffalo landjaeger.</p>
</div>
<div class="lead">
<p>Bacon ipsum dolor amet leberkas kevin meatball pork loin beef ribs prosciutto, turducken bacon bresaola tri-tip.
Strip steak flank shankle, sirloin short ribs shoulder meatball pork chop kevin ribeye jowl ham pork belly
turducken jerky. Flank tongue short loin ham hock brisket turducken tail filet mignon cupim. Pork capicola
buffalo kevin jowl chicken. Filet mignon brisket pig, landjaeger sausage cow fatback drumstick chicken buffalo
tenderloin spare ribs.</p>
<p>Swine shankle porchetta pancetta. Buffalo chicken turducken ground round kevin shoulder, salami pig t-bone beef
ribs tri-tip tongue pork belly doner. Landjaeger meatloaf short loin biltong. Alcatra tongue shankle, tri-tip
pancetta porchetta tenderloin corned beef pastrami rump. Bresaola chislic beef kielbasa sausage, ball tip
burgdoggen boudin capicola short loin tenderloin buffalo landjaeger.</p>
</div>
{{ end }}

View File

@ -2,27 +2,33 @@
{{ import "/components/table_component.jet" }}
{{ block mainContent() }}
<h3>Edit user</h3>
<h3>Edit user</h3>
<div class="row">
<form class="mb-5 col-4 ms-auto me-auto" method="post" action="{{ .currentPath }}">
<div class="row">
<form class="mb-5 col-4 ms-auto me-auto user-edit" method="post" action="{{ .currentPath }}">
<div class="mb-3">
<label class="form-label">First name</label>
<input type="text" name="first-name" class="form-control" value="{{ user.FirstName }}" required>
<input type="text" name="first-name" class="form-control" value="{{ .user.FirstName }}" required>
</div>
<div class="mb-3">
<label class="form-label">Last name</label>
<input type="text" name="last-name" class="form-control" value="{{ user.LastName }}" required>
<input type="text" name="last-name" class="form-control" value="{{ .user.LastName }}" required>
</div>
<div class="mb-3">
<label class="form-label">Email</label>
<input type="email" name="email" class="form-control" value="{{ user.Email }}" required>
<input type="email" name="email" class="form-control" value="{{ .user.Email }}" required>
</div>
<div class="mb-3">
<label class="form-label">Password</label>
<input type="text" name="password" class="form-control">
</div>
<div class="d-flex">
<a href="/users" class="btn btn-outline-secondary ms-auto me-2">
<a href="{{ .backlink }}" class="btn btn-outline-secondary ms-auto me-2">
Cancel
</a>
<button type="submit" class="btn btn-success">
@ -30,6 +36,59 @@
</button>
</div>
</form>
</div>
</div>
{{ end }}
{{ block jsBlock() }}
<script>
document.querySelector("form.user-edit").addEventListener("submit", (e) => {
e.preventDefault();
const form = e.currentTarget;
const formData = new FormData(form);
const postData = Object.fromEntries(formData.entries());
const url = "{{ .currentPath }}";
const backlink = "{{ .backlink }}";
console.log(postData)
fetch(url, {
method: "POST",
mode: "cors",
cache: "no-cache",
credentials: "same-origin",
headers: {
"Content-Type": "application/json",
},
redirect: "follow",
referrerPolicy: "no-referrer",
body: JSON.stringify(postData),
}).then(response => {
if (![200, 201, 400].includes(response.status)) {
throw new Error(response.statusText);
}
return response.json()
}).then(jsonData => {
console.log("response:", jsonData)
if (jsonData.success) {
window.location = backlink;
} else {
/*
if (jsonData.validationErrors) {
displayValidationErrors(jsonData.validationErrors);
doSpinner(this.btnSave, false);
} else {
throw new Error(jsonData.error);
}
*/
}
}).catch(err => {
console.error(err);
});
});
</script>
{{ end }}

View File

@ -3,7 +3,9 @@
{{ block mainContent() }}
<h3>Users</h3>
{{ yield usersTable(users=users) }}
<h3>Users</h3>
{{ yield usersTable(users=.users) }}
{{ end }}

View File

@ -3,7 +3,11 @@ package views
import "github.com/kataras/iris/v12"
func GetAboutPage(ctx iris.Context) {
if err := ctx.View("pages/about.jet"); err != nil {
vars := iris.Map{
"activePage": "about",
}
if err := ctx.View("pages/about.jet", vars); err != nil {
showError(ctx, err)
return
}

View File

@ -7,7 +7,11 @@ import (
)
func GetIndexPage(ctx iris.Context) {
if err := ctx.View("pages/index.jet"); err != nil {
vars := iris.Map{
"activePage": "index",
}
if err := ctx.View("pages/index.jet", vars); err != nil {
showError(ctx, err)
return
}

View File

@ -8,7 +8,7 @@ func CreateRouter(app *iris.Application) {
app.Get("/users", GetUsersPage)
app.Get("/users/{userId:uuid}", EditUserPage)
app.Post("/users/{userId:uuid}", SaveUserPage)
app.Post("/users/{userId:uuid}", SaveUser)
app.Get("/about", GetAboutPage)
}

View File

@ -1,15 +1,18 @@
package views
import (
"fmt"
"iris-test/app/lib/auth"
"iris-test/app/repository"
"github.com/kataras/iris/v12"
)
type editUserForm struct {
FirstName string `form:"first-name"`
LastName string `form:"last-name"`
Email string `form:"email"`
type editUserJSON struct {
FirstName string `json:"first-name"`
LastName string `json:"last-name"`
Email string `json:"email"`
Password string `json:"password"`
}
func GetUsersPage(ctx iris.Context) {
@ -24,9 +27,12 @@ func GetUsersPage(ctx iris.Context) {
isActive := true
users := userRepository.List(&repository.UserFilter{IsActive: &isActive}, &pagination, &ordering)
ctx.ViewData("users", users)
vars := iris.Map{
"activePage": "users",
"users": users,
}
if err := ctx.View("pages/users.jet"); err != nil {
if err := ctx.View("pages/users.jet", vars); err != nil {
showError(ctx, err)
return
}
@ -39,18 +45,23 @@ func EditUserPage(ctx iris.Context) {
filter := repository.UserFilter{Id: &userId}
user := userRepository.Get(&filter)
ctx.ViewData("user", user)
ctx.ViewData("currentPath", ctx.Path())
vars := iris.Map{
"activePage": "users",
"user": user,
"currentPath": ctx.Path(),
"backlink": "/users",
}
if err := ctx.View("pages/user-edit.jet"); err != nil {
if err := ctx.View("pages/user-edit.jet", vars); err != nil {
showError(ctx, err)
return
}
}
func SaveUserPage(ctx iris.Context) {
var form editUserForm
err := ctx.ReadForm(&form)
func SaveUser(ctx iris.Context) {
var postData editUserJSON
err := ctx.ReadJSON(&postData)
if err != nil {
ctx.StopWithError(iris.StatusBadRequest, err)
return
@ -62,11 +73,21 @@ func SaveUserPage(ctx iris.Context) {
filter := repository.UserFilter{Id: &userId}
user := userRepository.Get(&filter)
user.FirstName = form.FirstName
user.LastName = form.LastName
user.Email = form.Email
user.FirstName = postData.FirstName
user.LastName = postData.LastName
user.Email = postData.Email
if len(postData.Password) > 0 {
user.SetPassword(postData.Password)
fmt.Printf("Set password: %s\n", user.Password)
fmt.Printf("IsPasswordGoodEnough: %v\n", auth.IsPasswordGoodEnough(postData.Password))
}
userRepository.Save(user)
ctx.Redirect("/users")
response := iris.Map{
"success": true,
}
ctx.JSON(response)
}

View File

@ -5,6 +5,7 @@ application:
debugSQL: true
isProduction: false
loopDelay: 3000
staticDir: "./static"
database:
host: "localhost"

BIN
static/example.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

6
static/styles.css Normal file
View File

@ -0,0 +1,6 @@
header.navbar {
background-color: #e0e0e0;
}
header.navbar .navbar-brand img {
max-height: 80px;
}/*# sourceMappingURL=styles.css.map */

1
static/styles.css.map Normal file
View File

@ -0,0 +1 @@
{"version":3,"sources":["styles.scss","styles.css"],"names":[],"mappings":"AAAA;EACI,yBAAA;ACCJ;ADAI;EACI,gBAAA;ACER","file":"styles.css"}

6
static/styles.scss Normal file
View File

@ -0,0 +1,6 @@
header.navbar {
background-color: #e0e0e0;
.navbar-brand img {
max-height: 80px;
}
}