Compare commits
39 Commits
2d2bea6543
...
exp
| Author | SHA1 | Date | |
|---|---|---|---|
| 1639dd787f | |||
| 9c299bc709 | |||
| 56f98a9c14 | |||
| 4cf50a7d81 | |||
| 8bc58eedbe | |||
| da5eeb3f0f | |||
| de681c1ac3 | |||
| faf9139d79 | |||
| 951c081680 | |||
| c4b5dcedf9 | |||
| 2d53824194 | |||
| 7d234c5aad | |||
| fef066b449 | |||
| 166049f4ff | |||
| 4f815151eb | |||
| 1fcbac610f | |||
| 1156cabe05 | |||
| 82b954af2e | |||
| 8eb491fa97 | |||
| 5b8e8017ca | |||
| 45ff8dd334 | |||
| 815c6a689d | |||
| 920e7972af | |||
| 13bf91ba7d | |||
| 140230d89b | |||
| 9a59acfd2d | |||
| 3b42227b81 | |||
| 1d6e8a9811 | |||
| 5adfeefa33 | |||
| 54313fe21b | |||
| 1e61052ec1 | |||
| b3dbd23a8e | |||
| 8051f053c3 | |||
| aac5e45b3f | |||
| 4e2ad80fdf | |||
| eb6d144e92 | |||
| 393cc1f3c0 | |||
| 3b4543c77a | |||
| d1cd20225b |
41
cmd/bcrypt/bcrypt.go
Normal file
41
cmd/bcrypt/bcrypt.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
switch os.Args[1] {
|
||||||
|
case "hash":
|
||||||
|
hash(os.Args[2])
|
||||||
|
case "compare":
|
||||||
|
compare(os.Args[2], os.Args[3])
|
||||||
|
default:
|
||||||
|
fmt.Printf("Invalid command: %v\n", os.Args[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hash(password string) {
|
||||||
|
fmt.Printf("Hash the password %q\n", password)
|
||||||
|
hashedBytes, err := bcrypt.GenerateFromPassword(
|
||||||
|
[]byte(password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("error hashing: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hash := string(hashedBytes)
|
||||||
|
fmt.Println(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func compare(password, hash string) {
|
||||||
|
fmt.Printf("Compare the password %q with the hash %q\n", password, hash)
|
||||||
|
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Password is invalid: %v\n", password)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println("Password is correct!")
|
||||||
|
}
|
||||||
144
cmd/exp/exp.go
Normal file
144
cmd/exp/exp.go
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
_ "github.com/jackc/pgx/v4/stdlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddUser(db *sql.DB, name string, email string) (error, int) {
|
||||||
|
row := db.QueryRow(`
|
||||||
|
INSERT INTO users(name, email)
|
||||||
|
VALUES($1, $2)
|
||||||
|
RETURNING id;
|
||||||
|
`, name, email)
|
||||||
|
var id int
|
||||||
|
err := row.Scan(&id)
|
||||||
|
return err, id
|
||||||
|
}
|
||||||
|
|
||||||
|
func QueryUser(db *sql.DB, id int) (error, string, string) {
|
||||||
|
row := db.QueryRow(`
|
||||||
|
SELECT name, email
|
||||||
|
FROM users
|
||||||
|
WHERE id=$1;`, id)
|
||||||
|
var name, email string
|
||||||
|
err := row.Scan(&name, &email)
|
||||||
|
return err, name, email
|
||||||
|
}
|
||||||
|
|
||||||
|
type Order struct {
|
||||||
|
ID int
|
||||||
|
UID int
|
||||||
|
Amount int
|
||||||
|
Desc string
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddOrder(db *sql.DB, uid int, amount int, desc string) (error, int) {
|
||||||
|
row := db.QueryRow(`
|
||||||
|
insert into orders(user_id, amount, description)
|
||||||
|
values($1, $2, $3)
|
||||||
|
returning id;
|
||||||
|
`, uid, amount, desc)
|
||||||
|
var id int
|
||||||
|
err := row.Scan(&id)
|
||||||
|
return err, id
|
||||||
|
}
|
||||||
|
|
||||||
|
func QueryOrders(db *sql.DB, uid int) ([]Order, error) {
|
||||||
|
var orders []Order
|
||||||
|
rows, err := db.Query(`
|
||||||
|
select id, amount, description from orders where user_id=$1
|
||||||
|
`, uid)
|
||||||
|
if err != nil {
|
||||||
|
return orders, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
for rows.Next() {
|
||||||
|
var order Order
|
||||||
|
order.UID = uid
|
||||||
|
err := rows.Scan(&order.ID, &order.Amount, &order.Desc)
|
||||||
|
if err != nil {
|
||||||
|
return orders, err
|
||||||
|
}
|
||||||
|
orders = append(orders, order)
|
||||||
|
}
|
||||||
|
err = rows.Err()
|
||||||
|
return orders, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
db, err := sql.Open("pgx", "host=localhost port=5432 user=baloo dbname=lenslocked sslmode=disable")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
err = db.Ping()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Connected!")
|
||||||
|
_, err = db.Exec(`
|
||||||
|
create table if not exists users (
|
||||||
|
id serial primary key,
|
||||||
|
name text,
|
||||||
|
email text not null
|
||||||
|
);
|
||||||
|
create table if not exists orders (
|
||||||
|
id serial primary key,
|
||||||
|
user_id int not null,
|
||||||
|
amount int,
|
||||||
|
description text
|
||||||
|
);`)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println("Tables created")
|
||||||
|
|
||||||
|
var name string
|
||||||
|
var email string
|
||||||
|
|
||||||
|
// Create User
|
||||||
|
/*
|
||||||
|
name = "Jon Rob Baker"
|
||||||
|
email = "demo@user.com"
|
||||||
|
err, id := AddUser(db, name, email)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println("User ", id, " created")
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Query User
|
||||||
|
err, name, email = QueryUser(db, 5)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
fmt.Println("Error, no rows!")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("User information: name=%s, email=%s\n", name, email)
|
||||||
|
|
||||||
|
uid := 5
|
||||||
|
|
||||||
|
// Create fake orders
|
||||||
|
/*
|
||||||
|
for i := 1; i <= 5; i++ {
|
||||||
|
amount := i * 100
|
||||||
|
desc := fmt.Sprintf("Fake order #%d", i)
|
||||||
|
err, _ := AddOrder(db, uid, amount, desc)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println("Created fake orders")
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Query Orders
|
||||||
|
orders, err := QueryOrders(db, uid)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println("Orders: ", orders)
|
||||||
|
}
|
||||||
26
cmd/tplfn/tplfn.go
Normal file
26
cmd/tplfn/tplfn.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
tplstr := "hello {{myFunc}}\n"
|
||||||
|
|
||||||
|
fmap := template.FuncMap{}
|
||||||
|
fmap["myFunc"] = func() (string, error) {
|
||||||
|
return "world", fmt.Errorf("hi")
|
||||||
|
}
|
||||||
|
|
||||||
|
tpl, err := template.New("demo").Funcs(fmap).Parse(tplstr)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("Error creating template: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tpl.Execute(os.Stdout, nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
49
controllers/static.go
Normal file
49
controllers/static.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.kealoha.me/lks/lenslocked/templates"
|
||||||
|
"git.kealoha.me/lks/lenslocked/views"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Template interface {
|
||||||
|
Execute(w http.ResponseWriter, r *http.Request, data interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func StaticController(templatePath ...string) http.HandlerFunc {
|
||||||
|
tpl := views.Must(views.FromFS(templates.FS, templatePath...))
|
||||||
|
|
||||||
|
err := tpl.TestTemplate(nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) { tpl.Execute(w, r, nil) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func FAQ(templatePath ...string) http.HandlerFunc {
|
||||||
|
questions := []struct {
|
||||||
|
Question string
|
||||||
|
Answer string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Question: "Is this a real website?",
|
||||||
|
Answer: "No.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Question: "I Can Has Cheezburger?",
|
||||||
|
Answer: "No.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tpl := views.Must(views.FromFS(templates.FS, templatePath...))
|
||||||
|
|
||||||
|
err := tpl.TestTemplate(nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
tpl.Execute(w, r, questions)
|
||||||
|
}
|
||||||
|
}
|
||||||
104
controllers/users.go
Normal file
104
controllers/users.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.kealoha.me/lks/lenslocked/models"
|
||||||
|
"git.kealoha.me/lks/lenslocked/templates"
|
||||||
|
"git.kealoha.me/lks/lenslocked/views"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Users struct {
|
||||||
|
Templates struct {
|
||||||
|
Signup Template
|
||||||
|
Signin Template
|
||||||
|
}
|
||||||
|
UserService *models.UserService
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u Users) GetSignup(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var data struct {
|
||||||
|
Email string
|
||||||
|
}
|
||||||
|
data.Email = r.FormValue("email")
|
||||||
|
u.Templates.Signup.Execute(w, r, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u Users) PostSignup(w http.ResponseWriter, r *http.Request) {
|
||||||
|
email := r.FormValue("email")
|
||||||
|
password := r.FormValue("password")
|
||||||
|
user, err := u.UserService.Create(email, password)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "User created: %+v", user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u Users) GetSignin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var data struct {
|
||||||
|
Email string
|
||||||
|
}
|
||||||
|
data.Email = r.FormValue("email")
|
||||||
|
u.Templates.Signin.Execute(w, r, data)
|
||||||
|
}
|
||||||
|
func (u Users) PostSignin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var data struct {
|
||||||
|
Email string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
data.Email = r.FormValue("email")
|
||||||
|
data.Password = r.FormValue("password")
|
||||||
|
user, err := u.UserService.Authenticate(data.Email, data.Password)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
http.Error(w, "Something went wrong.", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bad cookie
|
||||||
|
cookie := http.Cookie{
|
||||||
|
Name: "bad",
|
||||||
|
Value: user.Email,
|
||||||
|
Path: "/",
|
||||||
|
HttpOnly: true,
|
||||||
|
}
|
||||||
|
http.SetCookie(w, &cookie)
|
||||||
|
|
||||||
|
fmt.Fprintf(w, "User authenticated: %+v", user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u Users) CurrentUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
email, err := r.Cookie("bad")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprint(w, "The bad cookie could not be read.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "Bad cookie: %s\n", email.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithTemplates(user_service *models.UserService, signup Template, signin Template) Users {
|
||||||
|
u := Users{}
|
||||||
|
u.Templates.Signup = signup
|
||||||
|
u.Templates.Signin = signin
|
||||||
|
u.UserService = user_service
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
func Default(user_service *models.UserService, templatePath ...string) Users {
|
||||||
|
signup_tpl := views.Must(views.FromFS(templates.FS, "signup.gohtml", "tailwind.gohtml"))
|
||||||
|
signin_tpl := views.Must(views.FromFS(templates.FS, "signin.gohtml", "tailwind.gohtml"))
|
||||||
|
|
||||||
|
err := signup_tpl.TestTemplate(nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
err = signin_tpl.TestTemplate(nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return WithTemplates(user_service, signup_tpl, signin_tpl)
|
||||||
|
}
|
||||||
19
go.mod
19
go.mod
@@ -1,3 +1,22 @@
|
|||||||
module git.kealoha.me/lks/lenslocked
|
module git.kealoha.me/lks/lenslocked
|
||||||
|
|
||||||
go 1.22.5
|
go 1.22.5
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-chi/chi/v5 v5.1.0
|
||||||
|
github.com/jackc/pgx/v4 v4.18.3
|
||||||
|
golang.org/x/crypto v0.26.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gorilla/csrf v1.7.2
|
||||||
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
|
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||||
|
github.com/jackc/pgconn v1.14.3 // indirect
|
||||||
|
github.com/jackc/pgio v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgproto3/v2 v2.3.3 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
|
github.com/jackc/pgtype v1.14.0 // indirect
|
||||||
|
golang.org/x/text v0.17.0 // indirect
|
||||||
|
)
|
||||||
|
|||||||
188
go.sum
Normal file
188
go.sum
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||||
|
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||||
|
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||||
|
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||||
|
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
|
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||||
|
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||||
|
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
|
||||||
|
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
|
||||||
|
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
|
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||||
|
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||||
|
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
||||||
|
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||||
|
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
|
||||||
|
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
|
||||||
|
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
|
||||||
|
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
||||||
|
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
|
||||||
|
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||||
|
github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w=
|
||||||
|
github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM=
|
||||||
|
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
||||||
|
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
||||||
|
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
|
||||||
|
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
|
||||||
|
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
|
||||||
|
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag=
|
||||||
|
github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||||
|
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||||
|
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||||
|
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
|
||||||
|
github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
|
||||||
|
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||||
|
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
||||||
|
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||||
|
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||||
|
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
||||||
|
github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA=
|
||||||
|
github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw=
|
||||||
|
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
|
||||||
|
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||||
|
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
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/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||||
|
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||||
|
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||||
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
|
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||||
|
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||||
|
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
|
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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||||
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
|
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||||
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
|
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||||
|
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||||
|
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||||
|
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
|
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||||
|
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||||
|
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
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=
|
||||||
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
86
main.go
86
main.go
@@ -1,50 +1,68 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
ctrlrs "git.kealoha.me/lks/lenslocked/controllers"
|
||||||
|
"git.kealoha.me/lks/lenslocked/models"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/gorilla/csrf"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
_ "github.com/jackc/pgx/v4/stdlib"
|
||||||
)
|
)
|
||||||
|
|
||||||
func homeHandler(w http.ResponseWriter, r *http.Request) {
|
const DEBUG bool = true
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf8")
|
|
||||||
fmt.Fprint(w, "<h1>Welcome to my awesome site!</h1>")
|
|
||||||
}
|
|
||||||
func contactHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf8")
|
|
||||||
fmt.Fprint(w, "<h1>Contact Page</h1><p>To get in touch, email me at <a href=\"mailto:example@example.com\">example@example.com</a></p>")
|
|
||||||
}
|
|
||||||
func faqHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf8")
|
|
||||||
fmt.Fprint(w, `
|
|
||||||
<h1>FAQ</h1>
|
|
||||||
<hr>
|
|
||||||
<h3>Is this a real website?</h3>
|
|
||||||
<p>No.</p>
|
|
||||||
<h3>I Can Has Cheezburger?</h3>
|
|
||||||
<p>No.</p>
|
|
||||||
`)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Router struct{}
|
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
func (router Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
switch r.URL.Path {
|
|
||||||
case "/":
|
|
||||||
homeHandler(w, r)
|
|
||||||
case "/contact":
|
|
||||||
contactHandler(w, r)
|
|
||||||
case "/faq":
|
|
||||||
faqHandler(w, r)
|
|
||||||
default:
|
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf8")
|
w.Header().Set("Content-Type", "text/html; charset=utf8")
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
fmt.Fprint(w, "404 page not found")
|
fmt.Fprint(w, "404 page not found")
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "<hr><p>%s</p>", r.URL.Path)
|
|
||||||
|
func ConnectDB() *sql.DB {
|
||||||
|
db, err := sql.Open("pgx", os.Getenv("LENSLOCKED_DB_STRING"))
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprint("Error connecting to database: %w", err))
|
||||||
|
}
|
||||||
|
err = db.Ping()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprint("Error connecting to database: %w", err))
|
||||||
|
}
|
||||||
|
return db
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var router Router
|
csrfKey := []byte(os.Getenv("LENSLOCKED_CSRF_KEY"))
|
||||||
fmt.Println("Starting the server on :3000...")
|
if len(csrfKey) < 32 {
|
||||||
http.ListenAndServe(":3000", router)
|
panic("Error: bad csrf protection key")
|
||||||
|
}
|
||||||
|
|
||||||
|
db := ConnectDB()
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
userService := models.UserService{DB: db}
|
||||||
|
var usersCtrlr ctrlrs.Users = ctrlrs.Default(&userService)
|
||||||
|
|
||||||
|
r := chi.NewRouter()
|
||||||
|
|
||||||
|
r.Use(middleware.Logger)
|
||||||
|
|
||||||
|
r.Get("/", ctrlrs.StaticController("home.gohtml", "tailwind.gohtml"))
|
||||||
|
r.Get("/contact", ctrlrs.StaticController("contact.gohtml", "tailwind.gohtml"))
|
||||||
|
r.Get("/faq", ctrlrs.FAQ("faq.gohtml", "tailwind.gohtml"))
|
||||||
|
|
||||||
|
r.Get("/signup", usersCtrlr.GetSignup)
|
||||||
|
r.Post("/signup", usersCtrlr.PostSignup)
|
||||||
|
r.Get("/signin", usersCtrlr.GetSignin)
|
||||||
|
r.Post("/signin", usersCtrlr.PostSignin)
|
||||||
|
|
||||||
|
r.Get("/user", usersCtrlr.CurrentUser)
|
||||||
|
|
||||||
|
r.NotFound(notFoundHandler)
|
||||||
|
fmt.Println("Starting the server on :3000...")
|
||||||
|
http.ListenAndServe(":3000", csrf.Protect(csrfKey, csrf.Secure(!DEBUG))(r))
|
||||||
}
|
}
|
||||||
|
|||||||
5
models/sql/users.sql
Normal file
5
models/sql/users.sql
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
CREATE TABLE users (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
email TEXT UNIQUE NOT NULL,
|
||||||
|
password_hash TEXT NOT NULL
|
||||||
|
);
|
||||||
64
models/user.go
Normal file
64
models/user.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
ID int
|
||||||
|
Email string
|
||||||
|
PasswordHash string
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserService struct {
|
||||||
|
DB *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (us *UserService) Create(email, password string) (*User, error) {
|
||||||
|
email = strings.ToLower(email)
|
||||||
|
|
||||||
|
hashedBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create user: %w", err)
|
||||||
|
}
|
||||||
|
passwordHash := string(hashedBytes)
|
||||||
|
|
||||||
|
user := User{
|
||||||
|
Email: email,
|
||||||
|
PasswordHash: passwordHash,
|
||||||
|
}
|
||||||
|
row := us.DB.QueryRow(`
|
||||||
|
INSERT INTO users (email, password_hash)
|
||||||
|
VALUES ($1, $2) RETURNING id
|
||||||
|
`, email, passwordHash)
|
||||||
|
err = row.Scan(&user.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create user: %w", err)
|
||||||
|
}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (us UserService) Authenticate(email, password string) (*User, error) {
|
||||||
|
user := User{
|
||||||
|
Email: strings.ToLower(email),
|
||||||
|
}
|
||||||
|
|
||||||
|
row := us.DB.QueryRow(`
|
||||||
|
SELECT id, password_hash
|
||||||
|
FROM users WHERE email=$1
|
||||||
|
`, email)
|
||||||
|
err := row.Scan(&user.ID, &user.PasswordHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("authenticate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("authenticate: %w", err)
|
||||||
|
}
|
||||||
|
return &user, nil
|
||||||
|
}
|
||||||
12
templates/contact.gohtml
Normal file
12
templates/contact.gohtml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
{{template "head" .}}
|
||||||
|
<body>
|
||||||
|
{{template "header".}}
|
||||||
|
<main class="px-6">
|
||||||
|
<h1 class="py-4 text-4xl semibold tracking-tight font-sans">Contact Page</h1>
|
||||||
|
<p>To get in touch, email me at <a class="underline" href="mailto:example@example.com">example@example.com</a></p>
|
||||||
|
</main>
|
||||||
|
{{template "footer" .}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
27
templates/faq.gohtml
Normal file
27
templates/faq.gohtml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
{{template "head" .}}
|
||||||
|
<body>
|
||||||
|
{{template "header".}}
|
||||||
|
<main class="px-6">
|
||||||
|
<h1 class="py-4 text-4xl semibold tracking-tight">FAQ</h1>
|
||||||
|
<hr class="pb-4">
|
||||||
|
<div class="flex flex-col gap-8">
|
||||||
|
{{range .}}
|
||||||
|
{{template "qa" .}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{{template "footer" .}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{{define "qa"}}
|
||||||
|
<div>
|
||||||
|
<h3 class="block text-lg semibold">{{.Question}}</h3>
|
||||||
|
<p class="block text-sm ml-2">{{.Answer}}</p>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
6
templates/fs.go
Normal file
6
templates/fs.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package templates
|
||||||
|
|
||||||
|
import "embed"
|
||||||
|
|
||||||
|
//go:embed *
|
||||||
|
var FS embed.FS
|
||||||
29
templates/home.gohtml
Normal file
29
templates/home.gohtml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
{{template "head" .}}
|
||||||
|
<body>
|
||||||
|
{{template "header".}}
|
||||||
|
<main class="px-6">
|
||||||
|
<h1 class="py-4 text-4xl semibold tracking-tight">
|
||||||
|
Welcome to my awesome site!
|
||||||
|
</h1>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
{{template "lorem-ipsum"}}
|
||||||
|
{{template "lorem-ipsum"}}
|
||||||
|
{{template "lorem-ipsum"}}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{{template "footer" .}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
{{define "lorem-ipsum"}}
|
||||||
|
<p>
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
||||||
|
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
|
||||||
|
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||||
|
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore
|
||||||
|
eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt
|
||||||
|
in culpa qui officia deserunt mollit anim id est laborum.
|
||||||
|
</p>
|
||||||
|
{{end}}
|
||||||
65
templates/signin.gohtml
Normal file
65
templates/signin.gohtml
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
{{template "head" .}}
|
||||||
|
<body class="min-h-screen bg-gray-100">
|
||||||
|
{{template "header".}}
|
||||||
|
<main class="py-12 flex justify-center">
|
||||||
|
<div class="px-8 py-8 bg-white rounded shadow">
|
||||||
|
<h1 class="pt-4 pb-8 text-center text-3xl font-bold text-gray-900">
|
||||||
|
Welcome back!
|
||||||
|
</h1>
|
||||||
|
<form action="/signin" method="post">
|
||||||
|
{{csrfField}}
|
||||||
|
<div class="py-2">
|
||||||
|
<label for="email" class="text-sm font-semibold text-gray-800">
|
||||||
|
Email Address
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
name="email"
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="Email address"
|
||||||
|
required
|
||||||
|
autocomplete="email"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 placeholder-gray-500
|
||||||
|
text-gray-800 rounded"
|
||||||
|
value="{{.Email}}"
|
||||||
|
{{if not .Email}}autofocus{{end}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="py-2">
|
||||||
|
<label for="password" class="text-sm font-semibold text-gray-800">
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
name="password"
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
placeholder="Password"
|
||||||
|
required
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 placeholder-gray-500
|
||||||
|
text-gray-800 rounded"
|
||||||
|
{{if .Email}}autofocus{{end}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="py-4">
|
||||||
|
<button class="w-full py-4 px-2 bg-indigo-600 hover:bg-indigo-700
|
||||||
|
text-white rounded font-bold text-lg">
|
||||||
|
Sign in
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="py-2 w-full flex justify-between">
|
||||||
|
<p class="text-xs text-gray-500">
|
||||||
|
Need an account?
|
||||||
|
<a href="/signup" class="underline">Sign up</a>
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-gray-500">
|
||||||
|
<a href="/reset-pw" class="underline">Forgot your password?</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{{template "footer" .}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
53
templates/signup.gohtml
Normal file
53
templates/signup.gohtml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
{{template "head" .}}
|
||||||
|
<body class="min-h-screen bg-gray-100">
|
||||||
|
{{template "header".}}
|
||||||
|
<main class="px-6">
|
||||||
|
<div class="py-12 flex justify-center">
|
||||||
|
<div class="px-8 py-8 bg-white rounded shadow">
|
||||||
|
<h1 class="pt-4 pb-8 text-center text-3xl font-bold text-gray-900">
|
||||||
|
Sign Up!
|
||||||
|
</h1>
|
||||||
|
<form action="/signup" method="post">
|
||||||
|
{{csrfField}}
|
||||||
|
<div>
|
||||||
|
<label for="signupEmail" class="text-sm font-semibold text-gray-800">Email Address</label>
|
||||||
|
<input name="email" id="signupEmail" type="email" placeholder="Email address" required autocomplete="email"
|
||||||
|
class="w-full px-3 py-2 boarder boarder-gray-300 placeholder-gray-500 text-gray-800 rounded"
|
||||||
|
value="{{.Email}}"
|
||||||
|
{{if not .Email}}autofocus{{end}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="signupPassword" class="text-sm font-semibold text-gray-800">Password</label>
|
||||||
|
<input name="password" id="signupPassword" type="password" placeholder="password" required
|
||||||
|
class="w-full px-3 py-2 boarder boarder-gray-300 placeholder-gray-500 text-gray-800 rounded"
|
||||||
|
{{if .Email}}autofocus{{end}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="referalCode" class="text-sm font-semibold text-gray-800">Referal Code</label>
|
||||||
|
<input name="referalCode" id="referalCode" type="text" placeholder="Referal Code" required
|
||||||
|
class="w-full px-3 py-2 boarder boarder-gray-300 placeholder-gray-500 text-gray-800 rounded"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button type="submit" class="w-full py-4 px-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded font-bold text-lg">Sign up</button>
|
||||||
|
</div>
|
||||||
|
<div class="py-2 w-full flex justify-between">
|
||||||
|
<p class="text-xs text-gray-500">
|
||||||
|
Already have an account?
|
||||||
|
<a href="/signin" class="underline">Sign in</a>
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-gray-500">
|
||||||
|
<a href="/reset-pw" class="underline">Forgot your password?</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{{template "footer" .}}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
30
templates/tailwind.gohtml
Normal file
30
templates/tailwind.gohtml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{{define "head"}}
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "header"}}
|
||||||
|
<header class="bg-gradient-to-r from-blue-800 to-indigo-800 text-white">
|
||||||
|
<nav class="px-6 py-6 flex items-center space-x-12">
|
||||||
|
<div class="text-2xl font-serif">Lenslocked</div>
|
||||||
|
<div class="flex-grow">
|
||||||
|
<a class="text-base font-semibold hover:text-blue-100 pr-8" href="/">Home</a>
|
||||||
|
<a class="text-base font-semibold hover:text-blue-100 pr-8" href="/contact">Contact</a>
|
||||||
|
<a class="text-base font-semibold hover:text-blue-100 pr-8" href="/faq">FAQ</a>
|
||||||
|
</div>
|
||||||
|
<div class="space-x-4">
|
||||||
|
<a href="/signin">Sign in</a>
|
||||||
|
<a href="/signup" clss="px-4 py-2 bg-blue-700 hover:bg-blue-600 rounded">Sign up</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "footer"}}
|
||||||
|
<br>
|
||||||
|
<hr>
|
||||||
|
<p class="text-gray-400 font-light">Copyright your mom 2057</p>
|
||||||
|
{{end}}
|
||||||
73
views/template.go
Normal file
73
views/template.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io/fs"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/csrf"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Template struct {
|
||||||
|
htmlTpl *template.Template
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Template) Execute(w http.ResponseWriter, r *http.Request, data interface{}) {
|
||||||
|
tpl, err := t.htmlTpl.Clone()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Template Clone Error: %v", err)
|
||||||
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tpl = tpl.Funcs(template.FuncMap{
|
||||||
|
"csrfField": func() template.HTML { return csrf.TemplateField(r) },
|
||||||
|
})
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf8")
|
||||||
|
err = tpl.Execute(w, data)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error executing template: %v", err)
|
||||||
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (t Template) TestTemplate(data interface{}) error {
|
||||||
|
var testWriter strings.Builder
|
||||||
|
tpl, err := t.htmlTpl.Clone()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tpl.Execute(&testWriter, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromFile(pattern ...string) (Template, error) {
|
||||||
|
fs := os.DirFS(".")
|
||||||
|
return FromFS(fs, pattern...)
|
||||||
|
}
|
||||||
|
func FromFS(fs fs.FS, pattern ...string) (Template, error) {
|
||||||
|
tpl := template.New(pattern[0])
|
||||||
|
tpl = tpl.Funcs(template.FuncMap{
|
||||||
|
"csrfField": func() template.HTML {
|
||||||
|
return `<div class="hidden">STUB: PLACEHOLDER</div>`
|
||||||
|
},
|
||||||
|
})
|
||||||
|
tpl, err := tpl.ParseFS(fs, pattern...)
|
||||||
|
if err != nil {
|
||||||
|
return Template{}, fmt.Errorf("Error parsing template: %v", err)
|
||||||
|
}
|
||||||
|
return Template{
|
||||||
|
htmlTpl: tpl,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Must(t Template, err error) Template {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user