Skip to content

Commit 1b2acbc

Browse files
FEATURE (storages): Add Google Drive
1 parent 75acd16 commit 1b2acbc

33 files changed

+776
-35
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,5 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
147147
## 🤝 Contributing
148148

149149
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
150+
151+
To see diagrams it is recommended to install "Markdown Preview Enhanced" plugin

backend/go.mod

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,17 @@ require (
2525
gorm.io/gorm v1.26.1
2626
)
2727

28+
require (
29+
cloud.google.com/go/auth v0.16.2 // indirect
30+
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
31+
cloud.google.com/go/compute/metadata v0.7.0 // indirect
32+
github.com/google/s2a-go v0.1.9 // indirect
33+
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
34+
github.com/googleapis/gax-go/v2 v2.14.2 // indirect
35+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect
36+
google.golang.org/grpc v1.73.0 // indirect
37+
)
38+
2839
require (
2940
dario.cat/mergo v1.0.2 // indirect
3041
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
@@ -117,11 +128,13 @@ require (
117128
go.opentelemetry.io/otel/trace v1.36.0 // indirect
118129
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
119130
golang.org/x/arch v0.17.0 // indirect
120-
golang.org/x/net v0.40.0 // indirect
131+
golang.org/x/net v0.41.0 // indirect
132+
golang.org/x/oauth2 v0.30.0
121133
golang.org/x/sync v0.15.0 // indirect
122134
golang.org/x/sys v0.33.0 // indirect
123135
golang.org/x/text v0.26.0 // indirect
124136
golang.org/x/tools v0.33.0 // indirect
137+
google.golang.org/api v0.239.0
125138
google.golang.org/protobuf v1.36.6 // indirect
126139
gopkg.in/yaml.v2 v2.4.0 // indirect
127140
gopkg.in/yaml.v3 v3.0.1 // indirect

backend/go.sum

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
cloud.google.com/go/auth v0.16.2 h1:QvBAGFPLrDeoiNjyfVunhQ10HKNYuOwZ5noee0M5df4=
2+
cloud.google.com/go/auth v0.16.2/go.mod h1:sRBas2Y1fB1vZTdurouM0AzuYQBMZinrUYL8EufhtEA=
3+
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
4+
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
5+
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
6+
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
7+
cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
8+
cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
19
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
210
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
311
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
@@ -107,8 +115,14 @@ github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w
107115
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
108116
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
109117
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
118+
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
119+
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
110120
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
111121
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
122+
github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
123+
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
124+
github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=
125+
github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
112126
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
113127
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
114128
github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4=
@@ -296,6 +310,10 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
296310
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
297311
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
298312
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
313+
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
314+
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
315+
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
316+
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
299317
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
300318
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
301319
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -344,12 +362,18 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
344362
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
345363
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
346364
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
365+
google.golang.org/api v0.239.0 h1:2hZKUnFZEy81eugPs4e2XzIJ5SOwQg0G82bpXD65Puo=
366+
google.golang.org/api v0.239.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50=
347367
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1:SGktgSolFCo75dnHJF2yMvnns6jCmHFJ0vE4Vn2JKvQ=
348368
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw=
349369
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
350370
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
371+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE=
372+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
351373
google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
352374
google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
375+
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
376+
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
353377
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
354378
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
355379
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

backend/internal/config/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ type EnvVariables struct {
2828

2929
DataFolder string
3030
TempFolder string
31+
32+
TestGoogleDriveClientID string `env:"TEST_GOOGLE_DRIVE_CLIENT_ID" required:"true"`
33+
TestGoogleDriveClientSecret string `env:"TEST_GOOGLE_DRIVE_CLIENT_SECRET" required:"true"`
34+
TestGoogleDriveTokenJSON string `env:"TEST_GOOGLE_DRIVE_TOKEN_JSON" required:"true"`
3135
}
3236

3337
var (

backend/internal/features/backups/background_service.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ func (s *BackupBackgroundService) cleanOldBackups() error {
127127
err = storage.DeleteFile(backup.ID)
128128
if err != nil {
129129
s.logger.Error("Failed to delete backup file", "backupId", backup.ID, "error", err)
130-
continue
131130
}
132131

133132
if err := s.backupRepository.DeleteByID(backup.ID); err != nil {

backend/internal/features/backups/usecases/postgresql/create_backup_uc.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ func (uc *CreatePostgresqlBackupUsecase) Execute(
4444
)
4545

4646
pg := db.Postgresql
47+
48+
if pg == nil {
49+
return fmt.Errorf("postgresql database configuration is required for pg_dump backups")
50+
}
51+
4752
if pg.Database == nil || *pg.Database == "" {
4853
return fmt.Errorf("database name is required for pg_dump backups")
4954
}

backend/internal/features/storages/enums.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package storages
33
type StorageType string
44

55
const (
6-
StorageTypeLocal StorageType = "LOCAL"
7-
StorageTypeS3 StorageType = "S3"
6+
StorageTypeLocal StorageType = "LOCAL"
7+
StorageTypeS3 StorageType = "S3"
8+
StorageTypeGoogleDrive StorageType = "GOOGLE_DRIVE"
89
)

backend/internal/features/storages/model.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"io"
66
"log/slog"
7+
google_drive_storage "postgresus-backend/internal/features/storages/models/google_drive"
78
local_storage "postgresus-backend/internal/features/storages/models/local"
89
s3_storage "postgresus-backend/internal/features/storages/models/s3"
910

@@ -18,8 +19,9 @@ type Storage struct {
1819
LastSaveError *string `json:"lastSaveError" gorm:"column:last_save_error;type:text"`
1920

2021
// specific storage
21-
LocalStorage *local_storage.LocalStorage `json:"localStorage" gorm:"foreignKey:StorageID"`
22-
S3Storage *s3_storage.S3Storage `json:"s3Storage" gorm:"foreignKey:StorageID"`
22+
LocalStorage *local_storage.LocalStorage `json:"localStorage" gorm:"foreignKey:StorageID"`
23+
S3Storage *s3_storage.S3Storage `json:"s3Storage" gorm:"foreignKey:StorageID"`
24+
GoogleDriveStorage *google_drive_storage.GoogleDriveStorage `json:"googleDriveStorage" gorm:"foreignKey:StorageID"`
2325
}
2426

2527
func (s *Storage) SaveFile(logger *slog.Logger, fileID uuid.UUID, file io.Reader) error {
@@ -65,6 +67,8 @@ func (s *Storage) getSpecificStorage() StorageFileSaver {
6567
return s.LocalStorage
6668
case StorageTypeS3:
6769
return s.S3Storage
70+
case StorageTypeGoogleDrive:
71+
return s.GoogleDriveStorage
6872
default:
6973
panic("invalid storage type: " + string(s.Type))
7074
}

backend/internal/features/storages/model_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"io"
88
"os"
99
"path/filepath"
10+
"postgresus-backend/internal/config"
11+
google_drive_storage "postgresus-backend/internal/features/storages/models/google_drive"
1012
local_storage "postgresus-backend/internal/features/storages/models/local"
1113
s3_storage "postgresus-backend/internal/features/storages/models/s3"
1214
"postgresus-backend/internal/util/logger"
@@ -34,6 +36,8 @@ type S3Container struct {
3436
func Test_Storage_BasicOperations(t *testing.T) {
3537
ctx := context.Background()
3638

39+
validateEnvVariables(t)
40+
3741
// Setup S3 container
3842
s3Container, err := setupS3Container(ctx)
3943
require.NoError(t, err, "Failed to setup S3 container")
@@ -68,6 +72,15 @@ func Test_Storage_BasicOperations(t *testing.T) {
6872
S3Endpoint: "http://" + s3Container.endpoint, // Use http:// explicitly for testing
6973
},
7074
},
75+
{
76+
name: "GoogleDriveStorage",
77+
storage: &google_drive_storage.GoogleDriveStorage{
78+
StorageID: uuid.New(),
79+
ClientID: config.GetEnv().TestGoogleDriveClientID,
80+
ClientSecret: config.GetEnv().TestGoogleDriveClientSecret,
81+
TokenJSON: config.GetEnv().TestGoogleDriveTokenJSON,
82+
},
83+
},
7184
}
7285

7386
for _, tc := range testCases {
@@ -222,3 +235,10 @@ func setupS3Container(ctx context.Context) (*S3Container, error) {
222235
region: region,
223236
}, nil
224237
}
238+
239+
func validateEnvVariables(t *testing.T) {
240+
env := config.GetEnv()
241+
assert.NotEmpty(t, env.TestGoogleDriveClientID, "TEST_GOOGLE_DRIVE_CLIENT_ID is empty")
242+
assert.NotEmpty(t, env.TestGoogleDriveClientSecret, "TEST_GOOGLE_DRIVE_CLIENT_SECRET is empty")
243+
assert.NotEmpty(t, env.TestGoogleDriveTokenJSON, "TEST_GOOGLE_DRIVE_TOKEN_JSON is empty")
244+
}

0 commit comments

Comments
 (0)