Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
emmdim committed Dec 11, 2024
1 parent 449d51e commit 191f79d
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 0 deletions.
5 changes: 5 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/vocdoni/saas-backend/account"
"github.com/vocdoni/saas-backend/db"
"github.com/vocdoni/saas-backend/notifications"
"github.com/vocdoni/saas-backend/objectstorage"
"github.com/vocdoni/saas-backend/stripe"
"github.com/vocdoni/saas-backend/subscriptions"
"go.vocdoni.io/dvote/apiclient"
Expand Down Expand Up @@ -40,6 +41,8 @@ type APIConfig struct {
StripeClient *stripe.StripeClient
// Subscriptions permissions manager
Subscriptions *subscriptions.Subscriptions
// Object storage
ObjectStorage *objectstorage.ObjectStorageClient
}

// API type represents the API HTTP server with JWT authentication capabilities.
Expand All @@ -57,6 +60,7 @@ type API struct {
transparentMode bool
stripe *stripe.StripeClient
subscriptions *subscriptions.Subscriptions
objectStorage *objectstorage.ObjectStorageClient
}

// New creates a new API HTTP server. It does not start the server. Use Start() for that.
Expand All @@ -77,6 +81,7 @@ func New(conf *APIConfig) *API {
transparentMode: conf.FullTransparentMode,
stripe: conf.StripeClient,
subscriptions: conf.Subscriptions,
objectStorage: conf.ObjectStorage,
}
}

Expand Down
59 changes: 59 additions & 0 deletions api/object_storage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package api

import (
"io"
"net/http"
)

func (a *API) handleFileUpload(w http.ResponseWriter, r *http.Request) {
// 32 MB is the default used by FormFile() function
if err := r.ParseMultipartForm(32 << 20); err != nil {
ErrGenericInternalServerError.With("could not parse form").Write(w)
return
}

// Get a reference to the fileHeaders.
// They are accessible only after ParseMultipartForm is called
files := r.MultipartForm.File["file"]
for _, fileHeader := range files {
// Open the file
file, err := fileHeader.Open()
if err != nil {
ErrGenericInternalServerError.Withf("cannot open file %s", err.Error()).Write(w)
break
}
defer file.Close()
buff := make([]byte, 512)
_, err = file.Read(buff)
if err != nil {
ErrGenericInternalServerError.Withf("cannot read file %s", err.Error()).Write(w)
break
}
// checking the content type
// so we don't allow files other than images
filetype := http.DetectContentType(buff)
if filetype != "image/jpeg" && filetype != "image/png" && filetype != "image/jpg" {
ErrGenericInternalServerError.With("The provided file format is not allowed. Please upload a JPEG,JPG or PNG image").Write(w)
break
}
_, err = file.Seek(0, io.SeekStart)
if err != nil {
ErrGenericInternalServerError.Withf("%s", err.Error()).Write(w)
break
}
fileBytes, err := io.ReadAll(file)
if err != nil {
ErrGenericInternalServerError.Withf("cannot read file %s", err.Error()).Write(w)
break
}
// upload the file using the object storage client
// and get the URL of the uploaded file
url, err := a.objectStorage.Upload(fileHeader.Filename, fileBytes)
if err != nil {
ErrGenericInternalServerError.Withf("cannot upload file %s", err.Error()).Write(w)
break
}
httpWriteJSON(w, map[string]string{"url": url})
}

}
16 changes: 16 additions & 0 deletions cmd/service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/vocdoni/saas-backend/db"
"github.com/vocdoni/saas-backend/notifications/mailtemplates"
"github.com/vocdoni/saas-backend/notifications/smtp"
"github.com/vocdoni/saas-backend/objectstorage"
"github.com/vocdoni/saas-backend/stripe"
"github.com/vocdoni/saas-backend/subscriptions"
"go.vocdoni.io/dvote/apiclient"
Expand All @@ -37,6 +38,11 @@ func main() {
flag.String("emailFromName", "Vocdoni", "Email service from name")
flag.String("stripeApiSecret", "", "Stripe API secret")
flag.String("stripeWebhookSecret", "", "Stripe Webhook secret")
flag.String("storageApiKey", "", "Object storage API key")
flag.String("storageApiSecret", "", "Object storage API secret")
flag.String("storageApiEndpoint", "", "Object storage API endpoint")
flag.String("storageApiRegion", "", "Object storage API region name")
flag.String("storageApiBucket", "", "Object storage API bucket name")
// parse flags
flag.Parse()
// initialize Viper
Expand Down Expand Up @@ -67,6 +73,12 @@ func main() {
// stripe vars
stripeApiSecret := viper.GetString("stripeApiSecret")
stripeWebhookSecret := viper.GetString("stripeWebhookSecret")
// object storage vars
storageApiKey := viper.GetString("storageApiKey")
storageApiSecret := viper.GetString("storageApiSecret")
storageApiEndpoint := viper.GetString("storageApiEndpoint")
storageApiRegion := viper.GetString("storageApiRegion")
storageApiBucket := viper.GetString("storageApiBucket")

log.Init("debug", "stdout", os.Stderr)
// create Stripe client and include it in the API configuration
Expand Down Expand Up @@ -144,6 +156,10 @@ func main() {
DB: database,
})
apiConf.Subscriptions = subscriptions
// initialize the s3 like object storage
if apiConf.ObjectStorage, err = objectstorage.New(storageApiKey, storageApiSecret, storageApiEndpoint, storageApiRegion, storageApiBucket); err != nil {
log.Fatalf("could not create the object storage: %v", err)
}
// create the local API server
api.New(apiConf).Start()
log.Infow("server started", "host", host, "port", port)
Expand Down
103 changes: 103 additions & 0 deletions objectstorage/objectstorage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package objectstorage

import (
"bytes"
"context"
"fmt"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
"github.com/aws/aws-sdk-go-v2/service/s3"
)

type ObjectStorageClient struct {
config aws.Config
client *s3.Client
defaultBucket string
}

func New(apiKey, apiSecret, apiEndpoint, apiRegion, apiBucket string) (*ObjectStorageClient, error) {
cfg, err := config.LoadDefaultConfig(context.Background(),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(apiKey, apiSecret, "")),
)
if err != nil {
return nil, fmt.Errorf("error seting up s3 session %v", err)
}

// s3Config := &aws.Config{
// Credentials: aws.NewStaticCredentials(apiKey, apiSecret, ""), // Specifies your credentials.
// Endpoint: aws.String(apiEndpoint), // Find your endpoint in the control panel, under Settings. Prepend "https://".
// S3ForcePathStyle: aws.Bool(false), // // Configures to use subdomain/virtual calling format. Depending on your version, alternatively use o.UsePathStyle = false
// Region: aws.String(apiRegion), // Must be "us-east-1" when creating new Spaces. Otherwise, use the region in your endpoint, such as "nyc3".
// }
client := s3.NewFromConfig(cfg)
return &ObjectStorageClient{
config: cfg,
client: client,
}, nil
}

