blob: 3445b592280712866919d91e6301fbce4466ef83 [file] [log] [blame]
Giorgi Lekveishvili50357562023-06-09 12:57:18 +04001package main
2
3import (
4 "database/sql"
5 _ "embed"
6 "flag"
7 "fmt"
8 "log"
9 "net/http"
10 "net/url"
11 "strconv"
12 "strings"
13
14 _ "github.com/glebarez/go-sqlite"
15)
16
17var port = flag.Int("port", 8080, "Port to listen on")
18var dbPath = flag.String("db-path", "entries.db", "Path to the sqlite file")
19
20//go:embed index.html
21var indexHtml []byte
22
23//go:embed ok.html
24var okHtml []byte
25
26type entry struct {
27 Email string
28 InstallationType string
29 NumMembers int
30 Apps []string
31 PayPerMonth float64
32 PrepayFullYear bool
33 Thoughts string
34}
35
36func getFormValues(f url.Values, name string) ([]string, error) {
37 return f[name], nil
38}
39
40func getFormValue(f url.Values, name string) (string, error) {
41 if ret, ok := f[name]; ok {
42 switch len(ret) {
43 case 0:
44 return "", fmt.Errorf("%s is required", name)
45 case 1:
46 return ret[0], nil
47 default:
48 return "", fmt.Errorf("%s too many values", name)
49 }
50 }
51 return "", fmt.Errorf("%s is required", name)
52}
53
54func getFormValueInt(f url.Values, name string) (int, error) {
55 v, err := getFormValue(f, name)
56 if err != nil {
57 return 0, err
58 }
59 return strconv.Atoi(v)
60}
61
62func getFormValueBool(f url.Values, name string) (bool, error) {
63 v, err := getFormValue(f, name)
64 if err != nil {
65 return false, err
66 }
67 return strconv.ParseBool(v)
68}
69
70func getFormValueFloat64(f url.Values, name string) (float64, error) {
71 v, err := getFormValue(f, name)
72 if err != nil {
73 return 0, err
74 }
75 return strconv.ParseFloat(v, 64)
76}
77
78func NewEntry(f url.Values) (*entry, error) {
79 var e entry
80 var err error = nil
81 e.Email, err = getFormValue(f, "email")
82 if err != nil {
83 return nil, err
84 }
85 e.InstallationType, err = getFormValue(f, "installation_type")
86 if err != nil {
87 return nil, err
88 }
89 e.NumMembers, err = getFormValueInt(f, "num_members")
90 if err != nil {
91 return nil, err
92 }
93 e.Apps, err = getFormValues(f, "apps")
94 if err != nil {
95 return nil, err
96 }
97 e.PayPerMonth, err = getFormValueFloat64(f, "pay_per_month")
98 if err != nil {
99 return nil, err
100 }
101 e.PrepayFullYear, err = getFormValueBool(f, "pay_full_year")
102 if err != nil {
103 return nil, err
104 }
105 e.Thoughts, err = getFormValue(f, "thoughts")
106 if err != nil {
107 return nil, err
108 }
109 return &e, nil
110}
111
112type AddToWaitlistFn func(e *entry) error
113
114type Server struct {
115 port int
116 indexHtml []byte
117 okHtml []byte
118 addToWaitlist func(e *entry) error
119}
120
121func NewServer(port int, indexHtml, okHtml []byte, addToWaitlist AddToWaitlistFn) *Server {
122 return &Server{
123 port,
124 indexHtml,
125 okHtml,
126 addToWaitlist,
127 }
128}
129
130func (s *Server) Start() {
131 http.HandleFunc("/waitlist", s.waitlist)
132 http.HandleFunc("/", s.index)
133 log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
134}
135
136func (s *Server) index(w http.ResponseWriter, r *http.Request) {
137 if _, err := w.Write(s.indexHtml); err != nil {
138 http.Error(w, err.Error(), http.StatusInternalServerError)
139 return
140 }
141}
142
143func (s *Server) waitlist(w http.ResponseWriter, r *http.Request) {
144 if r.Method != http.MethodPost {
145 http.Error(w, "Invalid request", http.StatusBadRequest)
146 return
147 }
148 if err := r.ParseForm(); err != nil {
149 http.Error(w, "Invalid request", http.StatusBadRequest)
150 return
151 }
152 e, err := NewEntry(r.PostForm)
153 if err != nil {
154 http.Error(w, fmt.Sprintf("Invalid request: %s", err), http.StatusBadRequest)
155 return
156 }
157 if err := s.addToWaitlist(e); err != nil {
158 http.Error(w, err.Error(), http.StatusInternalServerError)
159 return
160 }
161 w.Write([]byte(s.okHtml))
162}
163
164func NewAddToWaitlist(db *sql.DB) AddToWaitlistFn {
165 return func(e *entry) error {
166 tx, err := db.Begin()
167 if err != nil {
168 return err
169 }
170 stm, err := tx.Prepare(`
171INSERT INTO waitlist (
172 email,
173 installation_type,
174 num_members,
175 apps,
176 pay_per_month,
177 prepay_full_year,
178 thoughts
179) VALUES (
180 ?, ?, ?, ?, ?, ?, ?
181);`)
182 if err != nil {
183 return err
184 }
185 defer stm.Close()
186 if _, err := stm.Exec(e.Email, e.InstallationType, e.NumMembers, strings.Join(e.Apps, ","), e.PayPerMonth, e.PrepayFullYear, e.Thoughts); err != nil {
187 return err
188 }
189 return tx.Commit()
190 }
191}
192
193func main() {
194 flag.Parse()
195 db, err := sql.Open("sqlite", *dbPath)
196 if err != nil {
197 log.Fatal(err)
198 }
199 defer db.Close()
200 s := NewServer(*port, indexHtml, okHtml, NewAddToWaitlist(db))
201 s.Start()
202}