package main import ( "embed" "encoding/json" "fmt" "html/template" "log" "net/http" "strconv" "time" "github.com/gorilla/mux" ) //go:embed *.js *.html.in *.css *.png robots.txt var content embed.FS func handleEntryOptions(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "OPTIONS, POST, GET") w.Header().Set("Access-Control-Allow-Headers", "Content-Type") w.WriteHeader(http.StatusNoContent) } func handleEntryPost(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "OPTIONS, POST, GET") w.Header().Set("Access-Control-Allow-Headers", "Content-Type") var entry Entry err := json.NewDecoder(r.Body).Decode(&entry) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } err = InsertEntry(&entry) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } log.Println("New entry for", entry.Location) } type EntriesPage struct { Total uint `json:"total"` Offset uint `json:"offset"` Size uint `json:"size"` Entries []Entry `json:"entries"` } func handleEntryGet(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "OPTIONS, POST, GET") w.Header().Set("Access-Control-Allow-Headers", "Content-Type") from64, _ := strconv.ParseUint(r.FormValue("from"), 10, 32) from := uint(from64) count, err := CountEntries() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } entries, err := ListEntries(from, 1000) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if entries == nil { entries = []Entry{} } page := EntriesPage{Total: count, Offset: from, Size: uint(len(entries)), Entries: entries} err = json.NewEncoder(w).Encode(&page) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } } type Stat struct { Title string Description string } type Home struct { Stats []Stat } func count(title string, amount int) Stat { return Stat{title, fmt.Sprintf("%d visits", amount)} } func handleHome(pathname string) func(http.ResponseWriter, *http.Request) { t := template.Must(template.ParseFS(content, pathname)) return func(w http.ResponseWriter, r *http.Request) { var home Home now := time.Now().UTC() last_day, err := EntriesSince(now.AddDate(0, 0, -1)) if err == nil { home.Stats = append(home.Stats, count("Last 24 hours", len(last_day))) } last_month, err := EntriesSince(now.AddDate(0, 0, -30)) if err == nil { home.Stats = append(home.Stats, count("Last 30 days", len(last_month))) } total, err := CountEntries() if err == nil { home.Stats = append(home.Stats, count("Total", int(total))) } first, err := FirstEntry() if err == nil { home.Stats = append(home.Stats, Stat{"First visit", first.StartedAt.Format("_2 January 2006, 15:04")}) } if len(home.Stats) < 1 { http.Error(w, err.Error(), http.StatusInternalServerError) return } t.Execute(w, home) } } func handleRequests(port string) { router := mux.NewRouter() router.HandleFunc("/entries", handleEntryOptions).Methods("OPTIONS") router.HandleFunc("/entries", handleEntryPost).Methods("POST") router.HandleFunc("/entries", handleEntryGet).Methods("GET") router.HandleFunc("/", handleHome("index.html.in")).Methods("GET") router.PathPrefix("/").Handler(http.FileServer(http.FS(content))) log.Fatal(http.ListenAndServe(port, router)) } func main() { log.Println("Starting up") cfg := LoadConfig() InitEntries(cfg.DB) defer CloseEntries() handleRequests(cfg.Port) }