video upload to AWS
This commit is contained in:
21
go.mod
21
go.mod
@@ -13,3 +13,24 @@ require (
|
||||
github.com/lib/pq v1.10.9
|
||||
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
36
go.sum
@@ -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/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
|
||||
@@ -1,7 +1,149 @@
|
||||
package main
|
||||
|
||||
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"
|
||||
"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
13
main.go
@@ -1,6 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -17,6 +20,7 @@ type apiConfig struct {
|
||||
platform string
|
||||
filepathRoot string
|
||||
assetsRoot string
|
||||
s3Client *s3.Client
|
||||
s3Bucket string
|
||||
s3Region string
|
||||
s3CfDistribution string
|
||||
@@ -98,6 +102,15 @@ func main() {
|
||||
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()
|
||||
appHandler := http.StripPrefix("/app", http.FileServer(http.Dir(filepathRoot)))
|
||||
mux.Handle("/app/", appHandler)
|
||||
|
||||
Reference in New Issue
Block a user