package main import ( "database/sql" "encoding/base64" "encoding/json" "fmt" "log" "net/http" "os" "strconv" "strings" "time" ) var token string type Handler struct { db *sql.DB ntfy *Ntfy visits *VisitHandler } func getStaticFile(relPath string, contentType string, w http.ResponseWriter) { file, err := os.ReadFile(relPath) if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "%s", err.Error()) return } w.Header().Add("Content-Type", contentType) w.WriteHeader(http.StatusOK) w.Write(file) } func isAuthorized(r *http.Request) bool { auth := r.Header.Get("Authorization") if auth == "" { return false } return auth == fmt.Sprintf("Basic %s", token) } func (h *Handler) getRsvps(w http.ResponseWriter, r *http.Request) { if !isAuthorized(r) { w.WriteHeader(http.StatusUnauthorized) return } rsvps, err := GetRsvps(h.db) if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "%s", err.Error()) return } marshalledRsvps, err := json.Marshal(rsvps) if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Fprintf(w, "%s", err.Error()) return } w.Header().Add("Content-Type", "application/json") w.WriteHeader(http.StatusOK) fmt.Fprintf(w, "%s", marshalledRsvps) } func (h *Handler) createRsvp(w http.ResponseWriter, r *http.Request) { err := r.ParseForm() if err != nil { fmt.Println(err) w.WriteHeader(http.StatusBadRequest) return } attending := r.Form.Get("attending") == "true" partySize, err := strconv.ParseInt(r.Form.Get("party-size"), 10, 64) if err != nil { fmt.Println(err) w.WriteHeader(http.StatusBadRequest) return } partyMembers := make([]Member, partySize) for i := range partySize { name := r.Form.Get(fmt.Sprintf("name-%d", i)) child := r.Form.Has(fmt.Sprintf("child-%d", i)) && r.Form.Get(fmt.Sprintf("child-%d", i)) == "true" dietaryPreferences := r.Form.Get(fmt.Sprintf("diet-%d", i)) member := Member{ Name: name, Child: child, DietaryPreferences: dietaryPreferences, } partyMembers[i] = member } rsvp := Rsvp{ Attending: attending, PartySize: partySize, PartyMembers: partyMembers, } _, err = rsvp.CreateRsvp(h.db) if err != nil { fmt.Println(err) w.WriteHeader(http.StatusInternalServerError) return } if h.ntfy != nil { SendRsvpNotification(h.ntfy, &rsvp) } w.Header().Add("Location", "/rsvp_confirmed") w.WriteHeader(http.StatusSeeOther) } func SetupAuth() { var username string var password string for _, entry := range os.Environ() { split := strings.Split(entry, "=") switch split[0] { case "USERNAME": username = split[1] case "PASSWORD": password = split[1] } if username != "" && password != "" { break } } if username == "" || password == "" { log.Fatal("no authorization details") } sb := new(strings.Builder) encoder := base64.NewEncoder(base64.StdEncoding, sb) encoder.Write(fmt.Appendf(nil, "%s:%s", username, password)) encoder.Close() token = sb.String() fmt.Println("auth ready") } func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Printf("%s - [%s] (%s) %s\n", time.Now().Format(time.RFC3339), r.RemoteAddr, r.Method, r.URL) visit := h.visits.HandleVisit(r.RemoteAddr) if visit != nil { h.ntfy.PublishNewVisitNotification(visit) } switch true { case r.Method == "GET" && r.URL.Path == "/favicon.ico": getStaticFile("./client/favicon.ico", "image/png", w) case r.Method == "GET" && r.URL.Path == "/": getStaticFile("./client/index.html", "text/html", w) case r.Method == "GET" && r.URL.Path == "/rsvp": getStaticFile("./client/rsvp.html", "text/html", w) case r.Method == "GET" && r.URL.Path == "/rsvp_confirmed": getStaticFile("./client/rsvp_confirmed.html", "text/html", w) case r.Method == "GET" && r.URL.Path == "/rsvps_list": getStaticFile("./client/rsvps_list.html", "text/html", w) case r.Method == "GET" && r.URL.Path == "/login": getStaticFile("./client/login.html", "text/html", w) case r.Method == "GET" && r.URL.Path == "/index.js": getStaticFile("./client/index.js", "text/javascript", w) case r.Method == "GET" && r.URL.Path == "/rsvp.js": getStaticFile("./client/rsvp.js", "text/javascript", w) case r.Method == "GET" && r.URL.Path == "/rsvp_confirmed.js": getStaticFile("./client/rsvp_confirmed.js", "text/javascript", w) case r.Method == "GET" && r.URL.Path == "/rsvps_list.js": getStaticFile("./client/rsvps_list.js", "text/javascript", w) case r.Method == "GET" && r.URL.Path == "/login.js": getStaticFile("./client/login.js", "text/javascript", w) case r.Method == "GET" && r.URL.Path == "/style.css": getStaticFile("./client/style.css", "text/css", w) case r.Method == "GET" && r.URL.Path == "/api/rsvps": h.getRsvps(w, r) case r.Method == "POST" && r.URL.Path == "/api/rsvps": h.createRsvp(w, r) default: w.WriteHeader(http.StatusNotFound) } }