Start back end and skeleton front end

This commit is contained in:
Brandon Dyck 2020-03-24 08:59:27 -06:00
commit 5f96a929d4
4 changed files with 266 additions and 0 deletions

8
go.mod Normal file
View File

@ -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
)

6
go.sum Normal file
View File

@ -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=

121
http/server.go Normal file
View File

@ -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())
}

131
store.go Normal file
View File

@ -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
}