Compare commits

1 Commits

Author SHA1 Message Date
7730257cce Merge branch 'main' into login 2023-10-25 20:19:43 +02:00
29 changed files with 114 additions and 611 deletions

1
.gitignore vendored
View File

@ -1,5 +1,4 @@
/.vscode /.vscode
/__debug* /__debug*
/build
/tmp /tmp
/config.yaml /config.yaml

View File

@ -1,8 +1,2 @@
EXEC=iris-test
run: run:
@air @air
.PHONY: build
build:
@go build -ldflags "-s -w" -o ./build/${EXEC} ./app/main.go

View File

@ -7,61 +7,7 @@
- [Docs](https://docs.iris-go.com/iris/getting-started/quick-start) - [Docs](https://docs.iris-go.com/iris/getting-started/quick-start)
- [Examples](https://github.com/kataras/iris/tree/main/_examples) - [Examples](https://github.com/kataras/iris/tree/main/_examples)
- [Templating Examples](https://github.com/kataras/iris/tree/main/_examples/view) - [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] - [Jet]
- [Source](https://github.com/CloudyKit/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) - [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
```

View File

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

View File

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

View File

@ -1 +0,0 @@
package auth

View File

@ -1,41 +0,0 @@
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,36 +0,0 @@
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
}

View File

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

View File

@ -2,14 +2,10 @@ package main
import ( import (
"fmt" "fmt"
"iris-test/app/lib/cfg"
"iris-test/app/lib/db"
"iris-test/app/lib/logging"
"iris-test/app/views" "iris-test/app/views"
"os" "os"
"time" "time"
"github.com/go-playground/validator/v10"
"github.com/kataras/iris/v12" "github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/accesslog" "github.com/kataras/iris/v12/middleware/accesslog"
"github.com/kataras/iris/v12/sessions" "github.com/kataras/iris/v12/sessions"
@ -19,32 +15,32 @@ import (
var redisDB *redis.Database var redisDB *redis.Database
func createSessionEngine() *sessions.Sessions { func createSessionEngine() *sessions.Sessions {
redisAddr := fmt.Sprintf("%s:%d", cfg.Config.Redis.Host, cfg.Config.Redis.Port) redisAddr := fmt.Sprintf("%s:%d", Config.Redis.Host, Config.Redis.Port)
redisDB = redis.New(redis.Config{ redisDB = redis.New(redis.Config{
Network: "tcp", Network: "tcp",
Addr: redisAddr, Addr: redisAddr,
Timeout: time.Duration(30) * time.Second, Timeout: time.Duration(30) * time.Second,
MaxActive: 10, MaxActive: 10,
Username: cfg.Config.Redis.Username, Username: Config.Redis.Username,
Password: cfg.Config.Redis.Password, Password: Config.Redis.Password,
Database: cfg.Config.Redis.Database, Database: Config.Redis.Database,
Prefix: cfg.Config.Redis.Prefix, Prefix: Config.Redis.Prefix,
Driver: redis.GoRedis(), // defaults to this driver. Driver: redis.GoRedis(), // defaults to this driver.
// To set a custom, existing go-redis client, use the "SetClient" method: // To set a custom, existing go-redis client, use the "SetClient" method:
// Driver: redis.GoRedis().SetClient(customGoRedisClient) // Driver: redis.GoRedis().SetClient(customGoRedisClient)
}) })
sessionsEngine := sessions.New(sessions.Config{ sessions_engine := sessions.New(sessions.Config{
Cookie: "_session_id", Cookie: "_session_id",
Expires: 0, // defaults to 0: unlimited life. Another good value is: 45 * time.Minute, Expires: 0, // defaults to 0: unlimited life. Another good value is: 45 * time.Minute,
AllowReclaim: true, AllowReclaim: true,
CookieSecureTLS: true, CookieSecureTLS: true,
}) })
sessionsEngine.UseDatabase(redisDB) sessions_engine.UseDatabase(redisDB)
return sessionsEngine return sessions_engine
} }
func createAccessLog() *accesslog.AccessLog { func createAccessLog() *accesslog.AccessLog {
@ -87,24 +83,18 @@ func createApp() *iris.Application {
accessLog := createAccessLog() accessLog := createAccessLog()
app := iris.New() app := iris.New()
app.Logger().SetLevel(cfg.Config.Application.LogLevel) app.Logger().SetLevel(Config.Application.LogLevel)
app.Use(sessionsEngine.Handler()) app.Use(sessionsEngine.Handler())
app.UseRouter(accessLog.Handler) app.UseRouter(accessLog.Handler)
app.RegisterView(iris.Jet("./app/templates", ".jet").Reload(true)) app.RegisterView(iris.Jet("./app/templates", ".jet").Reload(true))
views.CreateRouter(app) 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 return app
} }
func main() { func main() {
cfg.InitCfg() InitCfg()
logging.InitLogging() InitLogging()
db.InitDB() InitDB()
app := createApp() app := createApp()
defer redisDB.Close() defer redisDB.Close()

View File

@ -1,10 +1,6 @@
package models package models
import ( import "time"
"iris-test/app/lib/auth"
"iris-test/app/lib/cfg"
"time"
)
type User struct { type User struct {
Id string `gorm:"type(uuid);unique"` Id string `gorm:"type(uuid);unique"`
@ -20,13 +16,3 @@ type User struct {
func (u *User) TableName() string { func (u *User) TableName() string {
return "users" 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
}

View File

@ -12,7 +12,6 @@ type UsersRepository struct {
} }
type UserFilter struct { type UserFilter struct {
Id *string
IsActive *bool IsActive *bool
} }
@ -23,14 +22,9 @@ func CreateUsersRepository(db *gorm.DB) *UsersRepository {
func applyFilter(db *gorm.DB, filter *UserFilter) *gorm.DB { func applyFilter(db *gorm.DB, filter *UserFilter) *gorm.DB {
query := db query := db
if filter.Id != nil { // if filter.State != "" {
query = query.Where("id = ?", filter.Id) // query = query.Where("state = ?", filter.State)
} // }
if filter.IsActive != nil {
query = query.Where(map[string]interface{}{"is_active": filter.IsActive})
}
// if filter.SendAt_lt != nil { // if filter.SendAt_lt != nil {
// query = query.Where("send_at < ?", filter.SendAt_lt) // query = query.Where("send_at < ?", filter.SendAt_lt)
// } // }
@ -75,18 +69,3 @@ func (repository *UsersRepository) List(filter *UserFilter, pagination *Paginati
return &users 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
}

View File

@ -2,60 +2,26 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title> <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="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/styles.css">
</head> </head>
<body> <body>
<header class="navbar"> <div class="container">
<div class="container"> <h1>{{ title }}</h1>
<nav class="main navbar navbar-expand-lg">
<div class="container-fluid"> <p>
<a class="navbar-brand" href="/"> <a href="/">Frontpage</a>
<img src="/static/example.png" alt=""> <a href="/users">Users</a>
</a> <a href="/about">About</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" </p>
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> <main>
{{ yield mainContent() }} {{ yield mainContent() }}
</main> </main>
</div> </div>
{{ block jsBlock() }}{{ end }}
</body> </body>
</html>
</html>

View File

@ -1,19 +1,10 @@
{{ block usersTable(users) }} {{ block usersTable(users) }}
<table class="table table-hover"> <p>blablabla</p>
<thead>
<tr> <table class="table">
<th>ID</th>
<th>First name</th>
<th>Last name</th>
<th>Email</th>
</tr>
</thead>
<tbody> <tbody>
{{ range users }} {{ range users }}
<tr> <tr>
<td>
<a href="/users/{{ .Id }}">{{ .Id }}</a>
</td>
<td>{{ .FirstName }}</td> <td>{{ .FirstName }}</td>
<td>{{ .LastName }}</td> <td>{{ .LastName }}</td>
<td>{{ .Email }}</td> <td>{{ .Email }}</td>

View File

@ -2,48 +2,14 @@
{{ block mainContent() }} {{ 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>Bacon ipsum dolor amet meatball ground round swine salami, drumstick alcatra ribeye pork loin corned beef tongue <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>
sausage tail jerky buffalo. Bacon capicola fatback turducken. Drumstick picanha jowl pork loin, meatball tri-tip <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>
doner pastrami beef ribs shankle chicken chuck turkey. Flank pig turducken hamburger shoulder, tongue alcatra <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>
prosciutto buffalo jowl. Frankfurter brisket ball tip, leberkas strip steak meatball burgdoggen jowl rump. Porchetta <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>
drumstick frankfurter pig cow spare ribs rump sausage chuck fatback andouille filet mignon jerky.</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>Chuck swine drumstick, fatback alcatra burgdoggen ball tip meatball andouille shoulder brisket turducken cupim spare <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>
ribs. Spare ribs strip steak alcatra tri-tip pork chop bresaola, t-bone cupim cow. Ball tip pork corned beef chislic <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>
buffalo. Cupim bacon doner turkey ball tip prosciutto andouille fatback corned beef. Meatball drumstick venison <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>
landjaeger filet mignon biltong, frankfurter chicken shank bacon flank strip steak ground round t-bone.</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>Meatloaf tri-tip biltong porchetta corned beef alcatra. Capicola chicken pastrami swine tri-tip, flank corned beef {{ end }}
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,34 +4,26 @@
{{ block mainContent() }} {{ block mainContent() }}
<div class="row mt-5 mb-5"> <div class="row">
<form class="col-6" method="post" action="/"> <form class="mb-5 col-4 ms-auto me-auto" method="post" action="/">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Email address</label> <label class="form-label">Email address</label>
<input type="email" name="email" class="form-control" value="edkirin@gmail.com"> <input type="email" name="email" class="form-control" value="edkirin@gmail.com">
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Password</label> <label class="form-label">Password</label>
<input type="text" name="password" class="form-control" value="tralala"> <input type="text" name="password" class="form-control" value="tralala">
</div> </div>
<button type="submit" class="btn btn-success"> <button type="submit" class="btn btn-success">
Submit Submit
</button> </button>
</form> </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"> {{ end }}
<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

@ -1,94 +0,0 @@
{{ 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="firstName" class="form-control" value="{{ .user.FirstName }}">
</div>
<div class="mb-3">
<label class="form-label">Last name</label>
<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 }}">
</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 }}

View File

@ -3,9 +3,13 @@
{{ block mainContent() }} {{ block mainContent() }}
<ul>
{{ range params1 }}
<li>{{ . }}</li>
{{ end }}
</ul>
<h3>Users</h3> <h3>{{ title }}</h3>
{{ yield usersTable(users=.users) }} {{ yield usersTable(users=users) }}
{{ end }}
{{ end }}

View File

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

View File

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

View File

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

View File

@ -1,112 +1,29 @@
package views package views
import ( import (
"fmt"
"iris-test/app/lib/auth"
"iris-test/app/lib/helpers"
"iris-test/app/repository" "iris-test/app/repository"
"github.com/go-playground/validator/v10"
"github.com/kataras/iris/v12" "github.com/kataras/iris/v12"
) )
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) { func GetUsersPage(ctx iris.Context) {
params1 := []string{"param 1", "param 2", "param 3"}
ctx.ViewData("params1", params1)
userRepository := repository.Dao.UsersRepository userRepository := repository.Dao.UsersRepository
pagination := repository.NewPagination() pagination := repository.NewPagination()
ordering := []repository.Ordering{ ordering := []repository.Ordering{
repository.NewOrdering("updated_at", repository.ORDERING_DESC),
repository.NewOrdering("first_name", repository.ORDERING_ASC), repository.NewOrdering("first_name", repository.ORDERING_ASC),
repository.NewOrdering("last_name", repository.ORDERING_ASC), repository.NewOrdering("last_name", repository.ORDERING_ASC),
} }
isActive := true users := userRepository.List(&repository.UserFilter{}, &pagination, &ordering)
users := userRepository.List(&repository.UserFilter{IsActive: &isActive}, &pagination, &ordering)
vars := iris.Map{ ctx.ViewData("users", users)
"activePage": "users",
"users": users,
}
if err := ctx.View("pages/users.jet", vars); err != nil { if err := ctx.View("pages/users.jet"); err != nil {
showError(ctx, err) showError(ctx, err)
return 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 editUserRequestDto
err := ctx.ReadJSON(&postData)
if err != nil {
// 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
}
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)
}

View File

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

21
go.mod
View File

@ -3,13 +3,11 @@ module iris-test
go 1.21.2 go 1.21.2
require ( require (
github.com/go-playground/validator/v10 v10.15.5
github.com/kataras/iris/v12 v12.2.7 github.com/kataras/iris/v12 v12.2.7
github.com/kelseyhightower/envconfig v1.4.0 github.com/kelseyhightower/envconfig v1.4.0
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
golang.org/x/crypto v0.14.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
gorm.io/driver/postgres v1.5.4 gorm.io/driver/postgres v1.5.3
gorm.io/gorm v1.25.5 gorm.io/gorm v1.25.5
) )
@ -25,12 +23,9 @@ require (
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fatih/structs v1.1.0 // indirect github.com/fatih/structs v1.1.0 // indirect
github.com/flosch/pongo2/v4 v4.0.2 // 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/golang/snappy v0.0.4 // indirect
github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 // indirect github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 // indirect
github.com/google/uuid v1.4.0 // indirect github.com/google/uuid v1.3.1 // indirect
github.com/gorilla/css v1.0.0 // indirect github.com/gorilla/css v1.0.0 // indirect
github.com/iris-contrib/schema v0.0.6 // indirect github.com/iris-contrib/schema v0.0.6 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
@ -46,23 +41,23 @@ require (
github.com/kataras/sitemap v0.0.6 // indirect github.com/kataras/sitemap v0.0.6 // indirect
github.com/kataras/tunnel v0.0.4 // indirect github.com/kataras/tunnel v0.0.4 // indirect
github.com/klauspost/compress v1.17.2 // 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/mailgun/raymond/v2 v2.0.48 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/microcosm-cc/bluemonday v1.0.26 // indirect github.com/microcosm-cc/bluemonday v1.0.26 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/redis/go-redis/v9 v9.2.1 // indirect github.com/redis/go-redis/v9 v9.2.0 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/schollz/closestmatch v2.1.0+incompatible // indirect github.com/schollz/closestmatch v2.1.0+incompatible // indirect
github.com/sergi/go-diff v1.1.0 // indirect github.com/sergi/go-diff v1.1.0 // indirect
github.com/tdewolff/minify/v2 v2.20.3 // indirect github.com/tdewolff/minify/v2 v2.19.10 // indirect
github.com/tdewolff/parse/v2 v2.7.2 // indirect github.com/tdewolff/parse/v2 v2.6.8 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/msgpack/v5 v5.4.0 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/yosssi/ace v0.0.5 // 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/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/net v0.17.0 // indirect golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect golang.org/x/sys v0.13.0 // indirect

53
go.sum
View File

@ -16,10 +16,7 @@ github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sx
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 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 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
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 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -33,16 +30,6 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 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 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw=
github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= 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 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
@ -56,8 +43,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 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= 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/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
@ -104,8 +91,6 @@ 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/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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw=
github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
@ -118,16 +103,15 @@ 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/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 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= 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-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 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= github.com/redis/go-redis/v9 v9.2.0 h1:zwMdX0A4eVzse46YN18QhuDiM4uf3JmkOB4VZrdt5uI=
github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/redis/go-redis/v9 v9.2.0/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 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
@ -142,27 +126,22 @@ 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 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 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.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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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.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.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 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tdewolff/minify/v2 v2.20.3 h1:8x2BICr21IoNFda5EUyNsoNcEZHL/W0ap+sfUJiGdmg= github.com/tdewolff/minify/v2 v2.19.10 h1:79Z2DJ9p0zSBj2BxD5fyLkIwhhRZnWSOeK3C52ljAu4=
github.com/tdewolff/minify/v2 v2.20.3/go.mod h1:AMF0J/eNujZLDbfMZvWweg5TSG/KuK+/UGKc+k1N8/w= github.com/tdewolff/minify/v2 v2.19.10/go.mod h1:IX+vt5HNKc+RFIQj7aI4D6RQ9p41vjay9lGorpHNC34=
github.com/tdewolff/parse/v2 v2.7.2 h1:9NdxF0nk/+lPI0YADDonSlpiY15hGcVUhXRj9hnK8sM= github.com/tdewolff/parse/v2 v2.6.8 h1:mhNZXYCx//xG7Yq2e/kVLNZw4YfYmeHbhx+Zc0OvFMA=
github.com/tdewolff/parse/v2 v2.7.2/go.mod h1:9p2qMIHpjRSTr1qnFxQr+igogyTUTlwvf9awHSm84h8= github.com/tdewolff/parse/v2 v2.6.8/go.mod h1:XHDhaU6IBgsryfdnpzUXBlT6leW/l25yrFBTEb4eIyM=
github.com/tdewolff/test v1.0.10 h1:uWiheaLgLcNFqHcdWveum7PQfMnIUTf9Kl3bFxrIoew= github.com/tdewolff/test v1.0.9 h1:SswqJCmeN4B+9gEAi/5uqT0qpi1y2/2O47V/1hhGZT0=
github.com/tdewolff/test v1.0.10/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= github.com/vmihailenco/msgpack/v5 v5.4.0 h1:hRM0digJwyR6vll33NNAwCFguy5JuBD6jxDmQP3l608=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= github.com/vmihailenco/msgpack/v5 v5.4.0/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 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= 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= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
@ -236,8 +215,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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo= gorm.io/driver/postgres v1.5.3 h1:qKGY5CPHOuj47K/VxbCXJfFvIUeqMSXXadqdCY+MbBU=
gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0= gorm.io/driver/postgres v1.5.3/go.mod h1:F+LtvlFhZT7UBiA81mC9W6Su3D4WUhSboc/36QZU0gk=
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs=

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

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

View File

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

View File

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