video upload to AWS

This commit is contained in:
2025-03-15 23:35:24 +02:00
parent bb2770e6bd
commit 1d0c56f493
4 changed files with 213 additions and 1 deletions

21
go.mod
View File

@@ -13,3 +13,24 @@ require (
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/mattn/go-sqlite3 v1.14.24 github.com/mattn/go-sqlite3 v1.14.24
) )
require (
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
github.com/aws/aws-sdk-go-v2/config v1.29.9 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.62 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 // indirect
github.com/aws/smithy-go v1.22.2 // indirect
)

36
go.sum
View File

@@ -1,3 +1,39 @@
github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM=
github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 h1:zAybnyUQXIZ5mok5Jqwlf58/TFE7uvd3IAsa1aF9cXs=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10/go.mod h1:qqvMj6gHLR/EXWZw4ZbqlPbQUyenf4h82UQUlKc+l14=
github.com/aws/aws-sdk-go-v2/config v1.29.9 h1:Kg+fAYNaJeGXp1vmjtidss8O2uXIsXwaRqsQJKXVr+0=
github.com/aws/aws-sdk-go-v2/config v1.29.9/go.mod h1:oU3jj2O53kgOU4TXq/yipt6ryiooYjlkqqVaZk7gY/U=
github.com/aws/aws-sdk-go-v2/credentials v1.17.62 h1:fvtQY3zFzYJ9CfixuAQ96IxDrBajbBWGqjNTCa79ocU=
github.com/aws/aws-sdk-go-v2/credentials v1.17.62/go.mod h1:ElETBxIQqcxej++Cs8GyPBbgMys5DgQPTwo7cUPDKt8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 h1:ZNTqv4nIdE/DiBfUUfXcLZ/Spcuz+RjeziUtNJackkM=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 h1:lguz0bmOoGzozP9XfRJR1QIayEYo+2vP/No3OfLF0pU=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 h1:moLQUoVq91LiqT1nbvzDukyqAlCv89ZmwaHw/ZFlFZg=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA=
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2 h1:jIiopHEV22b4yQP2q36Y0OmwLbsxNWdWwfZRR5QRRO4=
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2/go.mod h1:U5SNqwhXB3Xe6F47kXvWihPl/ilGaEDe8HD/50Z9wxc=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 h1:8JdC7Gr9NROg1Rusk25IcZeTO59zLxsKgE0gkh5O6h0=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.1/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 h1:KwuLovgQPcdjNMfFt9OhUd9a2OwcOKhxfvF4glTzLuA=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 h1:PZV5W8yk4OtH1JAuhV2PXwwO9v5G5Aoj+eMCn4T+1Kc=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.17/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
github.com/golang-jwt/jwt/v5 v5.0.0-rc.1 h1:tDQ1LjKga657layZ4JLsRdxgvupebc0xuPwRNuTfUgs= github.com/golang-jwt/jwt/v5 v5.0.0-rc.1 h1:tDQ1LjKga657layZ4JLsRdxgvupebc0xuPwRNuTfUgs=
github.com/golang-jwt/jwt/v5 v5.0.0-rc.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.0.0-rc.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=

View File

