Files
chirpy/internal/server/handlers_chirps.go
T

206 lines
5.3 KiB
Go
Raw Normal View History

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)
}