FEAT: Added MOre Video Statuses
Build, Push and Deploy CMS / build-push-deploy (push) Failing after 2m2s
Build, Push and Deploy CMS / build-push-deploy (push) Failing after 2m2s
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"thamanyah/cms/v2/internal/api"
|
||||
"thamanyah/cms/v2/internal/services"
|
||||
"time"
|
||||
)
|
||||
@@ -19,18 +20,14 @@ const (
|
||||
maxJSONBodySize = 1 << 20 // 1 MiB
|
||||
)
|
||||
|
||||
type categoriesResponse struct {
|
||||
Categories []string `json:"categories" example:"documentary,news"`
|
||||
}
|
||||
|
||||
// ListCategories returns the categories a video may be assigned to.
|
||||
//
|
||||
// @Summary List video categories
|
||||
// @Description Returns the fixed lookup set of categories a video can belong to. Values from this list are the only ones accepted in the `categories` field of POST /api/videos.
|
||||
// @Tags categories
|
||||
// @Produce json
|
||||
// @Success 200 {object} categoriesResponse
|
||||
// @Failure 500 {object} problemDetails "Categories could not be read from the database"
|
||||
// @Success 200 {object} api.CategoriesResponse
|
||||
// @Failure 500 {object} api.ProblemDetails "Categories could not be read from the database"
|
||||
// @Router /api/categories [get]
|
||||
func ListCategories(w http.ResponseWriter, r *http.Request) {
|
||||
categories, err := services.DB.ListCategories(r.Context())
|
||||
@@ -44,7 +41,7 @@ func ListCategories(w http.ResponseWriter, r *http.Request) {
|
||||
if categories == nil {
|
||||
categories = []string{}
|
||||
}
|
||||
writeJSON(w, http.StatusOK, categoriesResponse{Categories: categories})
|
||||
writeJSON(w, http.StatusOK, api.CategoriesResponse{Categories: categories})
|
||||
}
|
||||
|
||||
// allowedUploadContentTypes is the set of media types accepted for upload,
|
||||
@@ -57,16 +54,6 @@ var allowedUploadContentTypes = map[string]struct{}{
|
||||
"video/quicktime": {}, // .mov
|
||||
}
|
||||
|
||||
type presignRequest struct {
|
||||
FileName string `json:"fileName" example:"interview-cut.mov"`
|
||||
ContentType string `json:"contentType" enums:"video/mp4,video/quicktime" example:"video/quicktime"`
|
||||
}
|
||||
|
||||
type presignResponse struct {
|
||||
UploadURL string `json:"uploadUrl" example:"https://raw-uploads-bucket.s3.amazonaws.com/videos/a1b2....mov?X-Amz-Signature=..."`
|
||||
Key string `json:"key" example:"videos/a1b2c3d4e5f6.mov"`
|
||||
}
|
||||
|
||||
// PresignVideoUpload issues a presigned S3 PUT URL for a video upload.
|
||||
//
|
||||
// @Summary Create a presigned upload URL
|
||||
@@ -74,16 +61,16 @@ type presignResponse struct {
|
||||
// @Tags videos
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body presignRequest true "Name and media type of the file to be uploaded. contentType must be exactly 'video/mp4' or 'video/quicktime'."
|
||||
// @Success 200 {object} presignResponse
|
||||
// @Failure 400 {object} problemDetails "Request body was not valid JSON"
|
||||
// @Failure 422 {object} problemDetails "contentType was not 'video/mp4' or 'video/quicktime'"
|
||||
// @Failure 500 {object} problemDetails "Upload URL could not be issued"
|
||||
// @Param request body api.PresignRequest true "Name and media type of the file to be uploaded. contentType must be exactly 'video/mp4' or 'video/quicktime'."
|
||||
// @Success 200 {object} api.PresignResponse
|
||||
// @Failure 400 {object} api.ProblemDetails "Request body was not valid JSON"
|
||||
// @Failure 422 {object} api.ProblemDetails "contentType was not 'video/mp4' or 'video/quicktime'"
|
||||
// @Failure 500 {object} api.ProblemDetails "Upload URL could not be issued"
|
||||
// @Router /api/videos/presign [post]
|
||||
func PresignVideoUpload(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxJSONBodySize)
|
||||
|
||||
var req presignRequest
|
||||
var req api.PresignRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeProblem(w, http.StatusBadRequest, "Invalid request.",
|
||||
fmt.Sprintf("The request body could not be parsed as JSON (%s). Send an object with the string fields 'fileName' and 'contentType', and keep the body under 1 MiB.", err))
|
||||
@@ -111,50 +98,26 @@ func PresignVideoUpload(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, presignResponse{UploadURL: presignedURL, Key: key})
|
||||
}
|
||||
|
||||
type completeRequest struct {
|
||||
Title string `json:"title" example:"Inside the Newsroom"`
|
||||
Description string `json:"description" example:"A behind-the-scenes look at the evening bulletin."`
|
||||
Categories []string `json:"categories" example:"documentary,news"`
|
||||
Tags string `json:"tags" example:"media, press, riyadh"`
|
||||
FileName string `json:"fileName" example:"interview-cut.mov"`
|
||||
Key string `json:"key" example:"videos/a1b2c3d4e5f6.mov"`
|
||||
}
|
||||
|
||||
type videoResponse struct {
|
||||
ID string `json:"id" example:"0199f3a1-7c2e-7b21-9f0d-1a2b3c4d5e6f"`
|
||||
Title string `json:"title" example:"Inside the Newsroom"`
|
||||
Description string `json:"description" example:"A behind-the-scenes look at the evening bulletin."`
|
||||
Categories []string `json:"categories" example:"documentary,news"`
|
||||
Tags string `json:"tags" example:"media, press, riyadh"`
|
||||
FileName string `json:"fileName" example:"interview-cut.mov"`
|
||||
StorageKey string `json:"storageKey" example:"videos/a1b2c3d4e5f6.mov"`
|
||||
MediaConvertJobID string `json:"mediaConvertJobId" example:"1755300000000-abcdef"`
|
||||
Status string `json:"status" example:"processing"`
|
||||
SizeBytes int64 `json:"sizeBytes" example:"60"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
writeJSON(w, http.StatusOK, api.PresignResponse{UploadURL: presignedURL, Key: key})
|
||||
}
|
||||
|
||||
// CompleteVideoUpload registers an uploaded video and queues it for transcoding.
|
||||
//
|
||||
// @Summary Register an uploaded video
|
||||
// @Description Step 2 of the upload flow. Takes the storage key returned by POST /api/videos/presign — after the file has been PUT to the presigned URL — submits a MediaConvert transcoding job, and persists the video with its category links. The returned record has status "processing"; note that nothing currently updates that status once transcoding finishes.
|
||||
// @Description Step 2 of the upload flow. Takes the storage key returned by POST /api/videos/presign — after the file has been PUT to the presigned URL — submits a MediaConvert transcoding job, and persists the video with its category links. The returned record has status "processing" — one of the states "processing", "ready" or "failed" — though nothing currently moves it on once transcoding finishes.
|
||||
// @Tags videos
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body completeRequest true "Video metadata plus the storage key from the presign step. title and key are required; categories must be values from GET /api/categories."
|
||||
// @Success 201 {object} videoResponse
|
||||
// @Failure 400 {object} problemDetails "Request body was not valid JSON"
|
||||
// @Failure 422 {object} problemDetails "title was empty, or key was missing or not a key issued by this API"
|
||||
// @Failure 500 {object} problemDetails "Transcoding job could not be queued, or the video record could not be saved"
|
||||
// @Param request body api.CompleteRequest true "Video metadata plus the storage key from the presign step. title and key are required; categories must be values from GET /api/categories."
|
||||
// @Success 201 {object} api.VideoResponse
|
||||
// @Failure 400 {object} api.ProblemDetails "Request body was not valid JSON"
|
||||
// @Failure 422 {object} api.ProblemDetails "title was empty, or key was missing or not a key issued by this API"
|
||||
// @Failure 500 {object} api.ProblemDetails "Transcoding job could not be queued, or the video record could not be saved"
|
||||
// @Router /api/videos [post]
|
||||
func CompleteVideoUpload(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxJSONBodySize)
|
||||
|
||||
var req completeRequest
|
||||
var req api.CompleteRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeProblem(w, http.StatusBadRequest, "Invalid request.",
|
||||
fmt.Sprintf("The request body could not be parsed as JSON (%s). Send an object containing at least 'title' and 'key', and keep the body under 1 MiB.", err))
|
||||
@@ -198,7 +161,7 @@ func CompleteVideoUpload(w http.ResponseWriter, r *http.Request) {
|
||||
FileName: strings.TrimSpace(req.FileName),
|
||||
StorageKey: key,
|
||||
MediaConvertJobID: jobID,
|
||||
Status: "processing",
|
||||
Status: services.VideoStatusProcessing,
|
||||
SizeBytes: 60,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -208,7 +171,7 @@ func CompleteVideoUpload(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusCreated, videoResponse{
|
||||
writeJSON(w, http.StatusCreated, api.CompleteResponse{
|
||||
ID: video.ID,
|
||||
Title: video.Title,
|
||||
Description: video.Description,
|
||||
@@ -217,7 +180,7 @@ func CompleteVideoUpload(w http.ResponseWriter, r *http.Request) {
|
||||
FileName: video.FileName,
|
||||
StorageKey: video.StorageKey,
|
||||
MediaConvertJobID: video.MediaConvertJobID,
|
||||
Status: video.Status,
|
||||
Status: video.Status.String(),
|
||||
SizeBytes: video.SizeBytes,
|
||||
CreatedAt: video.CreatedAt,
|
||||
UpdatedAt: video.UpdatedAt,
|
||||
|
||||
Reference in New Issue
Block a user