diff --git a/.env.example b/.env.example index b522ac0..ba6b56c 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,5 @@ ENV=local -FIREBASE_AUTH_EMULATOR_HOST=firebase-emulator:9099 \ No newline at end of file +GCLOUD_PROJECT=fluentify-test +FIREBASE_AUTH_EMULATOR_HOST=firebase-emulator:9099 +FIREBASE_STORAGE_EMULATOR_HOST=firebase-emulator:9199 +DEFAULT_STORAGE_BUCKET=default-bucket \ No newline at end of file diff --git a/config/firebase.go b/config/firebase.go index 386fb3d..ab128d8 100644 --- a/config/firebase.go +++ b/config/firebase.go @@ -4,11 +4,18 @@ import ( "context" firebase "firebase.google.com/go/v4" "firebase.google.com/go/v4/auth" + "firebase.google.com/go/v4/storage" "log" + "os" ) func InitializeFirebaseApp() *firebase.App { - firebaseApp, err := firebase.NewApp(context.Background(), nil) + defaultBucketName := os.Getenv("DEFAULT_STORAGE_BUCKET") + config := &firebase.Config{ + StorageBucket: defaultBucketName + ".appspot.com", + } + + firebaseApp, err := firebase.NewApp(context.Background(), config) if err != nil { log.Fatalf("error initializing firebase app: %v\n", err) } @@ -24,3 +31,12 @@ func NewFirebaseAuthClient(app *firebase.App) *auth.Client { return authClient } + +func NewFirebaseStorageClient(app *firebase.App) *storage.Client { + storageClient, err := app.Storage(context.Background()) + if err != nil { + log.Fatalf("error getting firebase storage client: %v", err) + } + + return storageClient +} diff --git a/config/init.go b/config/init.go index 8574f8a..11270cf 100644 --- a/config/init.go +++ b/config/init.go @@ -8,17 +8,22 @@ import ( type Initialization struct { AuthMiddleware middleware.AuthMiddleware + UserService service.UserService - UserHandler handler.UserHandler + StorageService service.StorageService + + UserHandler handler.UserHandler } func NewInitialization( authMiddleware middleware.AuthMiddleware, + storageService service.StorageService, userService service.UserService, userHandler handler.UserHandler, ) *Initialization { return &Initialization{ AuthMiddleware: authMiddleware, + StorageService: storageService, UserService: userService, UserHandler: userHandler, } diff --git a/config/wire.go b/config/wire.go index fd75ee9..f2399a6 100644 --- a/config/wire.go +++ b/config/wire.go @@ -12,11 +12,22 @@ import ( var firebaseApp = wire.NewSet(InitializeFirebaseApp) var firebaseAuthClient = wire.NewSet(NewFirebaseAuthClient) +var firebaseStorageClient = wire.NewSet(NewFirebaseStorageClient) var authMiddlewareSet = wire.NewSet(middleware.AuthMiddlewareInit, wire.Bind(new(middleware.AuthMiddleware), new(*middleware.AuthMiddlewareImpl))) var userServiceSet = wire.NewSet(service.UserServiceInit, wire.Bind(new(service.UserService), new(*service.UserServiceImpl))) +var storageServiceSet = wire.NewSet(service.StorageServiceInit, wire.Bind(new(service.StorageService), new(*service.StorageServiceImpl))) var userHandlerSet = wire.NewSet(handler.UserHandlerInit, wire.Bind(new(handler.UserHandler), new(*handler.UserHandlerImpl))) func Init() *Initialization { - wire.Build(NewInitialization, firebaseApp, firebaseAuthClient, authMiddlewareSet, userServiceSet, userHandlerSet) + wire.Build( + NewInitialization, + firebaseApp, + firebaseAuthClient, + firebaseStorageClient, + authMiddlewareSet, + userServiceSet, + storageServiceSet, + userHandlerSet, + ) return nil } diff --git a/config/wire_gen.go b/config/wire_gen.go index de4f719..d042981 100644 --- a/config/wire_gen.go +++ b/config/wire_gen.go @@ -19,9 +19,11 @@ func Init() *Initialization { app := InitializeFirebaseApp() client := NewFirebaseAuthClient(app) authMiddlewareImpl := middleware.AuthMiddlewareInit(client) + storageClient := NewFirebaseStorageClient(app) + storageServiceImpl := service.StorageServiceInit(storageClient) userServiceImpl := service.UserServiceInit(client) userHandlerImpl := handler.UserHandlerInit(userServiceImpl) - initialization := NewInitialization(authMiddlewareImpl, userServiceImpl, userHandlerImpl) + initialization := NewInitialization(authMiddlewareImpl, storageServiceImpl, userServiceImpl, userHandlerImpl) return initialization } @@ -31,8 +33,12 @@ var firebaseApp = wire.NewSet(InitializeFirebaseApp) var firebaseAuthClient = wire.NewSet(NewFirebaseAuthClient) +var firebaseStorageClient = wire.NewSet(NewFirebaseStorageClient) + var authMiddlewareSet = wire.NewSet(middleware.AuthMiddlewareInit, wire.Bind(new(middleware.AuthMiddleware), new(*middleware.AuthMiddlewareImpl))) var userServiceSet = wire.NewSet(service.UserServiceInit, wire.Bind(new(service.UserService), new(*service.UserServiceImpl))) +var storageServiceSet = wire.NewSet(service.StorageServiceInit, wire.Bind(new(service.StorageService), new(*service.StorageServiceImpl))) + var userHandlerSet = wire.NewSet(handler.UserHandlerInit, wire.Bind(new(handler.UserHandler), new(*handler.UserHandlerImpl))) diff --git a/firebase-emulator/storage.rules b/firebase-emulator/storage.rules index f08744f..33f7f97 100644 --- a/firebase-emulator/storage.rules +++ b/firebase-emulator/storage.rules @@ -5,8 +5,12 @@ rules_version = '2'; // /databases/(default)/documents/users/$(request.auth.uid)).data.isAdmin; service firebase.storage { match /b/{bucket}/o { - match /{allPaths=**} { - allow read, write: if false; + match /public/{allPaths=**} { + allow read; + allow write: if false; + } + match /private/{userId}/{allPaths=**} { + allow read, write: if request.auth != null && request.auth.uid == userId; } } } diff --git a/go.mod b/go.mod index 71135cc..47f578d 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.21.4 require ( firebase.google.com/go/v4 v4.13.0 + github.com/google/uuid v1.6.0 github.com/google/wire v0.6.0 github.com/joho/godotenv v1.5.1 github.com/labstack/echo/v4 v4.11.4 @@ -29,7 +30,6 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/s2a-go v0.1.7 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.1 // indirect github.com/labstack/gommon v0.4.2 // indirect diff --git a/src/service/storage.service.go b/src/service/storage.service.go new file mode 100644 index 0000000..7411907 --- /dev/null +++ b/src/service/storage.service.go @@ -0,0 +1,83 @@ +package service + +import ( + "bytes" + "context" + "firebase.google.com/go/v4/storage" + "github.com/google/uuid" + "io" + "time" +) + +type StorageService interface { + UploadFile(file []byte, userId string) (string, error) + GetFile(filePath string) ([]byte, error) +} + +type StorageServiceImpl struct { + storageClient *storage.Client +} + +const ( + publicPath = "public/" + privatePath = "private/" + defaultTransferTimeout = 5 * time.Second +) + +func (service *StorageServiceImpl) UploadFile(file []byte, userId string) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), defaultTransferTimeout) + defer cancel() + + bucket, err := service.storageClient.DefaultBucket() + if err != nil { + return "", err + } + + fileName := uuid.New().String() + path := privatePath + userId + "/" + fileName + object := bucket.Object(path) + writer := object.NewWriter(ctx) + //Set the attribute + //writer.ObjectAttrs.Metadata = map[string]string{"firebaseStorageDownloadTokens": id.String()} + defer writer.Close() + + if _, err := io.Copy(writer, bytes.NewReader(file)); err != nil { + return "", err + } + // Set access control if needed + //if err := object.ACL().Set(context.Background(), storage.AllUsers, storage.RoleReader); err != nil { + // return "", err + //} + + return path, nil +} + +func (service *StorageServiceImpl) GetFile(filePath string) ([]byte, error) { + ctx, cancel := context.WithTimeout(context.Background(), defaultTransferTimeout) + defer cancel() + + bucket, err := service.storageClient.DefaultBucket() + if err != nil { + return nil, err + } + + object := bucket.Object(filePath) + reader, err := object.NewReader(ctx) + if err != nil { + return nil, err + } + defer reader.Close() + + buf := new(bytes.Buffer) + if _, err := buf.ReadFrom(reader); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func StorageServiceInit(storageClient *storage.Client) *StorageServiceImpl { + return &StorageServiceImpl{ + storageClient: storageClient, + } +}