1
0
Fork 0
datetime.link/app.go

140 lines
3.3 KiB
Go

package main
import (
"errors"
"html/template"
"net/http"
"github.com/serverwentdown/datetime.link/data"
"go.uber.org/zap"
)
// ErrNoTemplate is returned when a template was not found on the server
var ErrNoTemplate = errors.New("missing template")
// Datetime is the main application server
type Datetime struct {
*http.ServeMux
tmpl *template.Template
cities map[string]*data.City
}
// NewDatetime creates an application instance. It assumes certain resources
// like templates and data exist
func NewDatetime() (*Datetime, error) {
// Data
tmpl, err := template.New("templates").Funcs(templateFuncs).ParseGlob("templates/*")
if err != nil {
return nil, err
}
cities, err := data.ReadCities()
if err != nil {
return nil, err
}
// Mux
mux := http.NewServeMux()
app := &Datetime{mux, tmpl, cities}
// Routes
mux.Handle("/data/", http.FileServer(http.Dir(".")))
mux.Handle("/js/", http.FileServer(http.Dir("assets")))
mux.Handle("/css/", http.FileServer(http.Dir("assets")))
mux.Handle("/favicon.ico", http.FileServer(http.Dir("assets")))
mux.HandleFunc("/search", app.search)
mux.HandleFunc("/", app.index)
return app, nil
}
type appRequest struct {
App Datetime
Req Request
}
type appSearch struct {
App Datetime
Search []*data.City
}
// index handles all incoming page requests
func (app Datetime) index(w http.ResponseWriter, req *http.Request) {
var err error
if req.Method != http.MethodGet && req.Method != http.MethodHead {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
tmpl := app.loadTemplate("index", w, req)
if tmpl == nil {
return
}
if req.Method == http.MethodHead {
return
}
request := Request{}
if req.URL.Path != "/" {
request, err = ParseRequest(req.URL)
if errors.Is(err, ErrComponentsMismatch) {
l.Debug("not matching components", zap.Error(err))
app.error(HTTPError{http.StatusNotFound, err}, w, req)
return
}
if errors.Is(err, ErrInvalidTime) {
l.Debug("not matching components", zap.Error(err))
app.error(HTTPError{http.StatusNotFound, err}, w, req)
return
}
if err != nil {
l.Info("parse failed", zap.Error(err))
app.error(HTTPError{http.StatusBadRequest, err}, w, req)
return
}
}
l.Debug("rendering template", zap.Reflect("request", request))
err = tmpl.Execute(w, appRequest{app, request})
if err != nil {
l.Error("templating failed", zap.Error(err))
app.templateError(HTTPError{http.StatusInternalServerError, err}, w, req)
return
}
}
// search handles zone search queries
func (app Datetime) search(w http.ResponseWriter, req *http.Request) {
var err error
if req.Method != http.MethodGet && req.Method != http.MethodHead {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
tmpl := app.loadTemplate("search", w, req)
if tmpl == nil {
return
}
if req.Method == http.MethodHead {
return
}
// TODO: do search
query := req.URL.Query()
search, err := FullSearchCities(app.cities, query.Get("zone"))
if err != nil {
l.Error("search failed", zap.Error(err))
app.error(HTTPError{http.StatusInternalServerError, err}, w, req)
return
}
//l.Debug("rendering template", zap.Reflect("search", search))
err = tmpl.Execute(w, appSearch{app, search})
if err != nil {
l.Error("templating failed", zap.Error(err))
app.templateError(HTTPError{http.StatusInternalServerError, err}, w, req)
return
}
}