Compare commits
11 Commits
login
...
bf6ced948a
| Author | SHA1 | Date | |
|---|---|---|---|
| bf6ced948a | |||
| 442b145711 | |||
| 3290837555 | |||
| 50187f5a34 | |||
| dd671d561c | |||
| 7512d75a4d | |||
| c1997aaab5 | |||
| 45f6f19441 | |||
| 2b13292e3b | |||
| da3deedb0c | |||
| 7024cd0de9 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
/.vscode
|
||||
/__debug*
|
||||
/build
|
||||
/tmp
|
||||
/config.yaml
|
||||
|
||||
6
Makefile
6
Makefile
@ -1,2 +1,8 @@
|
||||
EXEC=iris-test
|
||||
|
||||
run:
|
||||
@air
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
@go build -ldflags "-s -w" -o ./build/${EXEC} ./app/main.go
|
||||
|
||||
53
README.md
53
README.md
@ -9,5 +9,58 @@
|
||||
- [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
|
||||
|
||||
- [Password Hashing (bcrypt)](https://gowebexamples.com/password-hashing/)
|
||||
|
||||
|
||||
## Tools
|
||||
|
||||
- [Bombardier benchmarking](https://github.com/codesenberg/bombardier)
|
||||
|
||||
|
||||
## Bombardier benchmark
|
||||
|
||||
Pandora - Jet templating engine
|
||||
|
||||
```
|
||||
eden@pandora:[~/apps/sandbox/golang/iris-web-framework]: bombardier -c 100 -d 10s -l http://localhost:8000 ±[A1][main]
|
||||
Bombarding http://localhost:8000 for 10s using 100 connection(s)
|
||||
Done!
|
||||
Statistics Avg Stdev Max
|
||||
Reqs/sec 10542.16 2631.23 17296.86
|
||||
Latency 9.50ms 3.22ms 42.93ms
|
||||
Latency Distribution
|
||||
50% 8.77ms
|
||||
75% 11.86ms
|
||||
90% 15.31ms
|
||||
95% 17.86ms
|
||||
99% 23.90ms
|
||||
HTTP codes:
|
||||
1xx - 0, 2xx - 105258, 3xx - 0, 4xx - 0, 5xx - 0
|
||||
others - 0
|
||||
Throughput: 28.08MB/s
|
||||
```
|
||||
```
|
||||
eden@pandora:[~/apps/sandbox/golang/iris-web-framework]: bombardier -c 100 -d 10s -l http://localhost:8000/users ±[A1][main]
|
||||
Bombarding http://localhost:8000/users for 10s using 100 connection(s)
|
||||
Done!
|
||||
Statistics Avg Stdev Max
|
||||
Reqs/sec 1096.26 427.09 3211.54
|
||||
Latency 91.08ms 80.06ms 471.41ms
|
||||
Latency Distribution
|
||||
50% 78.37ms
|
||||
75% 156.58ms
|
||||
90% 191.60ms
|
||||
95% 223.49ms
|
||||
99% 309.59ms
|
||||
HTTP codes:
|
||||
1xx - 0, 2xx - 11060, 3xx - 0, 4xx - 0, 5xx - 0
|
||||
others - 0
|
||||
Throughput: 19.91MB/s
|
||||
```
|
||||
|
||||
|
||||
|
||||
1
app/lib/auth/auth.go
Normal file
1
app/lib/auth/auth.go
Normal file
@ -0,0 +1 @@
|
||||
package auth
|
||||
41
app/lib/auth/passwords.go
Normal file
41
app/lib/auth/passwords.go
Normal 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
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package main
|
||||
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"`
|
||||
@ -1,7 +1,9 @@
|
||||
package main
|
||||
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)
|
||||
}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
package main
|
||||
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))
|
||||
}
|
||||
32
app/main.go
32
app/main.go
@ -2,6 +2,9 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"iris-test/app/lib/cfg"
|
||||
"iris-test/app/lib/db"
|
||||
"iris-test/app/lib/logging"
|
||||
"iris-test/app/views"
|
||||
"os"
|
||||
"time"
|
||||
@ -15,32 +18,32 @@ import (
|
||||
var redisDB *redis.Database
|
||||
|
||||
func createSessionEngine() *sessions.Sessions {
|
||||
redisAddr := fmt.Sprintf("%s:%d", Config.Redis.Host, 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: Config.Redis.Username,
|
||||
Password: Config.Redis.Password,
|
||||
Database: Config.Redis.Database,
|
||||
Prefix: 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)
|
||||
})
|
||||
|
||||
sessions_engine := sessions.New(sessions.Config{
|
||||
sessionsEngine := sessions.New(sessions.Config{
|
||||
Cookie: "_session_id",
|
||||
Expires: 0, // defaults to 0: unlimited life. Another good value is: 45 * time.Minute,
|
||||
AllowReclaim: true,
|
||||
CookieSecureTLS: true,
|
||||
})
|
||||
|
||||
sessions_engine.UseDatabase(redisDB)
|
||||
sessionsEngine.UseDatabase(redisDB)
|
||||
|
||||
return sessions_engine
|
||||
return sessionsEngine
|
||||
}
|
||||
|
||||
func createAccessLog() *accesslog.AccessLog {
|
||||
@ -83,18 +86,23 @@ func createApp() *iris.Application {
|
||||
accessLog := createAccessLog()
|
||||
|
||||
app := iris.New()
|
||||
app.Logger().SetLevel(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() {
|
||||
InitCfg()
|
||||
InitLogging()
|
||||
InitDB()
|
||||
cfg.InitCfg()
|
||||
logging.InitLogging()
|
||||
db.InitDB()
|
||||
|
||||
app := createApp()
|
||||
defer redisDB.Close()
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"iris-test/app/lib/auth"
|
||||
"iris-test/app/lib/cfg"
|
||||
"time"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Id string `gorm:"type(uuid);unique"`
|
||||
@ -16,3 +20,13 @@ type User struct {
|
||||
func (u *User) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ type UsersRepository struct {
|
||||
}
|
||||
|
||||
type UserFilter struct {
|
||||
Id *string
|
||||
IsActive *bool
|
||||
}
|
||||
|
||||
@ -22,9 +23,14 @@ func CreateUsersRepository(db *gorm.DB) *UsersRepository {
|
||||
func applyFilter(db *gorm.DB, filter *UserFilter) *gorm.DB {
|
||||
query := db
|
||||
|
||||
// if filter.State != "" {
|
||||
// query = query.Where("state = ?", filter.State)
|
||||
// }
|
||||
if filter.Id != nil {
|
||||
query = query.Where("id = ?", filter.Id)
|
||||
}
|
||||
|
||||
if filter.IsActive != nil {
|
||||
query = query.Where(map[string]interface{}{"is_active": filter.IsActive})
|
||||
}
|
||||
|
||||
// if filter.SendAt_lt != nil {
|
||||
// query = query.Where("send_at < ?", filter.SendAt_lt)
|
||||
// }
|
||||
@ -69,3 +75,18 @@ func (repository *UsersRepository) List(filter *UserFilter, pagination *Paginati
|
||||
|
||||
return &users
|
||||
}
|
||||
|
||||
func (repository *UsersRepository) Get(filter *UserFilter) *models.User {
|
||||
var user models.User
|
||||
|
||||
query := repository.db.Model(&models.User{})
|
||||
query = applyFilter(query, filter)
|
||||
query.First(&user)
|
||||
|
||||
return &user
|
||||
}
|
||||
|
||||
func (repository *UsersRepository) Save(user *models.User) *models.User {
|
||||
repository.db.Save(user)
|
||||
return user
|
||||
}
|
||||
|
||||
@ -2,26 +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">
|
||||
<h1>{{ title }}</h1>
|
||||
|
||||
<p>
|
||||
<a href="/">Frontpage</a>
|
||||
<a href="/users">Users</a>
|
||||
<a href="/about">About</a>
|
||||
</p>
|
||||
<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 {{ if .activePage == "index" }}active{{ end }}'>
|
||||
Frontpage
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/users" class='nav-link {{ if .activePage == "users" }}active{{ end }}'>
|
||||
Users
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<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>
|
||||
@ -1,10 +1,19 @@
|
||||
{{ block usersTable(users) }}
|
||||
<p>blablabla</p>
|
||||
|
||||
<table class="table">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>First name</th>
|
||||
<th>Last name</th>
|
||||
<th>Email</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range users }}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/users/{{ .Id }}">{{ .Id }}</a>
|
||||
</td>
|
||||
<td>{{ .FirstName }}</td>
|
||||
<td>{{ .LastName }}</td>
|
||||
<td>{{ .Email }}</td>
|
||||
|
||||
@ -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 }}
|
||||
@ -4,26 +4,34 @@
|
||||
|
||||
{{ block mainContent() }}
|
||||
|
||||
<div class="row">
|
||||
<form class="mb-5 col-4 ms-auto me-auto" 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">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Password</label>
|
||||
<input type="text" name="password" class="form-control" value="tralala">
|
||||
</div>
|
||||
<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">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Password</label>
|
||||
<input type="text" name="password" class="form-control" value="tralala">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-success">
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success">
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
</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 }}
|
||||
94
app/templates/pages/user-edit.jet
Normal file
94
app/templates/pages/user-edit.jet
Normal file
@ -0,0 +1,94 @@
|
||||
{{ extends "/base/base.jet" }}
|
||||
{{ import "/components/table_component.jet" }}
|
||||
|
||||
|
||||
|
||||
{{ block mainContent() }}
|
||||
|
||||
<h3>Edit user</h3>
|
||||
|
||||
|
||||
<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>
|
||||
</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>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Email</label>
|
||||
<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="{{ .backlink }}" class="btn btn-outline-secondary ms-auto me-2">
|
||||
Cancel
|
||||
</a>
|
||||
<button type="submit" class="btn btn-success">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</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 }}
|
||||
@ -3,13 +3,9 @@
|
||||
|
||||
|
||||
{{ block mainContent() }}
|
||||
<ul>
|
||||
{{ range params1 }}
|
||||
<li>{{ . }}</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
|
||||
<h3>{{ title }}</h3>
|
||||
<h3>Users</h3>
|
||||
|
||||
{{ yield usersTable(users=.users) }}
|
||||
|
||||
{{ yield usersTable(users=users) }}
|
||||
{{ end }}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -7,5 +7,8 @@ func CreateRouter(app *iris.Application) {
|
||||
app.Post("/", PostIndexPage)
|
||||
|
||||
app.Get("/users", GetUsersPage)
|
||||
app.Get("/users/{userId:uuid}", EditUserPage)
|
||||
app.Post("/users/{userId:uuid}", SaveUser)
|
||||
|
||||
app.Get("/about", GetAboutPage)
|
||||
}
|
||||
|
||||
@ -1,15 +1,21 @@
|
||||
package views
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"iris-test/app/lib/auth"
|
||||
"iris-test/app/repository"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
)
|
||||
|
||||
func GetUsersPage(ctx iris.Context) {
|
||||
params1 := []string{"param 1", "param 2", "param 3"}
|
||||
ctx.ViewData("params1", params1)
|
||||
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) {
|
||||
userRepository := repository.Dao.UsersRepository
|
||||
|
||||
pagination := repository.NewPagination()
|
||||
@ -18,12 +24,70 @@ func GetUsersPage(ctx iris.Context) {
|
||||
repository.NewOrdering("last_name", repository.ORDERING_ASC),
|
||||
}
|
||||
|
||||
users := userRepository.List(&repository.UserFilter{}, &pagination, &ordering)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
func EditUserPage(ctx iris.Context) {
|
||||
userId := ctx.Params().Get("userId")
|
||||
userRepository := repository.Dao.UsersRepository
|
||||
|
||||
filter := repository.UserFilter{Id: &userId}
|
||||
user := userRepository.Get(&filter)
|
||||
|
||||
vars := iris.Map{
|
||||
"activePage": "users",
|
||||
"user": user,
|
||||
"currentPath": ctx.Path(),
|
||||
"backlink": "/users",
|
||||
}
|
||||
|
||||
if err := ctx.View("pages/user-edit.jet", vars); err != nil {
|
||||
showError(ctx, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func SaveUser(ctx iris.Context) {
|
||||
var postData editUserJSON
|
||||
err := ctx.ReadJSON(&postData)
|
||||
|
||||
if err != nil {
|
||||
ctx.StopWithError(iris.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
userId := ctx.Params().Get("userId")
|
||||
userRepository := repository.Dao.UsersRepository
|
||||
|
||||
filter := repository.UserFilter{Id: &userId}
|
||||
user := userRepository.Get(&filter)
|
||||
|
||||
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)
|
||||
|
||||
response := iris.Map{
|
||||
"success": true,
|
||||
}
|
||||
|
||||
ctx.JSON(response)
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ application:
|
||||
debugSQL: true
|
||||
isProduction: false
|
||||
loopDelay: 3000
|
||||
staticDir: "./static"
|
||||
|
||||
database:
|
||||
host: "localhost"
|
||||
|
||||
BIN
static/example.png
Normal file
BIN
static/example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
6
static/styles.css
Normal file
6
static/styles.css
Normal 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
1
static/styles.css.map
Normal 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
6
static/styles.scss
Normal file
@ -0,0 +1,6 @@
|
||||
header.navbar {
|
||||
background-color: #e0e0e0;
|
||||
.navbar-brand img {
|
||||
max-height: 80px;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user