package main import ( "bytes" "context" "crypto/rand" "fmt" "io" "log" "math/big" "net/http" "net/url" "time" h "gitlab.codemonkeysoftware.net/b/hatmill" a "gitlab.codemonkeysoftware.net/b/hatmill/attribute" e "gitlab.codemonkeysoftware.net/b/hatmill/element" "gitlab.codemonkeysoftware.net/b/henwen" _ "gitlab.codemonkeysoftware.net/b/henwen" ) const Addr = ":8080" const Title = "Henwen" const baseURL = "http://localhost:8080" const dbFileName = "./henwen.db" const ( pathRoot = "/" pathCreate = "/create" pathDoCreate = "/create/do" pathAdmin = "/admin" ) var store *henwen.Store type handler struct { mux *http.ServeMux } func NewHandler() http.Handler { h := &handler{ 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) { _ = writePage(w, pageRoot()) } func (h *handler) handleCreate(w http.ResponseWriter, r *http.Request) { _ = writePage(w, pageCreate()) } func (h *handler) handleDoCreate(w http.ResponseWriter, r *http.Request) { earliest, err := time.Parse(dateFmt, r.FormValue(fieldNameEarliest)) if err != nil { fmt.Fprint(w, "bad earliest date") return } latest, err := time.Parse(dateFmt, 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) _ = writePage(w, pageDoCreate(eventName, description, earliest, latest)) } func (h *handler) handleAdmin(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() _ = writePage(w, pageAdmin(query.Get(keyEventID), query.Get(keyAdminCode))) } func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.mux.ServeHTTP(w, r) } func writePage(w io.Writer, contents h.Term) error { page := e.Html()( e.Head()( e.Title()(h.Text(Title)), ), e.Body()( e.H1()(h.Text(Title)), e.Div()(contents), ), ) _, err := h.WriteDocument(w, page) return err } func pageRoot() h.Term { return h.Terms{ e.H2()(h.Text("Welcome!")), e.A(a.Href(pathCreate))( h.Text("Create event"), ), } } const ( fieldNameEarliest = "earliestDate" fieldNameLatest = "latestDate" fieldNameEventName = "eventName" fieldNameDescription = "eventDescription" ) func pageCreate() h.Term { return h.Terms{ e.H2()(h.Text("Create an event")), e.Form(a.Action(pathDoCreate), a.Method(http.MethodPost))( e.Label(a.For(fieldNameEventName))(h.Text("Event name")), e.Input(a.Name(fieldNameEventName)), e.Label(a.For(fieldNameDescription))(h.Text("Description")), e.Textarea(a.Name(fieldNameEventName), a.Placeholder("What's going on?"))(), e.Label(a.For(fieldNameEarliest))(h.Text("Earliest date")), e.Input(a.Name(fieldNameEarliest), a.Type("date")), e.Label(a.For(fieldNameLatest))(h.Text("Latest date")), e.Input(a.Name(fieldNameLatest), a.Type("date")), e.Input(a.Type("submit")), ), } } func pageAdmin(alphaID, adminCode string) h.Term { event, err := store.GetEvent(context.Background(), henwen.GetEventQuery{ AlphaID: alphaID, AdminCode: adminCode, }) if err != nil { return h.Text(err.Error()) } return h.Terms{ e.H2()(h.Text("Edit your event")), e.Form(a.Action(pathDoCreate), a.Method(http.MethodPost))( e.Label(a.For(fieldNameEventName))(h.Text("Event name")), e.Input( a.Name(fieldNameEventName), a.Value(event.Name), ), e.Br(), e.Label(a.For(fieldNameDescription))(h.Text("Description")), e.Textarea(a.Name(fieldNameEventName))(h.Text(event.Description)), e.Br(), e.Label(a.For(fieldNameEarliest))(h.Text("Earliest date")), e.Input( a.Name(fieldNameEarliest), a.Type("date"), a.Value(event.EarliestDate.Format(dateFmt)), ), e.Br(), e.Label(a.For(fieldNameLatest))(h.Text("Latest date")), e.Input( a.Name(fieldNameLatest), a.Type("date"), a.Value(event.LatestDate.Format(dateFmt)), ), e.Br(), e.Input(a.Type("submit")), ), } } const keyEventID = "event_id" const keyAdminCode = "admin_code" func pageDoCreate(name, description string, earliest, latest time.Time) h.Term { event, err := store.CreateEvent(context.Background(), henwen.CreateEventCommand{ Name: name, Description: description, EarliestDate: earliest, LatestDate: latest, }) if err != nil { return h.Text(err.Error()) } var adminQuery = make(url.Values) adminQuery.Add(keyEventID, event.AlphaID) adminQuery.Add(keyAdminCode, event.AdminCode) adminURL := baseURL + pathAdmin + "?" + adminQuery.Encode() const dateDisplayFmt = "Monday, January 2, 2006" return h.Terms{ e.H2()(h.Text("Created event!")), e.P()( h.Text("You can find it again at "), e.A(a.Href(adminURL))(h.Text(adminURL)), h.Text("."), ), e.H3()(h.Text("Name")), h.Text(name), e.H3()(h.Text("Earliest date")), h.Text(earliest.Format(dateDisplayFmt)), e.H3()(h.Text("Latest date")), h.Text(latest.Format(dateDisplayFmt)), } } var chars = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") func genString() (string, error) { const length = 10 charsLength := big.NewInt(int64(len(chars))) var maxN big.Int maxN.Exp(charsLength, big.NewInt(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 dateFmt = "2006-01-02" func main() { var err error store, err = henwen.NewStore(dbFileName, genString) if err != nil { log.Fatal(err) } srv := http.Server{ Addr: Addr, Handler: NewHandler(), } log.Println(srv.ListenAndServe()) }