package handlers import ( "crypto/rand" "encoding/hex" "encoding/json" "fmt" "log" "net/http" "path/filepath" "strings" "thamanyah/cms/v2/internal/services" "thamanyah/cms/v2/internal/views" "time" ) const ( videoUploadPrefix = "videos" uploadURLExpiry = 15 * time.Minute maxJSONBodySize = 1 << 20 // 1 MiB ) func NewVideo(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) http.Error(w, "Something Went Wrong Loading Categories", http.StatusInternalServerError) return } views.VideoUpload(categories).Render(r.Context(), w) } type presignRequest struct { FileName string `json:"fileName"` ContentType string `json:"contentType"` } type presignResponse struct { UploadURL string `json:"uploadUrl"` Key string `json:"key"` } func PresignVideoUpload(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxJSONBodySize) var req presignRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSONError(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)) return } filename, err := randomFilename(req.FileName) if err != nil { writeJSONError(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.") return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(presignResponse{UploadURL: presignedURL, Key: key}) } type completeRequest struct { Title string `json:"title"` Description string `json:"description"` Categories []string `json:"categories"` Tags string `json:"tags"` FileName string `json:"fileName"` Key string `json:"key"` } func CompleteVideoUpload(w http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(w, r.Body, maxJSONBodySize) var req completeRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { renderUploadError(w, r, "Invalid request.") return } title := strings.TrimSpace(req.Title) if title == "" { renderUploadError(w, r, "Title is required.") return } key := strings.TrimSpace(req.Key) if key == "" || !strings.HasPrefix(key, videoUploadPrefix+"/") { renderUploadError(w, r, "A video file is required.") return } categories := make([]string, 0, len(req.Categories)) for _, category := range req.Categories { if category = strings.TrimSpace(category); category != "" { categories = append(categories, category) } } jobID, err := services.MediaConvertClient.QueueEncodingJob(r.Context(), key) if err != nil { log.Printf("Something Went Wrong On Creation Of Transcoding Job: %s", err) renderUploadError(w, r, "Something Went Wrong On Creation Of Transcoding Job") return } _, err = services.DB.CreateVideo(r.Context(), services.Video{ Title: title, Description: strings.TrimSpace(req.Description), Categories: categories, Tags: strings.TrimSpace(req.Tags), FileName: strings.TrimSpace(req.FileName), StorageKey: key, MediaConvertJobID: jobID, Status: "processing", SizeBytes: 60, }) if err != nil { log.Printf("Something Went Wrong Saving The Video Record: %s", err) renderUploadError(w, r, "Something Went Wrong Saving The Video Record") return } views.VideoUploadSuccess(views.VideoMetadata{ Title: title, Description: strings.TrimSpace(req.Description), Categories: categories, Tags: strings.TrimSpace(req.Tags), FileName: strings.TrimSpace(req.FileName), JobID: jobID, StoredAs: key, SizeBytes: 60, }).Render(r.Context(), w) } func writeJSONError(w http.ResponseWriter, status int, message string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(map[string]string{"error": message}) } func renderUploadError(w http.ResponseWriter, r *http.Request, message string) { fmt.Println("Error Ocurred: " + message) w.WriteHeader(http.StatusUnprocessableEntity) views.VideoUploadError(message).Render(r.Context(), w) } func randomFilename(original string) (string, error) { ext := filepath.Ext(filepath.Base(original)) buf := make([]byte, 16) if _, err := rand.Read(buf); err != nil { return "", err } return hex.EncodeToString(buf) + ext, nil }