Start back end and skeleton front end
This commit is contained in:
		
							
								
								
									
										8
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								go.mod
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										6
									
								
								go.sum
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										121
									
								
								http/server.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										131
									
								
								store.go
									
									
									
									
									
										Normal 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 | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user