code/client/main.go (151 lines of code) (raw):

// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package main import ( "context" "embed" "encoding/json" "fmt" "io/fs" "net/http" "os" "github.com/charmbracelet/log" "github.com/gorilla/handlers" "github.com/gorilla/mux" ) //go:embed static var static embed.FS const ( mongoport = "27017" defaultPort = "80" ) func main() { dbHost := os.Getenv("DBHOST") port := os.Getenv("PORT") if port == "" { port = defaultPort } if dbHost == "" { log.Fatal("DBHOST env not set. Need ip/host with mongo on port", mongoport) } ctx := context.Background() svc, err := newTrainerService(ctx, dbHost, mongoport) if err != nil { log.Fatal("error connecting to mongo: ", err) } fSys, err := fs.Sub(static, "static") if err != nil { panic(err) } router := mux.NewRouter().StrictSlash(true) router.HandleFunc("/healthz", healthHandler).Methods(http.MethodGet) router.HandleFunc("/api/v1/healthz", healthHandler).Methods(http.MethodGet) router.HandleFunc("/api/v1/trainer", listHandler(svc)).Methods(http.MethodGet) router.HandleFunc("/api/v1/trainer", createHandler(svc)).Methods(http.MethodPost) router.HandleFunc("/api/v1/trainer", deleteHandler(svc)).Methods(http.MethodDelete) router.HandleFunc("/api/v1/trainer", updateHandler(svc)).Methods(http.MethodPut) router.PathPrefix("/").Handler(logWrap(http.FileServer(http.FS(fSys)))) headersOk := handlers.AllowedHeaders([]string{"X-Requested-With"}) originsOk := handlers.AllowedOrigins([]string{"*"}) methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS", "DELETE"}) log.Fatal(http.ListenAndServe(":"+port, handlers.CORS(originsOk, headersOk, methodsOk)(router))) } func respond(w http.ResponseWriter, r *http.Request, status int, content []byte, err error) { c := fmt.Sprintf("%s %s %s %s %d", r.Host, r.Method, r.RequestURI, r.Proto, status) w.WriteHeader(status) if content == nil && err != nil { log.With("source", "api").Error(c, "error", err) content = []byte(err.Error()) w.Write(content) return } log.With("source", "api").Info(c) w.Write(content) return } func logWrap(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { recorder := &statusRecorder{ ResponseWriter: w, Status: 200, } h.ServeHTTP(recorder, r) c := fmt.Sprintf("%s %s %s %s %d", r.Host, r.Method, r.RequestURI, r.Proto, recorder.Status) log.With("source", "static").Info(c) }) } type statusRecorder struct { http.ResponseWriter Status int } func (r *statusRecorder) WriteHeader(status int) { r.Status = status r.ResponseWriter.WriteHeader(status) } func healthHandler(w http.ResponseWriter, r *http.Request) { respond(w, r, http.StatusInternalServerError, []byte("ok"), nil) return } func listHandler(svc trainerCRUDer) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { trainers, err := svc.list(context.Background()) if err != nil { respond(w, r, http.StatusInternalServerError, nil, err) return } j, err := json.Marshal(trainers) if err != nil { respond(w, r, http.StatusInternalServerError, nil, err) return } respond(w, r, http.StatusOK, j, nil) return } } func createHandler(svc trainerCRUDer) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { t := trainer{} if err := json.NewDecoder(r.Body).Decode(&t); err != nil { respond(w, r, http.StatusInternalServerError, nil, err) return } if err := svc.create(context.Background(), t); err != nil { respond(w, r, http.StatusInternalServerError, nil, err) return } j, err := json.Marshal(t) if err != nil { respond(w, r, http.StatusInternalServerError, nil, err) return } respond(w, r, http.StatusCreated, j, nil) return } } func deleteHandler(svc trainerCRUDer) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { t := trainer{} if err := json.NewDecoder(r.Body).Decode(&t); err != nil { respond(w, r, http.StatusInternalServerError, nil, err) return } if err := svc.delete(context.Background(), t); err != nil { respond(w, r, http.StatusInternalServerError, nil, err) return } respond(w, r, http.StatusNoContent, nil, nil) return } } func updateHandler(svc trainerCRUDer) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { data := map[string]trainer{} if err := json.NewDecoder(r.Body).Decode(&data); err != nil { respond(w, r, http.StatusInternalServerError, nil, err) return } if err := svc.update(context.Background(), data["original"], data["replacement"]); err != nil { respond(w, r, http.StatusInternalServerError, nil, err) return } respond(w, r, http.StatusOK, nil, nil) return } }