Compare commits
No commits in common. "1639dd787f418b0914757dba299c8b54174f7c54" and "166049f4fff87ca72cebc2d95d5d095b984cb46b" have entirely different histories.
1639dd787f
...
166049f4ff
@ -1,26 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -4,21 +4,23 @@ import (
|
||||
"git.kealoha.me/lks/lenslocked/templates"
|
||||
"git.kealoha.me/lks/lenslocked/views"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Template interface {
|
||||
Execute(w http.ResponseWriter, r *http.Request, data interface{})
|
||||
Execute(w http.ResponseWriter, data interface{})
|
||||
}
|
||||
|
||||
func StaticController(templatePath ...string) http.HandlerFunc {
|
||||
func StaticTemplate(templatePath ...string) http.HandlerFunc {
|
||||
tpl := views.Must(views.FromFS(templates.FS, templatePath...))
|
||||
|
||||
err := tpl.TestTemplate(nil)
|
||||
var testWriter strings.Builder
|
||||
err := tpl.ExecuteWriter(&testWriter, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) { tpl.Execute(w, r, nil) }
|
||||
return func(w http.ResponseWriter, r *http.Request) { tpl.Execute(w, nil) }
|
||||
}
|
||||
|
||||
func FAQ(templatePath ...string) http.HandlerFunc {
|
||||
@ -38,12 +40,13 @@ func FAQ(templatePath ...string) http.HandlerFunc {
|
||||
|
||||
tpl := views.Must(views.FromFS(templates.FS, templatePath...))
|
||||
|
||||
err := tpl.TestTemplate(nil)
|
||||
var testWriter strings.Builder
|
||||
err := tpl.ExecuteWriter(&testWriter, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
tpl.Execute(w, r, questions)
|
||||
tpl.Execute(w, questions)
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,102 +3,40 @@ package controllers
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"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
|
||||
New Template
|
||||
}
|
||||
UserService *models.UserService
|
||||
}
|
||||
|
||||
func (u Users) GetSignup(w http.ResponseWriter, r *http.Request) {
|
||||
func (u Users) New(w http.ResponseWriter, r *http.Request) {
|
||||
var data struct {
|
||||
Email string
|
||||
}
|
||||
data.Email = r.FormValue("email")
|
||||
u.Templates.Signup.Execute(w, r, data)
|
||||
u.Templates.New.Execute(w, data)
|
||||
}
|
||||
func (u Users) Create(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprint(w, "TODO! ", r.FormValue("email"))
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
func FromStaticTemplate(templatePath ...string) Users {
|
||||
tpl := views.Must(views.FromFS(templates.FS, templatePath...))
|
||||
|
||||
var testWriter strings.Builder
|
||||
err := tpl.ExecuteWriter(&testWriter, nil)
|
||||
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
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 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
|
||||
u.Templates.New = tpl
|
||||
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)
|
||||
}
|
||||
|
||||
6
go.mod
6
go.mod
@ -5,12 +5,10 @@ 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
|
||||
golang.org/x/crypto v0.20.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
|
||||
@ -18,5 +16,5 @@ require (
|
||||
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
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
)
|
||||
|
||||
14
go.sum
14
go.sum
@ -16,13 +16,7 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
|
||||
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=
|
||||
@ -132,8 +126,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
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/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
|
||||
golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
|
||||
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=
|
||||
@ -162,8 +156,8 @@ 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/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
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=
|
||||
|
||||
50
main.go
50
main.go
@ -1,68 +1,30 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
const DEBUG bool = true
|
||||
|
||||
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf8")
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
fmt.Fprint(w, "404 page not found")
|
||||
}
|
||||
|
||||
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() {
|
||||
csrfKey := []byte(os.Getenv("LENSLOCKED_CSRF_KEY"))
|
||||
if len(csrfKey) < 32 {
|
||||
panic("Error: bad csrf protection key")
|
||||
}
|
||||
|
||||
db := ConnectDB()
|
||||
defer db.Close()
|
||||
|
||||
userService := models.UserService{DB: db}
|
||||
var usersCtrlr ctrlrs.Users = ctrlrs.Default(&userService)
|
||||
|
||||
var usersCtrlr ctrlrs.Users = ctrlrs.FromStaticTemplate("signup.gohtml", "tailwind.gohtml")
|
||||
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("/", ctrlrs.StaticTemplate("home.gohtml", "tailwind.gohtml"))
|
||||
r.Get("/contact", ctrlrs.StaticTemplate("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.Get("/signup", usersCtrlr.New)
|
||||
r.Post("/signup", usersCtrlr.Create)
|
||||
r.NotFound(notFoundHandler)
|
||||
fmt.Println("Starting the server on :3000...")
|
||||
http.ListenAndServe(":3000", csrf.Protect(csrfKey, csrf.Secure(!DEBUG))(r))
|
||||
http.ListenAndServe(":3000", r)
|
||||
}
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
CREATE TABLE users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL
|
||||
);
|
||||
@ -1,64 +0,0 @@
|
||||
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
|
||||
}
|
||||
@ -1,65 +0,0 @@
|
||||
<!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>
|
||||
@ -10,7 +10,6 @@
|
||||
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"
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
<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="#">Sign in</a>
|
||||
<a href="/signup" clss="px-4 py-2 bg-blue-700 hover:bg-blue-600 rounded">Sign up</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@ -3,60 +3,40 @@ package views
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"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) },
|
||||
})
|
||||
|
||||
func (t Template) Execute(w http.ResponseWriter, data interface{}) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf8")
|
||||
err = tpl.Execute(w, data)
|
||||
err := t.htmlTpl.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 (t Template) ExecuteWriter(w io.Writer, data interface{}) error {
|
||||
return t.htmlTpl.Execute(w, data)
|
||||
}
|
||||
|
||||
func FromFile(pattern ...string) (Template, error) {
|
||||
fs := os.DirFS(".")
|
||||
return FromFS(fs, pattern...)
|
||||
func FromFile(filepath string) (Template, error) {
|
||||
tpl, err := template.ParseFiles(filepath)
|
||||
if err != nil {
|
||||
return Template{}, fmt.Errorf("Error parsing template: %v", err)
|
||||
}
|
||||
return Template{
|
||||
htmlTpl: tpl,
|
||||
}, nil
|
||||
}
|
||||
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...)
|
||||
tpl, err := template.ParseFS(fs, pattern...)
|
||||
if err != nil {
|
||||
return Template{}, fmt.Errorf("Error parsing template: %v", err)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user