From c99177e0dafd093564c8a8aa712fd1e618bb2efa Mon Sep 17 00:00:00 2001 From: tscrond Date: Sat, 21 Jun 2025 20:02:06 +0200 Subject: [PATCH] add deleting full user data option --- README.md | 8 ++-- internal/api/data_delete.go | 49 +++++++++++++++++++---- internal/cloud_storage/gcs/gcs_handler.go | 49 +++++++++++++++++++++++ internal/cloud_storage/types/object.go | 1 + 4 files changed, 96 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index bc003bf..59cc8fd 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,11 @@ It handles file uploads to a Google Cloud Storage (GCS) bucket. ## Features -- Receives files from the frontend and uploads them to a specified GCS bucket. -- Built with Go for performance and reliability. -- Dockerized for easy deployment. +- Uploading files to the cloud storage (GCS) +- Displaying metadata about stored and received objects +- Sharing files in the store via email or private/public links +- Previewing supported file types (image, video, audio, pdf) +- Adding notes attached to stored files ## Prerequisites diff --git a/internal/api/data_delete.go b/internal/api/data_delete.go index bb930b9..fcb5e32 100644 --- a/internal/api/data_delete.go +++ b/internal/api/data_delete.go @@ -2,6 +2,7 @@ package api import ( "database/sql" + "encoding/json" "fmt" "log" "net/http" @@ -70,6 +71,38 @@ func (s *APIServer) deleteAccount(w http.ResponseWriter, r *http.Request) { pkg.WriteJSONResponse(w, http.StatusForbidden, "authorization_failed", nil) return } + bucketName := pkg.GetUserBucketName(s.bucketHandler.GetBucketBaseName(), authUserData.Id) + + fullResponse := map[string]any{} + fullResponse["bucket"] = map[string]any{ + "name": bucketName, + "deleted": false, + } + + type DeleteAccountRequest struct { + DeleteUserData bool `json:"delete_user_data"` + } + + var req DeleteAccountRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + pkg.WriteJSONResponse(w, http.StatusBadRequest, "bad_request", "") + return + } + + if req.DeleteUserData { + if err := s.bucketHandler.DeleteBucket(ctx, bucketName); err != nil { + log.Printf("failed to delete bucket %s err: %s\n", bucketName, err) + fullResponse["bucket"] = map[string]any{ + "name": bucketName, + "deleted": false, + } + } + + fullResponse["bucket"] = map[string]any{ + "name": bucketName, + "deleted": true, + } + } deletedAccount, err := s.repository.Queries.DeleteAccount(ctx, authUserData.Id) if err != nil { @@ -77,13 +110,13 @@ func (s *APIServer) deleteAccount(w http.ResponseWriter, r *http.Request) { pkg.WriteJSONResponse(w, http.StatusInternalServerError, "authorization_failed", nil) return } - - pkg.WriteJSONResponse(w, http.StatusOK, "success", map[string]any{ - "account_deleted": map[string]any{ - "id": deletedAccount.GoogleID, - "email": deletedAccount.UserEmail, - "user_name": deletedAccount.UserName.String, - }, - }) + + fullResponse["account_deleted"] = map[string]any{ + "id": deletedAccount.GoogleID, + "email": deletedAccount.UserEmail, + "user_name": deletedAccount.UserName.String, + } + + pkg.WriteJSONResponse(w, http.StatusOK, "success", fullResponse) } diff --git a/internal/cloud_storage/gcs/gcs_handler.go b/internal/cloud_storage/gcs/gcs_handler.go index f76a5a4..7ee5e53 100644 --- a/internal/cloud_storage/gcs/gcs_handler.go +++ b/internal/cloud_storage/gcs/gcs_handler.go @@ -407,3 +407,52 @@ func (b *GCSBucketHandler) DeleteObjectFromBucket(ctx context.Context, object, b log.Printf("object deleted successfully: (%s,%s)", o.BucketName(), o.ObjectName()) return nil } + +func (b *GCSBucketHandler) getAllObjectNames(ctx context.Context, bucket string) ([]string, error) { + ctx, cancel := context.WithTimeout(ctx, time.Second*10) + defer cancel() + + objectNames := []string{} + + it := b.Client.Bucket(bucket).Objects(ctx, nil) + for { + attrs, err := it.Next() + + if err == iterator.Done { + break + } + if err != nil { + return objectNames, fmt.Errorf("Bucket(%q).Objects: %w", bucket, err) + } + objectNames = append(objectNames, attrs.Name) + } + + return objectNames, nil +} + +func (b *GCSBucketHandler) DeleteBucket(ctx context.Context, bucket string) error { + ctx, cancel := context.WithTimeout(ctx, time.Minute*1) + defer cancel() + + objectsInBucket, err := b.getAllObjectNames(ctx, bucket) + if err != nil { + log.Println("failed_fetching_bucket_info, err: ", err) + } + + gcsBucket := b.Client.Bucket(bucket) + for _, o := range objectsInBucket { + object := gcsBucket.Object(o) + if err := object.Delete(ctx); err != nil { + log.Printf("failed deleting object %s, err: %s\n", o, err) + } + log.Printf("deleted object %s", o) + } + + if err := gcsBucket.Delete(ctx); err != nil { + log.Println(err) + return fmt.Errorf("failed_deleting_bucket") + } + + log.Printf("deleted bucket: %s\n", bucket) + return nil +} diff --git a/internal/cloud_storage/types/object.go b/internal/cloud_storage/types/object.go index 1a6766a..95553fe 100644 --- a/internal/cloud_storage/types/object.go +++ b/internal/cloud_storage/types/object.go @@ -16,5 +16,6 @@ type ObjectStorage interface { GetBucketBaseName() string GenerateSignedURL(ctx context.Context, bucket, object string, expiresAt time.Time) (string, error) DeleteObjectFromBucket(ctx context.Context, object, bucket string) error + DeleteBucket(ctx context.Context, bucket string) error Close() error }