From d3811aa1b9477ee97e3e2418134b5bbdc8fe1c43 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn Date: Wed, 5 Aug 2026 10:13:23 -0500 Subject: [PATCH] feat: implement basic routes --- assets/logo.png | Bin 0 -> 32010 bytes index.html | 5 ++ main.go | 175 +++++++++++++++++++++++++++++++++++++++++++++++- run.sh | 2 + 4 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 assets/logo.png create mode 100644 index.html create mode 100755 run.sh diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..199ac75c5e5e2a706fc7f65be9b1ef32f61c1b9d GIT binary patch literal 32010 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 AQvd(} literal 0 HcmV?d00001 diff --git a/index.html b/index.html new file mode 100644 index 0000000..e1fbb62 --- /dev/null +++ b/index.html @@ -0,0 +1,5 @@ + + +

Welcome to Chirpy

+ + diff --git a/main.go b/main.go index a3dd973..2602047 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,178 @@ package main -import "fmt" +import ( + "context" + "encoding/json" + "errors" + "fmt" + "log" + "net/http" + "os" + "os/signal" + "slices" + "strings" + "sync/atomic" + "syscall" + "time" +) + +type server struct { + fileServerHits atomic.Int32 +} + +type chirpRequest struct { + Body string `json:"body"` +} + +type validationError struct { + Err string `json:"error"` +} + +type chirpResponse struct { + CleanedBody string `json:"cleaned_body"` +} + +func writeJsonResponse(w http.ResponseWriter, response any) { + encoder := json.NewEncoder(w) + + if err := encoder.Encode(response); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } +} + +func (s *server) HandleFiles(prefix string) http.Handler { + fileServer := http.StripPrefix(prefix, http.FileServer(http.Dir("."))) + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + s.fileServerHits.Add(1) + fileServer.ServeHTTP(w, r) + }) +} + +func (s *server) HandleHealthChecks(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) +} + +func (s *server) HandleReset(w http.ResponseWriter, r *http.Request) { + s.fileServerHits.Store(0) + + w.Header().Add("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write([]byte("OK")) +} + +func (s *server) HandleMetrics(w http.ResponseWriter, r *http.Request) { + hits := s.fileServerHits.Load() + + w.Header().Add("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusOK) + + template := ` + + +

Welcome, Chirpy Admin

+

Chirpy has been visited %d times!

+ + + ` + + fmt.Fprintf(w, template, hits) +} + +func (s *server) HandleChirpValidation(w http.ResponseWriter, r *http.Request) { + blacklist := []string{ + "kerfuffle", + "sharbert", + "fornax", + } + + var chirpRequest chirpRequest + + defer r.Body.Close() + + w.Header().Add("Content-Type", "application/json") + + err := json.NewDecoder(r.Body).Decode(&chirpRequest) + + if err != nil { + w.WriteHeader(http.StatusBadRequest) + writeJsonResponse(w, validationError{ + Err: "Unable to deserialize JSON", + }) + return + } + + if len(chirpRequest.Body) > 140 { + w.WriteHeader(http.StatusBadRequest) + writeJsonResponse(w, validationError{ + Err: "Chirp is too long", + }) + return + } + + words := strings.Split(chirpRequest.Body, " ") + sanitized := []string{} + + for _, word := range words { + if slices.Contains(blacklist, strings.ToLower(word)) { + sanitized = append(sanitized, "****") + continue + } + + sanitized = append(sanitized, word) + } + + cleanedBody := strings.Join(sanitized, " ") + + w.WriteHeader(http.StatusOK) + writeJsonResponse(w, chirpResponse{CleanedBody: cleanedBody}) +} func main() { - fmt.Println("Hello, World!") + server := &server{} + mux := http.NewServeMux() + + appRoot := "/app/" + + mux.Handle(appRoot, server.HandleFiles(appRoot)) + mux.HandleFunc("GET /admin/metrics", server.HandleMetrics) + mux.HandleFunc("POST /admin/reset", server.HandleReset) + + mux.HandleFunc("POST /api/validate_chirp", server.HandleChirpValidation) + mux.HandleFunc("GET /api/healthz", server.HandleHealthChecks) + + port := ":8080" + + httpServer := http.Server{ + Handler: mux, + Addr: port, + } + + go func() { + log.Printf("Server started and listening on %s\n", port) + + err := httpServer.ListenAndServe() + + if !errors.Is(err, http.ErrServerClosed) { + log.Fatalf("HTTP Server error: %v", err) + } + + log.Println("Stopped serving new connections") + }() + + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + <-sigChan + + shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second) + + defer shutdownRelease() + + if err := httpServer.Shutdown(shutdownCtx); err != nil { + log.Fatalf("HTTP shutdown error: %v", err) + } + + log.Println("Graceful shutdown complete") } diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..e59f582 --- /dev/null +++ b/run.sh @@ -0,0 +1,2 @@ +go build -o ./bin +./bin/chirpy