From 5f96a929d462b10360ac382971b3c4447f7865ff Mon Sep 17 00:00:00 2001 From: Brandon Dyck Date: Tue, 24 Mar 2020 08:59:27 -0600 Subject: [PATCH] Start back end and skeleton front end --- go.mod | 8 +++ go.sum | 6 +++ http/server.go | 121 +++++++++++++++++++++++++++++++++++++++++++++ store.go | 131 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 266 insertions(+) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 http/server.go create mode 100644 store.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..25b6e6e --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module gitlab.codemonkeysoftware.net/b/henwen + +go 1.14 + +require ( + crawshaw.io/sqlite v0.2.5 + gitlab.codemonkeysoftware.net/b/hatmill v0.0.5 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d1aeadd --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +crawshaw.io/iox v0.0.0-20181124134642-c51c3df30797/go.mod h1:sXBiorCo8c46JlQV3oXPKINnZ8mcqnye1EkVkqsectk= +crawshaw.io/sqlite v0.2.5 h1:lUjWtEQJZApoL4V83cHsNeaGZkY5Ofmh8cGGBdZNxPU= +crawshaw.io/sqlite v0.2.5/go.mod h1:igAO5JulrQ1DbdZdtVq48mnZUBAPOeFzer7VhDWNtW4= +github.com/leanovate/gopter v0.2.4/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkOJ3uoLer8= +gitlab.codemonkeysoftware.net/b/hatmill v0.0.5 h1:7L70G1Qmdm7bSwH58qWY5psXOwoqcvokLhc4BPSIjWo= +gitlab.codemonkeysoftware.net/b/hatmill v0.0.5/go.mod h1:3jggrD9qkK8kXt0d3+Kwg3zti2PfPcociz7r3oxtjWU= diff --git a/http/server.go b/http/server.go new file mode 100644 index 0000000..87d718a --- /dev/null +++ b/http/server.go @@ -0,0 +1,121 @@ +package main + +import ( + "fmt" + "io" + "log" + "net/http" + "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" +) + +const Addr = ":8080" +const Title = "Henwen" + +const ( + PathRoot = "/" + PathCreate = "/create" + PathDoCreate = "/create/do" +) + +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" +) + +func pageCreate() h.Term { + return h.Terms{ + e.H2()(h.Text("Create an event")), + e.Form(a.Action(PathDoCreate), a.Method("POST"))( + e.Label(a.For(fieldNameEventName))(h.Text("Event name")), + e.Input(a.Name(fieldNameEventName)), + + 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 pageDoCreate(name string, earliest, latest time.Time) h.Term { + return h.Terms{ + e.H2()(h.Text("Created event!")), + + e.H3()(h.Text("Name")), + h.Text(name), + + e.H3()(h.Text("Earliest date")), + h.Text(earliest.Format(time.ANSIC)), + + e.H3()(h.Text("Latest date")), + h.Text(latest.Format(time.ANSIC)), + } +} + +func main() { + mux := http.NewServeMux() + mux.HandleFunc(PathRoot, func(w http.ResponseWriter, r *http.Request) { + _ = writePage(w, pageRoot()) + }) + mux.HandleFunc(PathCreate, func(w http.ResponseWriter, r *http.Request) { + _ = writePage(w, pageCreate()) + }) + mux.HandleFunc(PathDoCreate, func(w http.ResponseWriter, r *http.Request) { + const dateFmt = "2006-01-02" + 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 + } + + _ = writePage(w, pageDoCreate(eventName, earliest, latest)) + }) + + srv := http.Server{ + Addr: Addr, + Handler: mux, + } + log.Println(srv.ListenAndServe()) +} diff --git a/store.go b/store.go new file mode 100644 index 0000000..430c75e --- /dev/null +++ b/store.go @@ -0,0 +1,131 @@ +package henwen + +import ( + "context" + "os" + "time" + + "crawshaw.io/sqlite/sqlitex" +) + +type GenString func() (string, error) + +type Store struct { + pool *sqlitex.Pool + genString GenString +} + +const InMemory = ":memory:" + +func NewStore(filename string, genString GenString) (*Store, error) { + var needCreate bool + if filename == InMemory { + filename = "file::memory:?mode=memory" + needCreate = true + } else if _, err := os.Stat(filename); os.IsNotExist(err) { + needCreate = true + } + // If the file exists, then assume it was created properly. + + pool, err := sqlitex.Open(filename, 0, 10) + if err != nil { + return nil, err + } + store := &Store{ + pool: pool, + genString: genString, + } + if needCreate { + err = store.createSchema() + if err != nil { + defer pool.Close() + return nil, err + } + } + + return store, nil +} + +func (s *Store) Close() error { + return s.pool.Close() +} + +const schema = ` + CREATE TABLE event ( + id INTEGER PRIMARY KEY, + alpha_id TEXT NOT NULL, + admin_alpha_id TEXT NOT NULL, + name TEXT NOT NULL, + earliest_date DATE NOT NULL, + latest_date DATE NOT NULL, + duration INTEGER NOT NULL, + + UNIQUE (alpha_id) + ); + + CREATE TABLE response ( + id INTEGER PRIMARY KEY, + alpha_id TEXT NOT NULL, + event_id INTEGER NOT NULL, + + UNIQUE (alpha_id), + FOREIGN KEY (event_id) REFERENCES event(id) + ); + + CREATE TABLE response_time ( + response_id INTEGER PRIMARY KEY, + time INTEGER NOT NULL, + + CHECK (0 <= time < 24) + );` + +func (s *Store) createSchema() error { + conn := s.pool.Get(context.Background()) + defer s.pool.Put(conn) + return sqlitex.ExecScript(conn, schema) +} + +type CreateEventCommand struct { + Name string + EarliestDate, LatestDate time.Time + Duration int +} + +type CreateEventResult struct { + AlphaID, AdminAlphaID string +} + +func (s *Store) CreateEvent(ctx context.Context, cmd CreateEventCommand) (result CreateEventResult, err error) { + conn := s.pool.Get(ctx) + defer s.pool.Put(conn) + + alphaID, err := s.genString() + if err != nil { + return + } + + adminAlphaID, err := s.genString() + if err != nil { + return + } + + const dateFmt = "2006-01-02" + const query = ` + INSERT INTO event(alpha_id, admin_alpha_id, name, earliest_date, latest_date, duration) + VALUES (?, ?, ?, ?, ?, ?);` + err = sqlitex.Exec(conn, query, nil, + alphaID, + adminAlphaID, + cmd.Name, + cmd.EarliestDate.Format(dateFmt), + cmd.LatestDate.Format(dateFmt), + cmd.Duration, + ) + if err != nil { + return + } + + result.AdminAlphaID = adminAlphaID + result.AlphaID = alphaID + return +}