@@ -1,7 +1,149 @@
package main package main
import ( import (
"bytes"
"context"
"crypto/rand"
"encoding/base64"
"fmt"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/bootdotdev/learn-file-storage-s3-golang-starter/internal/auth"
"github.com/google/uuid"
"io"
"mime"
"net/http" "net/http"
"os"
) )
func (cfg *apiConfig) handlerUploadVideo(w http.ResponseWriter, r *http.Request) {} func (cfg *apiConfig) handlerUploadVideo(w http.ResponseWriter, r *http.Request) {
videoIDString := r.PathValue("videoID")
videoID, err := uuid.Parse(videoIDString)
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid ID", err)
return
}
token, err := auth.GetBearerToken(r.Header)
if err != nil {
respondWithError(w, http.StatusUnauthorized, "Couldn't find JWT", err)
return
}
userID, err := auth.ValidateJWT(token, cfg.jwtSecret)
if err != nil {
respondWithError(w, http.StatusUnauthorized, "Couldn't validate JWT", err)
return
}
fmt.Println("uploading video", videoID, "by user", userID)
var maxMemory int64
maxMemory = 10 << 30
err = r.ParseMultipartForm(maxMemory)
if err != nil {
respondWithError(w, http.StatusBadRequest, "Couldn't parse multipart form", err)
return
}
videoFile, header, err := r.FormFile("video")
defer videoFile.Close()
if err != nil {
respondWithError(w, http.StatusBadRequest, "Couldn't get file", err)
return
}
mediaType := header.Header.Get("Content-Type")
videoData, err := io.ReadAll(videoFile)
if err != nil {
respondWithError(w, http.StatusBadRequest, "Couldn't read video file", err)
return
}
video, err := cfg.db.GetVideo(videoID)
if err != nil {
respondWithError(w, http.StatusBadRequest, "Couldn't find video", err)
return
}
if video.UserID != userID {
respondWithError(w, http.StatusUnauthorized, "You are not authorized to upload this video", err)
return
}
var extension string
mimeType, _, err := mime.ParseMediaType(mediaType)
if err != nil {
respondWithError(w, http.StatusBadRequest, "Couldn't parse multipart form", err)
return
}
switch mimeType {
case "video/mp4":
extension = ".mp4"
break
default:
respondWithError(w, http.StatusBadRequest, "Couldn't recognize file type", err)
return
}
videoName := make([]byte, 32)
_, err = rand.Read(videoName)
if err != nil {
respondWithError(w, http.StatusBadRequest, "Couldn't generate random name", err)
return
}
videoPathName := base64.RawURLEncoding.EncodeToString(videoName)
tempFile, err := os.CreateTemp("", videoPathName)
if err != nil {
respondWithError(w, http.StatusBadRequest, "Couldn't create temp file", err)
return
}
defer os.Remove(videoPathName)
defer tempFile.Close()
_, err = io.Copy(tempFile, bytes.NewReader(videoData))
if err != nil {
respondWithError(w, http.StatusInternalServerError, "Couldn't create file", err)
return
}
tempFile.Seek(0, io.SeekStart)
fileName := videoPathName + extension
_, err = cfg.s3Client.PutObject(context.Background(), &s3.PutObjectInput{
Bucket: &cfg.s3Bucket,
Key: &fileName,
Body: tempFile,
ContentType: &mimeType,
})
if err != nil {
fmt.Printf("%s region, %s bucket", cfg.s3Region, cfg.s3Bucket)
respondWithError(w, http.StatusInternalServerError, "Couldn't upload video", err)
return
}
url := fmt.Sprintf("http://%s.s3.%s.amazonaws.com/%s", cfg.s3Bucket, cfg.s3Region, fileName)
video.VideoURL = &url
err = cfg.db.UpdateVideo(video)
if err != nil {
respondWithError(w, http.StatusBadRequest, "Couldn't update video", err)
return
}
video, err = cfg.db.GetVideo(videoID)
if err != nil {
respondWithError(w, http.StatusBadRequest, "Couldn't find video", err)
return
}
respondWithJSON(w, http.StatusOK, video)
}

13
main.go
View File

@@ -1,6 +1,9 @@
package main package main
import ( import (
"context"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
"log" "log"
"net/http" "net/http"
"os" "os"
@@ -17,6 +20,7 @@ type apiConfig struct {
platform string platform string
filepathRoot string filepathRoot string
assetsRoot string assetsRoot string
s3Client *s3.Client
s3Bucket string s3Bucket string
s3Region string s3Region string
s3CfDistribution string s3CfDistribution string
@@ -98,6 +102,15 @@ func main() {
log.Fatalf("Couldn't create assets directory: %v", err) log.Fatalf("Couldn't create assets directory: %v", err)
} }
awsCfg, err := config.LoadDefaultConfig(context.Background(), config.WithRegion(cfg.s3Region))
if err != nil {
log.Fatal(err)
}
client := s3.NewFromConfig(awsCfg)
cfg.s3Client = client
mux := http.NewServeMux() mux := http.NewServeMux()
appHandler := http.StripPrefix("/app", http.FileServer(http.Dir(filepathRoot))) appHandler := http.StripPrefix("/app", http.FileServer(http.Dir(filepathRoot)))
mux.Handle("/app/", appHandler) mux.Handle("/app/", appHandler)