package main import ( "bytes" "context" "crypto/rand" "flag" "fmt" "io" "log" "math/big" "net/http" "net/url" "time" hm "gitlab.codemonkeysoftware.net/b/hatmill" a "gitlab.codemonkeysoftware.net/b/hatmill/attribute" e "gitlab.codemonkeysoftware.net/b/hatmill/element" "gitlab.codemonkeysoftware.net/b/henwen" ) const ( pathRoot = "/" pathCreate = "/create" pathDoCreate = "/create/do" pathAdmin = "/admin" ) type handler struct { mux *http.ServeMux store *henwen.Store title string baseURL string } type HandlerParams struct { Title string Store *henwen.Store BaseURL string } func NewHandler(params HandlerParams) http.Handler { h := &handler{ store: params.Store, title: params.Title, baseURL: params.BaseURL, mux: http.NewServeMux(), } h.mux.HandleFunc(pathRoot, h.handleRoot) h.mux.HandleFunc(pathCreate, h.handleCreate) h.mux.HandleFunc(pathDoCreate, h.handleDoCreate) h.mux.HandleFunc(pathAdmin, h.handleAdmin) return h } func (h *handler) handleRoot(w http.ResponseWriter, r *http.Request) { body := hm.Terms{ e.H2()(hm.Text("Welcome!")), e.A(a.Href(pathCreate))( hm.Text("Create event"), ), } _ = h.writePage(w, body) } func (h *handler) handleCreate(w http.ResponseWriter, r *http.Request) { body := hm.Terms{ e.H2()(hm.Text("Create an event")), e.Form(a.Action(pathDoCreate), a.Method(http.MethodPost))( e.Label(a.For(fieldNameEventName))(hm.Text("Event name")), e.Input(a.Name(fieldNameEventName)), e.Label(a.For(fieldNameDescription))(hm.Text("Description")), e.Textarea(a.Name(fieldNameDescription), a.Placeholder("What's going on?"))(), e.Label(a.For(fieldNameEarliest))(hm.Text("Earliest date")), e.Input(a.Name(fieldNameEarliest), a.Type("date")), e.Label(a.For(fieldNameLatest))(hm.Text("Latest date")), e.Input(a.Name(fieldNameLatest), a.Type("date")), e.Input(a.Type("submit")), ), } _ = h.writePage(w, body) } func (h *handler) handleDoCreate(w http.ResponseWriter, r *http.Request) { earliest, err := time.Parse(formDateLayout, r.FormValue(fieldNameEarliest)) if err != nil { fmt.Fprint(w, "bad earliest date") return } latest, err := time.Parse(formDateLayout, r.FormValue(fieldNameLatest)) if err != nil { fmt.Fprint(w, "bad latest date") return } eventName := r.FormValue(fieldNameEventName) if eventName == "" { fmt.Fprint(w, "event name is required") return } description := r.FormValue(fieldNameDescription) event, err := h.store.CreateEvent(context.Background(), henwen.CreateEventCommand{ Name: eventName, Description: description, EarliestDate: earliest, LatestDate: latest, }) if err != nil { fmt.Fprint(w, err) return } var adminQuery = make(url.Values) adminQuery.Add(keyEventID, event.AlphaID) adminQuery.Add(keyAdminCode, event.AdminCode) adminURL := h.baseURL + pathAdmin + "?" + adminQuery.Encode() const dateDisplayFmt = "Monday, January 2, 2006" body := hm.Terms{ e.H2()(hm.Text("Created event!")), e.P()( hm.Text("You can find it again at "), e.A(a.Href(adminURL))(hm.Text(adminURL)), hm.Text("."), ), e.H3()(hm.Text("Name")), hm.Text(eventName), e.H3()(hm.Text("Description")), hm.Text(description), e.H3()(hm.Text("Earliest date")), hm.Text(earliest.Format(dateDisplayFmt)), e.H3()(hm.Text("Latest date")), hm.Text(latest.Format(dateDisplayFmt)), } _ = h.writePage(w, body) } func (h *handler) handleAdmin(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() event, err := h.store.GetEvent(context.Background(), henwen.GetEventQuery{ AlphaID: query.Get(keyEventID), AdminCode: query.Get(keyAdminCode), }) if err != nil { fmt.Fprint(w, err) return } body := hm.Terms{ e.H2()(hm.Text("Edit your event")), e.Form(a.Action(pathDoCreate), a.Method(http.MethodPost))( e.Label(a.For(fieldNameEventName))(hm.Text("Event name")), e.Input( a.Name(fieldNameEventName), a.Value(event.Name), ), e.Br(), e.Label(a.For(fieldNameDescription))(hm.Text("Description")), e.Textarea(a.Name(fieldNameEventName))(hm.Text(event.Description)), e.Br(), e.Label(a.For(fieldNameEarliest))(hm.Text("Earliest date")), e.Input( a.Name(fieldNameEarliest), a.Type("date"), a.Value(event.EarliestDate.Format(formDateLayout)), ), e.Br(), e.Label(a.For(fieldNameLatest))(hm.Text("Latest date")), e.Input( a.Name(fieldNameLatest), a.Type("date"), a.Value(event.LatestDate.Format(formDateLayout)), ), e.Br(), e.Input(a.Type("submit")), ), } _ = h.writePage(w, body) } func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.mux.ServeHTTP(w, r) } func (h *handler) writePage(w io.Writer, contents hm.Term) error { page := e.Html()( e.Head()( e.Title()(hm.Text(h.title)), ), e.Body()( e.H1()(hm.Text(h.title)), e.Div()(contents), ), ) _, err := hm.WriteDocument(w, page) return err } const ( fieldNameEarliest = "earliestDate" fieldNameLatest = "latestDate" fieldNameEventName = "eventName" fieldNameDescription = "eventDescription" ) const keyEventID = "event_id" const keyAdminCode = "admin_code" var chars = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") func genString(length int) (string, error) { charsLength := big.NewInt(int64(len(chars))) var maxN big.Int maxN.Exp(charsLength, big.NewInt(int64(length)), nil) n, err := rand.Int(rand.Reader, &maxN) if err != nil { return "", err } var buf bytes.Buffer for n.Cmp(&big.Int{}) == 1 { var charIdx big.Int n.DivMod(n, charsLength, &charIdx) _ = buf.WriteByte(chars[charIdx.Int64()]) } return buf.String(), nil } const formDateLayout = "2006-01-02" func main() { addr := flag.String("address", ":8080", "bind address for HTTP server as host:port") title := flag.String("title", "Henwen", "website title") baseURL := flag.String("baseURL", "http://localhost:8080", "base URL for HTTP routes") dbFileName := flag.String("db", "./henwen.db", "name of database file") flag.Parse() store, err := henwen.NewStore(*dbFileName, genString) if err != nil { log.Fatal(err) } srv := http.Server{ Addr: *addr, Handler: NewHandler(HandlerParams{ Store: store, Title: *title, BaseURL: *baseURL, }), } log.Println(srv.ListenAndServe()) }