// key is set in a string and can have a directory like notation (for example "folder-path/hello-world.txt")
func (osc *ObjectStorageClient) Upload(key string, payload []byte) (string, error) {
// The session the S3 Uploader will use
// Create an uploader with the session and default options
uploader := manager.NewUploader(osc.client)
// Upload the file to S3.
ctx, cancel := context.WithTimeout(context.Background(), time.Second*40)
defer cancel()
out, err := uploader.Upload(ctx, &s3.PutObjectInput{
Bucket: aws.String(osc.defaultBucket), // The path to the directory you want to upload the object to, starting with your Space name. Bucket: aws.String(myBucket),
Key: aws.String(key), // Object key, referenced whenever you want to access this file later. Key: aws.String(myString),
Body: bytes.NewReader(payload), // The object's contents.
// ACL: s3.ObjectCannedACLPrivate, // Defines Access-control List (ACL) permissions, such as private or public.
// Metadata: map[string]*string{ // Required. Defines metadata tags.
// "x-amz-meta-my-key": aws.String("your-value"),
// },
})
if err != nil {
return "", fmt.Errorf("failed to upload file, %v", err)
}
// object := s3.PutObjectInput{
// Bucket: aws.String(osc.config.apiBucket), // The path to the directory you want to upload the object to, starting with your Space name.
// Key: aws.String("folder-path/hello-world.txt"), // Object key, referenced whenever you want to access this file later.
// }

// _, err := osc.client.PutObject(&object)
// if err != nil {
// return err
// }
return out.Location, nil
}

// key is set in a string and can have a directory like notation (for example "folder-path/hello-world.txt")
func (osc *ObjectStorageClient) Get(key string) ([]byte, error) {
// The session the S3 Downloader will use
// sess := session.Must(session.NewSession(os.config))

// Create a downloader with the session and default options
downloader := manager.NewDownloader(osc.client)

// Create a file to write the S3 Object contents to.
// downloadFile, err := os.Create("downloaded-file")
// if err != nil {
// return nil, fmt.Errorf("failed to create file %v", err)
// }
// defer downloadFile.Close()

downloadFile := manager.NewWriteAtBuffer([]byte{})
ctx, cancel := context.WithTimeout(context.Background(), time.Second*40)
defer cancel()
// Write the contents of S3 Object to the file, returns the number of bytes
numBytes, err := downloader.Download(ctx, downloadFile, &s3.GetObjectInput{
Bucket: aws.String(osc.defaultBucket),
Key: aws.String(key),
})
if err != nil {
return nil, fmt.Errorf("failed to download file, %v", err)
}
fmt.Printf("file downloaded, %d bytes\n", numBytes)
return downloadFile.Bytes(), nil
}

0 comments on commit 191f79d

Please sign in to comment.