From 8bb971bcd0580a1c89a5998fe97b0b6c13e8eb28 Mon Sep 17 00:00:00 2001 From: David Delassus Date: Tue, 18 Feb 2025 23:22:30 +0100 Subject: [PATCH] :sparkles: add HTTP API endpoints for online restore --- api/main.go | 4 +++ api/restore_auth.go | 65 ++++++++++++++++++++++++++++++++++++++ api/restore_config.go | 73 +++++++++++++++++++++++++++++++++++++++++++ api/restore_logs.go | 67 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 209 insertions(+) create mode 100644 api/restore_auth.go create mode 100644 api/restore_config.go create mode 100644 api/restore_logs.go diff --git a/api/main.go b/api/main.go index edfae9e1..dd3c42ef 100644 --- a/api/main.go +++ b/api/main.go @@ -172,6 +172,10 @@ func NewHandler( }, ) r.Get("/api/v1/backup/config", BackupConfigUsecase(authStorage, configStorage)) + + r.Post("/api/v1/restore/auth", RestoreAuthUsecase(authStorage)) + r.Post("/api/v1/restore/logs", RestoreLogsUsecase(authStorage, logStorage)) + r.Post("/api/v1/restore/config", RestoreConfigUsecase(authStorage, configStorage)) }) return service diff --git a/api/restore_auth.go b/api/restore_auth.go new file mode 100644 index 00000000..134ea463 --- /dev/null +++ b/api/restore_auth.go @@ -0,0 +1,65 @@ +package api + +import ( + "context" + "log/slog" + + "mime/multipart" + + "github.com/swaggest/usecase" + "github.com/swaggest/usecase/status" + + "link-society.com/flowg/internal/models" + apiUtils "link-society.com/flowg/internal/utils/api" + + "link-society.com/flowg/internal/storage/auth" +) + +type RestoreAuthRequest struct { + Backup multipart.File `formData:"backup"` +} + +type RestoreAuthResponse struct { + Success bool `json:"success"` +} + +func RestoreAuthUsecase(authStorage *auth.Storage) usecase.Interactor { + u := usecase.NewInteractor( + apiUtils.RequireScopeApiDecorator( + authStorage, + models.SCOPE_WRITE_ACLS, + func( + ctx context.Context, + req RestoreAuthRequest, + resp *RestoreAuthResponse, + ) error { + defer req.Backup.Close() + + err := authStorage.Restore(ctx, req.Backup) + if err != nil { + slog.ErrorContext( + ctx, + "Failed to restore authentication database", + slog.String("channel", "api"), + slog.String("error", err.Error()), + ) + + return status.Wrap(err, status.Internal) + } + + resp.Success = true + + return nil + }, + ), + ) + + u.SetName("restore_auth") + u.SetTitle("Restore Authentication Database") + u.SetDescription("Upload a full snapshot of the authentication database.") + u.SetTags("backup") + + u.SetExpectedErrors(status.Unauthenticated, status.PermissionDenied, status.Internal) + + return u +} diff --git a/api/restore_config.go b/api/restore_config.go new file mode 100644 index 00000000..1a25d4ba --- /dev/null +++ b/api/restore_config.go @@ -0,0 +1,73 @@ +package api + +import ( + "context" + "log/slog" + + "mime/multipart" + + "github.com/swaggest/usecase" + "github.com/swaggest/usecase/status" + + "link-society.com/flowg/internal/models" + apiUtils "link-society.com/flowg/internal/utils/api" + + "link-society.com/flowg/internal/storage/auth" + "link-society.com/flowg/internal/storage/config" +) + +type RestoreConfigRequest struct { + Backup multipart.File `formData:"backup"` +} + +type RestoreConfigResponse struct { + Success bool `json:"success"` +} + +func RestoreConfigUsecase( + authStorage *auth.Storage, + configStorage *config.Storage, +) usecase.Interactor { + u := usecase.NewInteractor( + apiUtils.RequireScopesApiDecorator( + authStorage, + []models.Scope{ + models.SCOPE_WRITE_PIPELINES, + models.SCOPE_WRITE_TRANSFORMERS, + models.SCOPE_WRITE_ALERTS, + }, + func( + ctx context.Context, + req RestoreConfigRequest, + resp *RestoreConfigResponse, + ) error { + defer req.Backup.Close() + + err := configStorage.Restore(ctx, req.Backup) + if err != nil { + slog.ErrorContext( + ctx, + "Failed to restore configuration database", + slog.String("channel", "api"), + slog.String("error", err.Error()), + ) + + return status.Wrap(err, status.Internal) + } + + resp.Success = true + + return nil + }, + ), + ) + + u.SetName("restore_config") + u.SetTitle("Restore Configuration") + u.SetDescription("Upload a full snapshot of the configuration database.") + u.SetTags("backup") + + u.SetExpectedErrors(status.Unauthenticated, status.PermissionDenied, status.Internal) + + return u +} diff --git a/api/restore_logs.go b/api/restore_logs.go new file mode 100644 index 00000000..f32ad660 --- /dev/null +++ b/api/restore_logs.go @@ -0,0 +1,67 @@ +package api + +import ( + "context" + "log/slog" + + "mime/multipart" + + "github.com/swaggest/usecase" + "github.com/swaggest/usecase/status" + + "link-society.com/flowg/internal/models" + apiUtils "link-society.com/flowg/internal/utils/api" + + "link-society.com/flowg/internal/storage/auth" + "link-society.com/flowg/internal/storage/log" +) + +type RestoreLogsRequest struct { + Backup multipart.File `json:"backup"` +} + +type RestoreLogsResponse struct { + Success bool `json:"success"` +} + +func RestoreLogsUsecase( + authStorage *auth.Storage, + logStorage *log.Storage, +) usecase.Interactor { + u := usecase.NewInteractor( + apiUtils.RequireScopeApiDecorator( + authStorage, + models.SCOPE_WRITE_STREAMS, + func( + ctx context.Context, + req RestoreLogsRequest, + resp *RestoreLogsResponse, + ) error { + defer req.Backup.Close() + + err := logStorage.Restore(ctx, req.Backup) + if err != nil { + slog.ErrorContext( + ctx, + "Failed to restore logs database", + slog.String("channel", "api"), + slog.String("error", err.Error()), + ) + + return status.Wrap(err, status.Internal) + } + + return nil + }, + ), + ) + + u.SetName("restore_logs") + u.SetTitle("Restore Logs Database") + u.SetDescription("Upload a full snapshot of the logs database.") + u.SetTags("backup") + + u.SetExpectedErrors(status.Unauthenticated, status.PermissionDenied, status.Internal) + + return u +}