FIX: Changed Content Type From Static mp4 To Input
Build, Push and Deploy CMS / build-push-deploy (push) Successful in 2m18s

This commit is contained in:
FahdShalhoub
2026-08-16 23:56:21 +03:00
parent f26ea98b52
commit 4b719f712d
5 changed files with 104 additions and 35 deletions
+7 -3
View File
@@ -95,7 +95,7 @@ const docTemplate = `{
},
"/api/videos/presign": {
"post": {
"description": "Step 1 of the upload flow. Returns a short-lived presigned S3 URL that the client PUTs the video file to directly, plus the storage key identifying it. Once the PUT succeeds, pass that same key to POST /api/videos to register the video and start transcoding. The file itself never passes through this API.",
"description": "Step 1 of the upload flow. Returns a short-lived presigned S3 URL that the client PUTs the video file to directly, plus the storage key identifying it. The submitted contentType is signed into that URL, so the PUT must carry an identical Content-Type header or S3 rejects it as a signature mismatch. Once the PUT succeeds, pass the key to POST /api/videos to register the video and start transcoding. The file itself never passes through this API.",
"consumes": [
"application/json"
],
@@ -108,7 +108,7 @@ const docTemplate = `{
"summary": "Create a presigned upload URL",
"parameters": [
{
"description": "Name and media type of the file to be uploaded. contentType must be a video/* type.",
"description": "Name and media type of the file to be uploaded. contentType must be exactly 'video/mp4' or 'video/quicktime'.",
"name": "request",
"in": "body",
"required": true,
@@ -131,7 +131,7 @@ const docTemplate = `{
}
},
"422": {
"description": "contentType was not a video/* media type",
"description": "contentType was not 'video/mp4' or 'video/quicktime'",
"schema": {
"$ref": "#/definitions/handlers.problemDetails"
}
@@ -231,6 +231,10 @@ const docTemplate = `{
"properties": {
"contentType": {
"type": "string",
"enum": [
"video/mp4",
"video/quicktime"
],
"example": "video/quicktime"
},
"fileName": {
+7 -3
View File
@@ -88,7 +88,7 @@
},
"/api/videos/presign": {
"post": {
"description": "Step 1 of the upload flow. Returns a short-lived presigned S3 URL that the client PUTs the video file to directly, plus the storage key identifying it. Once the PUT succeeds, pass that same key to POST /api/videos to register the video and start transcoding. The file itself never passes through this API.",
"description": "Step 1 of the upload flow. Returns a short-lived presigned S3 URL that the client PUTs the video file to directly, plus the storage key identifying it. The submitted contentType is signed into that URL, so the PUT must carry an identical Content-Type header or S3 rejects it as a signature mismatch. Once the PUT succeeds, pass the key to POST /api/videos to register the video and start transcoding. The file itself never passes through this API.",
"consumes": [
"application/json"
],
@@ -101,7 +101,7 @@
"summary": "Create a presigned upload URL",
"parameters": [
{
"description": "Name and media type of the file to be uploaded. contentType must be a video/* type.",
"description": "Name and media type of the file to be uploaded. contentType must be exactly 'video/mp4' or 'video/quicktime'.",
"name": "request",
"in": "body",
"required": true,
@@ -124,7 +124,7 @@
}
},
"422": {
"description": "contentType was not a video/* media type",
"description": "contentType was not 'video/mp4' or 'video/quicktime'",
"schema": {
"$ref": "#/definitions/handlers.problemDetails"
}
@@ -224,6 +224,10 @@
"properties": {
"contentType": {
"type": "string",
"enum": [
"video/mp4",
"video/quicktime"
],
"example": "video/quicktime"
},
"fileName": {
+9 -5
View File
@@ -44,6 +44,9 @@ definitions:
handlers.presignRequest:
properties:
contentType:
enum:
- video/mp4
- video/quicktime
example: video/quicktime
type: string
fileName:
@@ -190,12 +193,13 @@ paths:
- application/json
description: Step 1 of the upload flow. Returns a short-lived presigned S3 URL
that the client PUTs the video file to directly, plus the storage key identifying
it. Once the PUT succeeds, pass that same key to POST /api/videos to register
the video and start transcoding. The file itself never passes through this
API.
it. The submitted contentType is signed into that URL, so the PUT must carry
an identical Content-Type header or S3 rejects it as a signature mismatch.
Once the PUT succeeds, pass the key to POST /api/videos to register the video
and start transcoding. The file itself never passes through this API.
parameters:
- description: Name and media type of the file to be uploaded. contentType must
be a video/* type.
be exactly 'video/mp4' or 'video/quicktime'.
in: body
name: request
required: true
@@ -213,7 +217,7 @@ paths:
schema:
$ref: '#/definitions/handlers.problemDetails'
"422":
description: contentType was not a video/* media type
description: contentType was not 'video/mp4' or 'video/quicktime'
schema:
$ref: '#/definitions/handlers.problemDetails'
"500":
+18 -8
View File
@@ -47,9 +47,19 @@ func ListCategories(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, categoriesResponse{Categories: categories})
}
// allowedUploadContentTypes is the set of media types accepted for upload,
// keyed by the exact string a client must send. Matched verbatim rather than
// normalised: the value is signed into the presigned URL, so rewriting it
// here would leave the client PUTting a header that no longer matches the
// signature.
var allowedUploadContentTypes = map[string]struct{}{
"video/mp4": {}, // .mp4
"video/quicktime": {}, // .mov
}
type presignRequest struct {
FileName string `json:"fileName" example:"interview-cut.mov"`
ContentType string `json:"contentType" example:"video/quicktime"`
ContentType string `json:"contentType" enums:"video/mp4,video/quicktime" example:"video/quicktime"`
}
type presignResponse struct {
@@ -60,14 +70,14 @@ type presignResponse struct {
// PresignVideoUpload issues a presigned S3 PUT URL for a video upload.
//
// @Summary Create a presigned upload URL
// @Description Step 1 of the upload flow. Returns a short-lived presigned S3 URL that the client PUTs the video file to directly, plus the storage key identifying it. Once the PUT succeeds, pass that same key to POST /api/videos to register the video and start transcoding. The file itself never passes through this API.
// @Description Step 1 of the upload flow. Returns a short-lived presigned S3 URL that the client PUTs the video file to directly, plus the storage key identifying it. The submitted contentType is signed into that URL, so the PUT must carry an identical Content-Type header or S3 rejects it as a signature mismatch. Once the PUT succeeds, pass the key to POST /api/videos to register the video and start transcoding. The file itself never passes through this API.
// @Tags videos
// @Accept json
// @Produce json
// @Param request body presignRequest true "Name and media type of the file to be uploaded. contentType must be a video/* type."
// @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 a video/* media type"
// @Failure 422 {object} problemDetails "contentType was not 'video/mp4' or 'video/quicktime'"
// @Failure 500 {object} problemDetails "Upload URL could not be issued"
// @Router /api/videos/presign [post]
func PresignVideoUpload(w http.ResponseWriter, r *http.Request) {
@@ -80,9 +90,9 @@ func PresignVideoUpload(w http.ResponseWriter, r *http.Request) {
return
}
if !strings.HasPrefix(req.ContentType, "video/") {
writeProblem(w, http.StatusUnprocessableEntity, "Unsupported file type. Please upload a video.",
fmt.Sprintf("The 'contentType' field was %q, but only video/* media types can be uploaded. Set it to the file's own MIME type, for example 'video/mp4'.", req.ContentType))
if _, ok := allowedUploadContentTypes[req.ContentType]; !ok {
writeProblem(w, http.StatusUnprocessableEntity, "Unsupported file type. Please upload an MP4 or MOV video.",
fmt.Sprintf("The 'contentType' field was %q, but only 'video/mp4' (.mp4) and 'video/quicktime' (.mov) are accepted. Send one of those two values exactly — it is signed into the upload URL, so the PUT must use the identical header.", req.ContentType))
return
}
@@ -94,7 +104,7 @@ func PresignVideoUpload(w http.ResponseWriter, r *http.Request) {
}
key := videoUploadPrefix + "/" + filename
presignedURL, err := services.S3Client.GetPresignedURL(r.Context(), key, "video/mp4", time.Hour)
presignedURL, err := services.S3Client.GetPresignedURL(r.Context(), key, req.ContentType, time.Hour)
if err != nil {
writeProblem(w, http.StatusInternalServerError, "Could not prepare upload.",
"A presigned upload URL could not be issued for the storage bucket. This is a server-side fault; no upload slot was reserved, so retry the request.")