/
form.go
170 lines (148 loc) · 3.25 KB
/
form.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package form
import (
"bytes"
"compress/gzip"
"crypto/md5"
"encoding/hex"
"fmt"
"html/template"
"net/http"
"strings"
)
//go:generate go run ../cmd/assets/main.go
//go:generate go run ../cmd/assets/main.go -asset alpine
var (
formTpl *template.Template
rawHash = make(map[string]string)
gzHash = make(map[string]string)
rawCache = make(map[string][]byte)
gzCache = make(map[string][]byte)
)
type (
ValidateFunc func(fieldID string, data map[string]interface{}) error
Config struct {
AlpineJSURL string
Recaptcha string
Brand Brand
Path Path
Title string
Description string
Tab string
Tabs []string
Links []*Link
Fields []*Field
Submit string
}
Link struct {
URL string
Label string
}
Field struct {
ID string
Label string
LabelHtml template.HTML
Type string
Options []Option
Validate ValidateFunc
SettingsTab string
}
Option struct {
ID string
Label string
}
Brand struct {
LogoURL string
AppName string
AppURL string
RegisterLabel string
RegisterButton string
LoginLabel string
LoginButton string
Primary string
PrimaryInverse string
Accent string
Neutral string
NeutralInverse string
}
Path struct {
// Defaults to /auth/
Base string
// Defaults to / provide where you want it to link back to
Home string
// paths for post and renderer
Account string
Login string
Refresh string
Register string
Terms string
}
)
func Render(w http.ResponseWriter, c *Config) (err error) {
if formTpl == nil {
formTpl, err = template.New("form").Parse(FormTemplate)
if err != nil {
return fmt.Errorf("form.Render: template parse error %v", err)
}
if _, err := formTpl.Parse(Layout); err != nil {
return fmt.Errorf("form.Render: formTpl parse error %v", err)
}
}
return formTpl.ExecuteTemplate(w, "layout", c)
}
func eTag(b []byte) string {
hasher := md5.New()
hasher.Write(b)
return hex.EncodeToString(hasher.Sum(nil))
}
func RenderAsset(w http.ResponseWriter, r *http.Request, path string) {
var (
rawAsset string
)
cType := "application/javascript"
switch path {
case "/client.css":
rawAsset = ClientCSS
cType = "text/css"
case "/client.js":
rawAsset = ClientJS
case "/alpine.js":
rawAsset = AlpineJS
default:
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
}
hs, ok := rawHash[path]
if !ok {
rawCache[path] = []byte(rawAsset)
rawHash[path] = eTag(rawCache[path])
hs = rawHash[path]
}
body := rawCache[path]
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
hs, ok = gzHash[path]
if !ok {
var b bytes.Buffer
gz := gzip.NewWriter(&b)
if _, err := gz.Write([]byte(rawAsset)); err != nil {
panic(err)
}
if err := gz.Close(); err != nil {
panic(err)
}
gzCache[path] = b.Bytes()
gzHash[path] = eTag(gzCache[path])
hs = gzHash[path]
}
body = gzCache[path]
}
h := w.Header()
h.Set("Content-Encoding", "gzip")
h.Set("Content-Type", cType)
h.Set("Etag", hs)
h.Set("Cache-Control", "max-age=86400")
if match := r.Header.Get("If-None-Match"); match != "" && strings.Contains(match, hs) {
w.WriteHeader(http.StatusNotModified)
return
}
w.Write(body)
}