Skip to content

Commit 7f2f89e

Browse files
feat: S3 bucket POST/GET endpoints (#27)
Co-authored-by: Ben Petrillo <benpetrillo.bp@gmail.com>
1 parent 182caf3 commit 7f2f89e

File tree

11 files changed

+247
-4
lines changed

11 files changed

+247
-4
lines changed

backend/go.mod

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,26 @@ module github.com/GenerateNU/platemate
33
go 1.23.4
44

55
require (
6+
github.com/aws/aws-sdk-go-v2 v1.33.0
7+
github.com/aws/aws-sdk-go-v2/credentials v1.17.54
8+
github.com/aws/aws-sdk-go-v2/service/s3 v1.73.2
69
github.com/goccy/go-json v0.10.4
7-
github.com/gofiber/fiber/v2 v2.52.5
10+
github.com/gofiber/fiber/v2 v2.52.6
811
github.com/joho/godotenv v1.5.1
912
github.com/stretchr/testify v1.10.0
1013
go.mongodb.org/mongo-driver v1.17.1
1114
)
1215

1316
require (
17+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect
18+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.28 // indirect
19+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.28 // indirect
20+
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.28 // indirect
21+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
22+
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.2 // indirect
23+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.9 // indirect
24+
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.9 // indirect
25+
github.com/aws/smithy-go v1.22.1 // indirect
1426
github.com/kr/pretty v0.3.1 // indirect
1527
github.com/rogpeppe/go-internal v1.11.0 // indirect
1628
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
@@ -21,7 +33,7 @@ require (
2133
github.com/caarlos0/env/v11 v11.3.1
2234
github.com/davecgh/go-spew v1.1.1 // indirect
2335
github.com/golang/snappy v0.0.4 // indirect
24-
github.com/google/uuid v1.6.0 // indirect
36+
github.com/google/uuid v1.6.0
2537
github.com/klauspost/compress v1.17.11 // indirect
2638
github.com/mattn/go-colorable v0.1.13 // indirect
2739
github.com/mattn/go-isatty v0.0.20 // indirect

backend/go.sum

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,38 @@
11
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
22
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
3+
github.com/aws/aws-sdk-go-v2 v1.33.0 h1:Evgm4DI9imD81V0WwD+TN4DCwjUMdc94TrduMLbgZJs=
4+
github.com/aws/aws-sdk-go-v2 v1.33.0/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
5+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8=
6+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc=
7+
github.com/aws/aws-sdk-go-v2/credentials v1.17.54 h1:4UmqeOqJPvdvASZWrKlhzpRahAulBfyTJQUaYy4+hEI=
8+
github.com/aws/aws-sdk-go-v2/credentials v1.17.54/go.mod h1:RTdfo0P0hbbTxIhmQrOsC/PquBZGabEPnCaxxKRPSnI=
9+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.28 h1:igORFSiH3bfq4lxKFkTSYDhJEUCYo6C8VKiWJjYwQuQ=
10+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.28/go.mod h1:3So8EA/aAYm36L7XIvCVwLa0s5N0P7o2b1oqnx/2R4g=
11+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.28 h1:1mOW9zAUMhTSrMDssEHS/ajx8JcAj/IcftzcmNlmVLI=
12+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.28/go.mod h1:kGlXVIWDfvt2Ox5zEaNglmq0hXPHgQFNMix33Tw22jA=
13+
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.28 h1:7kpeALOUeThs2kEjlAxlADAVfxKmkYAedlpZ3kdoSJ4=
14+
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.28/go.mod h1:pyaOYEdp1MJWgtXLy6q80r3DhsVdOIOZNB9hdTcJIvI=
15+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y=
16+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE=
17+
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.2 h1:e6um6+DWYQP1XCa+E9YVtG/9v1qk5lyAOelMOVwSyO8=
18+
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.5.2/go.mod h1:dIW8puxSbYLSPv/ju0d9A3CpwXdtqvJtYKDMVmPLOWE=
19+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.9 h1:TQmKDyETFGiXVhZfQ/I0cCFziqqX58pi4tKJGYGFSz0=
20+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.9/go.mod h1:HVLPK2iHQBUx7HfZeOQSEu3v2ubZaAY2YPbAm5/WUyY=
21+
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.9 h1:2aInXbh02XsbO0KobPGMNXyv2QP73VDKsWPNJARj/+4=
22+
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.9/go.mod h1:dgXS1i+HgWnYkPXqNoPIPKeUsUUYHaUbThC90aDnNiE=
23+
github.com/aws/aws-sdk-go-v2/service/s3 v1.73.2 h1:F3h8VYq9ZLBXYurmwrT8W0SPhgCcU0q+0WZJfT1dFt0=
24+
github.com/aws/aws-sdk-go-v2/service/s3 v1.73.2/go.mod h1:jGJ/v7FIi7Ys9t54tmEFnrxuaWeJLpwNgKp2DXAVhOU=
25+
github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro=
26+
github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
327
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
428
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
529
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
630
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
731
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
832
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
933
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
10-
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
11-
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
34+
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
35+
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
1236
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
1337
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
1438
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=

backend/internal/config/aws.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package config
2+
3+
type AWS struct {
4+
BucketName string `env:"BUCKET_NAME"`
5+
Region string `env:"REGION"`
6+
AccessKeyID string `env:"ACCESS_KEY_ID"`
7+
SecretAccessKey string `env:"SECRET_ACCESS_KEY"`
8+
}

backend/internal/config/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import "github.com/caarlos0/env/v11"
55
type Config struct {
66
App `envPrefix:"APP_"`
77
Atlas `envPrefix:"ATLAS_"`
8+
AWS `envPrefix:"AWS_"`
89
}
910

1011
func Load() (Config, error) {

backend/internal/handlers/health/health.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ func (h *Handler) GetHealth(c *fiber.Ctx) error {
1616
}
1717

1818
func (h *Handler) Ping(c *fiber.Ctx) error {
19+
// access query params with c
1920
err := h.service.InsertDocumentToHealth()
2021
if err != nil {
2122
return err
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package s3bucket
2+
3+
import (
4+
"github.com/GenerateNU/platemate/internal/config"
5+
"github.com/aws/aws-sdk-go-v2/service/s3"
6+
"github.com/gofiber/fiber/v2"
7+
"log"
8+
)
9+
10+
type Service struct {
11+
Presigner *s3.PresignClient
12+
}
13+
14+
func newService(presigner *s3.PresignClient) *Service {
15+
return &Service{
16+
Presigner: presigner,
17+
}
18+
}
19+
20+
func Routes(app *fiber.App, presigner *s3.PresignClient) {
21+
cfg, err := config.Load()
22+
if err != nil {
23+
log.Fatalf("Failed to load configuration: %v", err)
24+
}
25+
26+
service := newService(presigner)
27+
handler := &Handler{
28+
service: service,
29+
config: cfg,
30+
}
31+
32+
assets := app.Group("/api/v1/assets")
33+
34+
assets.Get("/:key/url", handler.GetPresignedUrlHandler)
35+
assets.Post("/upload", handler.PostPresignedUrlHandler)
36+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package s3bucket
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"github.com/GenerateNU/platemate/internal/config"
7+
"github.com/gofiber/fiber/v2"
8+
)
9+
10+
type GetParams struct {
11+
Bucket string
12+
Key string
13+
}
14+
15+
type PostParams struct {
16+
Bucket string
17+
Filetype string
18+
}
19+
20+
type Handler struct {
21+
service *Service
22+
config config.Config
23+
}
24+
25+
func (h *Handler) GetPresignedUrlHandler(c *fiber.Ctx) error {
26+
key := c.Params("key")
27+
28+
// get the name of the bucket
29+
bucketName := h.config.AWS.BucketName
30+
if bucketName == "" {
31+
return fmt.Errorf("S3_BUCKET environment variable is not set")
32+
}
33+
34+
object := &GetParams{
35+
Bucket: bucketName,
36+
Key: key,
37+
}
38+
url, err := h.service.GetPresignedUrl(object)
39+
if err != nil {
40+
return err
41+
}
42+
jsonData, err := json.MarshalIndent(url, "", " ")
43+
if err != nil {
44+
return err
45+
}
46+
c.Set("Content-Type", "application/json")
47+
return c.Send(jsonData)
48+
}
49+
50+
func (h *Handler) PostPresignedUrlHandler(c *fiber.Ctx) error {
51+
fileType := c.Query("fileType")
52+
if fileType == "" {
53+
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
54+
"error": "fileType query parameter is required",
55+
})
56+
}
57+
58+
bucketName := h.config.AWS.BucketName
59+
if bucketName == "" {
60+
return fmt.Errorf("S3_BUCKET environment variable is not set")
61+
}
62+
63+
object := &PostParams{
64+
Bucket: bucketName,
65+
Filetype: fileType,
66+
}
67+
68+
urlAndKey, err := h.service.CreateUrlAndKey(object)
69+
if err != nil {
70+
return err
71+
}
72+
jsonData, err := json.MarshalIndent(urlAndKey, "", " ")
73+
if err != nil {
74+
return err
75+
}
76+
c.Set("Content-Type", "application/json")
77+
return c.Send(jsonData)
78+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package s3bucket
2+
3+
import (
4+
"context"
5+
"github.com/aws/aws-sdk-go-v2/aws"
6+
"github.com/aws/aws-sdk-go-v2/service/s3"
7+
"github.com/google/uuid"
8+
)
9+
10+
type DownloadUrl struct {
11+
URL string `json:"download_url"`
12+
}
13+
14+
type UploadUrl struct {
15+
URL string `json:"upload_url"`
16+
Key string `json:"key"`
17+
}
18+
19+
func (s *Service) GetPresignedUrl(inputs *GetParams) (*DownloadUrl, error) {
20+
// generate a presigned URL
21+
req, err := s.Presigner.PresignGetObject(context.Background(), &s3.GetObjectInput{
22+
Bucket: aws.String(inputs.Bucket),
23+
Key: aws.String(inputs.Key),
24+
})
25+
if err != nil {
26+
return nil, err
27+
}
28+
29+
return &DownloadUrl{
30+
URL: req.URL,
31+
}, nil
32+
}
33+
34+
func (s *Service) CreateUrlAndKey(inputs *PostParams) (*UploadUrl, error) {
35+
// generate uuid
36+
fileUUID := uuid.New().String()
37+
fileKey := fileUUID + "." + inputs.Filetype
38+
39+
req, err := s.Presigner.PresignPutObject(context.Background(), &s3.PutObjectInput{
40+
Bucket: aws.String(inputs.Bucket),
41+
Key: aws.String(fileKey),
42+
})
43+
if err != nil {
44+
return nil, err
45+
}
46+
urlAndKey := &UploadUrl{
47+
URL: req.URL,
48+
Key: fileKey,
49+
}
50+
return urlAndKey, nil
51+
}

backend/internal/server/server.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package server
22

33
import (
4+
"github.com/GenerateNU/platemate/internal/config"
45
"github.com/GenerateNU/platemate/internal/handlers/health"
56
"github.com/GenerateNU/platemate/internal/handlers/menu_items"
7+
"github.com/GenerateNU/platemate/internal/handlers/s3bucket"
68
"github.com/GenerateNU/platemate/internal/xerr"
9+
"github.com/aws/aws-sdk-go-v2/aws"
10+
"github.com/aws/aws-sdk-go-v2/credentials"
11+
"github.com/aws/aws-sdk-go-v2/service/s3"
712
gojson "github.com/goccy/go-json"
813
"github.com/gofiber/fiber/v2"
914
"github.com/gofiber/fiber/v2/middleware/compress"
@@ -13,12 +18,36 @@ import (
1318
"github.com/gofiber/fiber/v2/middleware/recover"
1419
"github.com/gofiber/fiber/v2/middleware/requestid"
1520
"go.mongodb.org/mongo-driver/mongo"
21+
"log"
1622
)
1723

1824
func New(collections map[string]*mongo.Collection) *fiber.App {
25+
cfg, err := config.Load()
26+
if err != nil {
27+
log.Fatalf("Failed to load configuration: %v", err)
28+
}
29+
1930
app := setupApp()
2031

32+
awsConfig := aws.Config{
33+
Region: cfg.AWS.Region,
34+
Credentials: credentials.NewStaticCredentialsProvider(
35+
cfg.AWS.AccessKeyID,
36+
cfg.AWS.SecretAccessKey,
37+
"",
38+
),
39+
}
40+
if err != nil {
41+
log.Fatalf("Failed to load AWS config: %v", err)
42+
}
43+
44+
// create a S3 presign client
45+
s3Client := s3.NewFromConfig(awsConfig)
46+
presigner := s3.NewPresignClient(s3Client)
47+
2148
health.Routes(app, collections)
49+
s3bucket.Routes(app, presigner)
50+
2251
menu_items.Routes(app, collections)
2352
return app
2453
}

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module platemate
2+
3+
go 1.23.4

0 commit comments

Comments
 (0)