-
Notifications
You must be signed in to change notification settings - Fork 0
/
app.go
139 lines (119 loc) · 3.3 KB
/
app.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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
}
}