diff --git a/cms/internal/handlers/handlers.go b/cms/internal/handlers/handlers.go index 5d37ca0..0544ac4 100644 --- a/cms/internal/handlers/handlers.go +++ b/cms/internal/handlers/handlers.go @@ -10,14 +10,35 @@ func Health(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) } +// problemDetails is an error body in the RFC 9457 "Problem Details for HTTP +// APIs" format. Type stays "about:blank" — the value RFC 9457 defines for +// problems with no dedicated documentation URI — which is why Title carries +// the plain HTTP status phrase and Detail carries the occurrence-specific +// explanation. +type problemDetails struct { + Type string `json:"type"` + Title string `json:"title"` + Status int `json:"status"` + Detail string `json:"detail,omitempty"` +} + func writeJSON(w http.ResponseWriter, status int, body any) { - w.Header().Set("Content-Type", "application/json") + writeJSONContent(w, "application/json", status, body) +} + +func writeProblem(w http.ResponseWriter, status int, detail string) { + writeJSONContent(w, "application/problem+json", status, problemDetails{ + Type: "about:blank", + Title: http.StatusText(status), + Status: status, + Detail: detail, + }) +} + +func writeJSONContent(w http.ResponseWriter, contentType string, status int, body any) { + w.Header().Set("Content-Type", contentType) w.WriteHeader(status) if err := json.NewEncoder(w).Encode(body); err != nil { log.Printf("Something Went Wrong Writing The Response: %s", err) } } - -func writeJSONError(w http.ResponseWriter, status int, message string) { - writeJSON(w, status, map[string]string{"error": message}) -} diff --git a/cms/internal/handlers/videos.go b/cms/internal/handlers/videos.go index e409e7f..22e8f22 100644 --- a/cms/internal/handlers/videos.go +++ b/cms/internal/handlers/videos.go @@ -27,7 +27,7 @@ func ListCategories(w http.ResponseWriter, r *http.Request) { categories, err := services.DB.ListCategories(r.Context()) if err != nil { log.Printf("Something Went Wrong Loading Categories: %s", err) - writeJSONError(w, http.StatusInternalServerError, "Something Went Wrong Loading Categories") + writeProblem(w, http.StatusInternalServerError, "Something Went Wrong Loading Categories") return } @@ -52,25 +52,25 @@ func PresignVideoUpload(w http.ResponseWriter, r *http.Request) { var req presignRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - writeJSONError(w, http.StatusBadRequest, "Invalid request.") + writeProblem(w, http.StatusBadRequest, "Invalid request.") return } if !strings.HasPrefix(req.ContentType, "video/") { - writeJSONError(w, http.StatusUnprocessableEntity, fmt.Sprintf("Unsupported file type (%s). Please upload a video.", req.ContentType)) + writeProblem(w, http.StatusUnprocessableEntity, fmt.Sprintf("Unsupported file type (%s). Please upload a video.", req.ContentType)) return } filename, err := randomFilename(req.FileName) if err != nil { - writeJSONError(w, http.StatusInternalServerError, "Could not prepare upload.") + writeProblem(w, http.StatusInternalServerError, "Could not prepare upload.") return } key := videoUploadPrefix + "/" + filename presignedURL, err := services.S3Client.GetPresignedURL(r.Context(), key, "video/mp4", time.Hour) if err != nil { - writeJSONError(w, http.StatusInternalServerError, "Could not prepare upload.") + writeProblem(w, http.StatusInternalServerError, "Could not prepare upload.") return } @@ -106,19 +106,19 @@ func CompleteVideoUpload(w http.ResponseWriter, r *http.Request) { var req completeRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - writeJSONError(w, http.StatusBadRequest, "Invalid request.") + writeProblem(w, http.StatusBadRequest, "Invalid request.") return } title := strings.TrimSpace(req.Title) if title == "" { - writeJSONError(w, http.StatusUnprocessableEntity, "Title is required.") + writeProblem(w, http.StatusUnprocessableEntity, "Title is required.") return } key := strings.TrimSpace(req.Key) if key == "" || !strings.HasPrefix(key, videoUploadPrefix+"/") { - writeJSONError(w, http.StatusUnprocessableEntity, "A video file is required.") + writeProblem(w, http.StatusUnprocessableEntity, "A video file is required.") return } @@ -132,7 +132,7 @@ func CompleteVideoUpload(w http.ResponseWriter, r *http.Request) { jobID, err := services.MediaConvertClient.QueueEncodingJob(r.Context(), key) if err != nil { log.Printf("Something Went Wrong On Creation Of Transcoding Job: %s", err) - writeJSONError(w, http.StatusInternalServerError, "Something Went Wrong On Creation Of Transcoding Job") + writeProblem(w, http.StatusInternalServerError, "Something Went Wrong On Creation Of Transcoding Job") return } @@ -149,7 +149,7 @@ func CompleteVideoUpload(w http.ResponseWriter, r *http.Request) { }) if err != nil { log.Printf("Something Went Wrong Saving The Video Record: %s", err) - writeJSONError(w, http.StatusInternalServerError, "Something Went Wrong Saving The Video Record") + writeProblem(w, http.StatusInternalServerError, "Something Went Wrong Saving The Video Record") return }