package server import ( "fmt" "net/http" "slices" "strings" "time" "github.com/StevanFreeborn/chirpy/internal/api" "github.com/StevanFreeborn/chirpy/internal/auth" "github.com/StevanFreeborn/chirpy/internal/database" "github.com/google/uuid" ) const ( ascending = "asc" descending = "desc" ) type createChirpRequest struct { Body string `json:"body"` UserId string `json:"user_id"` } type chirpResponse struct { Id string `json:"id"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` Body string `json:"body"` UserId string `json:"user_id"` } // toChirpResponse maps a database Chirp to its API response shape. func toChirpResponse(c database.Chirp) chirpResponse { return chirpResponse{ Id: c.ID.String(), CreatedAt: c.CreatedAt.Format(time.RFC3339), UpdatedAt: c.UpdatedAt.Format(time.RFC3339), Body: c.Body, UserId: c.UserID.String(), } } // HandleCreateChirp creates a chirp for the authenticated user. func (s *Server) HandleCreateChirp(w http.ResponseWriter, r *http.Request) { bearerToken, err := auth.GetBearerToken(r.Header) if err != nil { api.WriteError(w, http.StatusUnauthorized, "You are not authorized to perform this action") return } requestUserId, err := auth.ValidateJWT(bearerToken, s.jwtSecret) if err != nil { api.WriteError(w, http.StatusUnauthorized, "You are not authorized to perform this action") return } createChirpRequest, err := api.DecodeJSON[createChirpRequest](r) if err != nil { api.WriteError(w, http.StatusBadRequest, err.Error()) return } if len(createChirpRequest.Body) > 140 { api.WriteError(w, http.StatusBadRequest, "Chirp is too long") return } createChirpParams := database.CreateChirpParams{ Body: cleanProfanity(createChirpRequest.Body), UserID: requestUserId, } createdChirp, err := s.db.CreateChirp(r.Context(), createChirpParams) if err != nil { api.WriteError(w, http.StatusInternalServerError, "Failed to create chirp. 🤷🏻‍♂️") return } api.WriteJSON(w, http.StatusCreated, toChirpResponse(createdChirp)) } // HandleGetChirps lists chirps, optionally filtered by author_id and sorted by // the sort query parameter. func (s *Server) HandleGetChirps(w http.ResponseWriter, r *http.Request) { authorId := r.URL.Query().Get("author_id") sortDir := r.URL.Query().Get("sort") hasAuthorId := strings.TrimSpace(authorId) != "" hasSort := strings.TrimSpace(sortDir) != "" validAuthorId, err := uuid.Parse(authorId) if hasAuthorId && err != nil { api.WriteError(w, http.StatusBadRequest, "Author id must be a valid uuid") return } if hasSort && sortDir != ascending && sortDir != descending { api.WriteError(w, http.StatusBadRequest, fmt.Sprintf("Sort direction must be either '%s' or '%s'", ascending, descending)) return } if !hasSort { sortDir = ascending } var existingChirps []database.Chirp if hasAuthorId { existingChirps, err = s.db.GetChirpsByAuthor(r.Context(), validAuthorId) } else { existingChirps, err = s.db.GetAllChirps(r.Context()) } if err != nil { api.WriteError(w, http.StatusInternalServerError, "Unable to retrieve chirps") return } slices.SortFunc(existingChirps, func(a, b database.Chirp) int { if sortDir == descending { return b.CreatedAt.Compare(a.CreatedAt) } return a.CreatedAt.Compare(b.CreatedAt) }) chirps := make([]chirpResponse, 0, len(existingChirps)) for _, c := range existingChirps { chirps = append(chirps, toChirpResponse(c)) } api.WriteJSON(w, http.StatusOK, chirps) } // HandleGetChirp returns a single chirp by id. func (s *Server) HandleGetChirp(w http.ResponseWriter, r *http.Request) { chirpId := r.PathValue("id") validChirpId, err := uuid.Parse(chirpId) if err != nil { api.WriteError(w, http.StatusBadRequest, "Chirp id is not valid. id must be valid UUID.") return } existingChirp, err := s.db.GetChirpById(r.Context(), validChirpId) if err != nil { api.WriteError(w, http.StatusNotFound, "No chirp with given id found.") return } api.WriteJSON(w, http.StatusOK, toChirpResponse(existingChirp)) } // HandleDeleteChirp deletes a chirp owned by the authenticated user. func (s *Server) HandleDeleteChirp(w http.ResponseWriter, r *http.Request) { accessToken, err := auth.GetBearerToken(r.Header) if err != nil { api.WriteError(w, http.StatusUnauthorized, "You are not authorized to perform this action") return } requestUserId, err := auth.ValidateJWT(accessToken, s.jwtSecret) if err != nil { api.WriteError(w, http.StatusUnauthorized, "You are not authorized to perform this action") return } chirpId := r.PathValue("id") validChirpId, err := uuid.Parse(chirpId) if err != nil { api.WriteError(w, http.StatusBadRequest, "Chirp id must be a valid uuid for an existing chirp") return } existingChirp, err := s.db.GetChirpById(r.Context(), validChirpId) if err != nil { api.WriteError(w, http.StatusNotFound, "Chirp id must be a valid uuid for an existing chirp") return } if existingChirp.UserID != requestUserId { api.WriteError(w, http.StatusForbidden, "You can not delete a chirp that does not belong to you") return } if err := s.db.DeleteChirpById(r.Context(), existingChirp.ID); err != nil { api.WriteError(w, http.StatusInternalServerError, "Unable to delete chirp") return } api.WriteJSON(w, http.StatusNoContent, nil) }