Compare commits
7 Commits
pongo-temp
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| ca4014e306 | |||
| bf6ced948a | |||
| 442b145711 | |||
| 3290837555 | |||
| 50187f5a34 | |||
| dd671d561c | |||
| 7512d75a4d |
@ -7,8 +7,10 @@
|
||||
- [Docs](https://docs.iris-go.com/iris/getting-started/quick-start)
|
||||
- [Examples](https://github.com/kataras/iris/tree/main/_examples)
|
||||
- [Templating Examples](https://github.com/kataras/iris/tree/main/_examples/view)
|
||||
- [Validator](https://pkg.go.dev/github.com/go-playground/validator@v9.31.0+incompatible)
|
||||
- [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 +18,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 +63,5 @@ Statistics Avg Stdev Max
|
||||
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 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"`
|
||||
@ -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)
|
||||
}
|
||||
|
||||
36
app/lib/helpers/validation.go
Normal file
36
app/lib/helpers/validation.go
Normal file
@ -0,0 +1,36 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
type ValidationError struct {
|
||||
ActualTag string `json:"tag"`
|
||||
Namespace string `json:"namespace"`
|
||||
Kind string `json:"kind"`
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
Param string `json:"param"`
|
||||
Field string `json:"field"`
|
||||
StructField string `json:"structField"`
|
||||
}
|
||||
|
||||
func WrapValidationErrors(errs validator.ValidationErrors) []ValidationError {
|
||||
validationErrors := make([]ValidationError, 0, len(errs))
|
||||
for _, validationErr := range errs {
|
||||
validationErrors = append(validationErrors, ValidationError{
|
||||
ActualTag: validationErr.ActualTag(),
|
||||
Namespace: validationErr.Namespace(),
|
||||
Kind: validationErr.Kind().String(),
|
||||
Type: validationErr.Type().String(),
|
||||
Value: fmt.Sprintf("%v", validationErr.Value()),
|
||||
Param: validationErr.Param(),
|
||||
Field: validationErr.Field(),
|
||||
StructField: validationErr.StructField(),
|
||||
})
|
||||
}
|
||||
|
||||
return validationErrors
|
||||
}
|
||||
@ -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))
|
||||
}
|
||||
29
app/main.go
29
app/main.go
@ -2,11 +2,14 @@ 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"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/kataras/iris/v12"
|
||||
"github.com/kataras/iris/v12/middleware/accesslog"
|
||||
"github.com/kataras/iris/v12/sessions"
|
||||
@ -16,17 +19,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 +87,24 @@ 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)
|
||||
app.Validator = validator.New()
|
||||
|
||||
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()
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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>
|
||||
@ -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,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 }}
|
||||
@ -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="firstName" class="form-control" value="{{ .user.FirstName }}">
|
||||
</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="lastName" class="form-control" value="{{ .user.LastName }}">
|
||||
</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 }}">
|
||||
</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 }}
|
||||
@ -3,7 +3,9 @@
|
||||
|
||||
|
||||
{{ block mainContent() }}
|
||||
<h3>Users</h3>
|
||||
|
||||
{{ yield usersTable(users=users) }}
|
||||
<h3>Users</h3>
|
||||
|
||||
{{ 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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -1,15 +1,20 @@
|
||||
package views
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"iris-test/app/lib/auth"
|
||||
"iris-test/app/lib/helpers"
|
||||
"iris-test/app/repository"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/kataras/iris/v12"
|
||||
)
|
||||
|
||||
type editUserForm struct {
|
||||
FirstName string `form:"first-name"`
|
||||
LastName string `form:"last-name"`
|
||||
Email string `form:"email"`
|
||||
type editUserRequestDto struct {
|
||||
FirstName string `json:"firstName" validate:"required"`
|
||||
LastName string `json:"lastName" validate:"required"`
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func GetUsersPage(ctx iris.Context) {
|
||||
@ -17,6 +22,7 @@ func GetUsersPage(ctx iris.Context) {
|
||||
|
||||
pagination := repository.NewPagination()
|
||||
ordering := []repository.Ordering{
|
||||
repository.NewOrdering("updated_at", repository.ORDERING_DESC),
|
||||
repository.NewOrdering("first_name", repository.ORDERING_ASC),
|
||||
repository.NewOrdering("last_name", repository.ORDERING_ASC),
|
||||
}
|
||||
@ -24,9 +30,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,20 +48,41 @@ 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 editUserRequestDto
|
||||
err := ctx.ReadJSON(&postData)
|
||||
|
||||
if err != nil {
|
||||
ctx.StopWithError(iris.StatusBadRequest, err)
|
||||
// Handle the error, below you will find the right way to do that...
|
||||
if errs, ok := err.(validator.ValidationErrors); ok {
|
||||
// Wrap the errors with JSON format, the underline library returns the errors as interface.
|
||||
validationErrors := helpers.WrapValidationErrors(errs)
|
||||
|
||||
// Fire an application/json+problem response and stop the handlers chain.
|
||||
ctx.StopWithProblem(
|
||||
iris.StatusBadRequest, iris.NewProblem().
|
||||
Title("Validation error").
|
||||
Detail("One or more fields failed to be validated").
|
||||
Key("errors", validationErrors).
|
||||
Key("success", false),
|
||||
)
|
||||
return
|
||||
}
|
||||
// It's probably an internal JSON error, let's dont give more info here.
|
||||
ctx.StopWithStatus(iris.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@ -62,11 +92,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)
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ application:
|
||||
debugSQL: true
|
||||
isProduction: false
|
||||
loopDelay: 3000
|
||||
staticDir: "./static"
|
||||
|
||||
database:
|
||||
host: "localhost"
|
||||
|
||||
21
go.mod
21
go.mod
@ -3,11 +3,13 @@ module iris-test
|
||||
go 1.21.2
|
||||
|
||||
require (
|
||||
github.com/go-playground/validator/v10 v10.15.5
|
||||
github.com/kataras/iris/v12 v12.2.7
|
||||
github.com/kelseyhightower/envconfig v1.4.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
golang.org/x/crypto v0.14.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/postgres v1.5.3
|
||||
gorm.io/driver/postgres v1.5.4
|
||||
gorm.io/gorm v1.25.5
|
||||
)
|
||||
|
||||
@ -23,9 +25,12 @@ require (
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/flosch/pongo2/v4 v4.0.2 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/google/uuid v1.4.0 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/iris-contrib/schema v0.0.6 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
@ -41,23 +46,23 @@ require (
|
||||
github.com/kataras/sitemap v0.0.6 // indirect
|
||||
github.com/kataras/tunnel v0.0.4 // indirect
|
||||
github.com/klauspost/compress v1.17.2 // indirect
|
||||
github.com/leodido/go-urn v1.2.4 // indirect
|
||||
github.com/mailgun/raymond/v2 v2.0.48 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.26 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/redis/go-redis/v9 v9.2.0 // indirect
|
||||
github.com/redis/go-redis/v9 v9.2.1 // indirect
|
||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/schollz/closestmatch v2.1.0+incompatible // indirect
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/tdewolff/minify/v2 v2.19.10 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.6.8 // indirect
|
||||
github.com/tdewolff/minify/v2 v2.20.3 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.7.2 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.0 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
github.com/yosssi/ace v0.0.5 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
|
||||
53
go.sum
53
go.sum
@ -16,7 +16,10 @@ github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sx
|
||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -30,6 +33,16 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw=
|
||||
github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24=
|
||||
github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
@ -43,8 +56,8 @@ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
@ -91,6 +104,8 @@ github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NB
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||
github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw=
|
||||
github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
@ -103,15 +118,16 @@ github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3r
|
||||
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
|
||||
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
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/redis/go-redis/v9 v9.2.0 h1:zwMdX0A4eVzse46YN18QhuDiM4uf3JmkOB4VZrdt5uI=
|
||||
github.com/redis/go-redis/v9 v9.2.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||
github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg=
|
||||
github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
@ -126,22 +142,27 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tdewolff/minify/v2 v2.19.10 h1:79Z2DJ9p0zSBj2BxD5fyLkIwhhRZnWSOeK3C52ljAu4=
|
||||
github.com/tdewolff/minify/v2 v2.19.10/go.mod h1:IX+vt5HNKc+RFIQj7aI4D6RQ9p41vjay9lGorpHNC34=
|
||||
github.com/tdewolff/parse/v2 v2.6.8 h1:mhNZXYCx//xG7Yq2e/kVLNZw4YfYmeHbhx+Zc0OvFMA=
|
||||
github.com/tdewolff/parse/v2 v2.6.8/go.mod h1:XHDhaU6IBgsryfdnpzUXBlT6leW/l25yrFBTEb4eIyM=
|
||||
github.com/tdewolff/test v1.0.9 h1:SswqJCmeN4B+9gEAi/5uqT0qpi1y2/2O47V/1hhGZT0=
|
||||
github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tdewolff/minify/v2 v2.20.3 h1:8x2BICr21IoNFda5EUyNsoNcEZHL/W0ap+sfUJiGdmg=
|
||||
github.com/tdewolff/minify/v2 v2.20.3/go.mod h1:AMF0J/eNujZLDbfMZvWweg5TSG/KuK+/UGKc+k1N8/w=
|
||||
github.com/tdewolff/parse/v2 v2.7.2 h1:9NdxF0nk/+lPI0YADDonSlpiY15hGcVUhXRj9hnK8sM=
|
||||
github.com/tdewolff/parse/v2 v2.7.2/go.mod h1:9p2qMIHpjRSTr1qnFxQr+igogyTUTlwvf9awHSm84h8=
|
||||
github.com/tdewolff/test v1.0.10 h1:uWiheaLgLcNFqHcdWveum7PQfMnIUTf9Kl3bFxrIoew=
|
||||
github.com/tdewolff/test v1.0.10/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
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/vmihailenco/msgpack/v5 v5.4.0 h1:hRM0digJwyR6vll33NNAwCFguy5JuBD6jxDmQP3l608=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.0/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||
@ -215,8 +236,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
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/postgres v1.5.3 h1:qKGY5CPHOuj47K/VxbCXJfFvIUeqMSXXadqdCY+MbBU=
|
||||
gorm.io/driver/postgres v1.5.3/go.mod h1:F+LtvlFhZT7UBiA81mC9W6Su3D4WUhSboc/36QZU0gk=
|
||||
gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo=
|
||||
gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0=
|
||||
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs=
|
||||
|
||||
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