From 8607f2098ad7c0fbf5ef93be7777f4bbfea6a43b Mon Sep 17 00:00:00 2001 From: StarFleetCPTN Date: Sat, 15 Mar 2025 17:36:36 -0700 Subject: [PATCH] feat: Integrate Google Drive support and enhance configuration handling - Add Google Drive as a source and destination option in the configuration forms. - Implement Google Drive authentication flow and token management. - Update job and configuration handlers to support Google Drive-specific settings. - Enhance UI components to include Google Drive configuration templates. - Introduce new tests for Google Drive integration and ensure proper handling of authentication and configuration. - Update database migrations to accommodate new fields related to Google Drive configurations. --- components/admin_tools.templ | 3 +- components/config_form.templ | 21 +- components/configs.templ | 56 ++ components/job_form.templ | 53 +- components/jobs.templ | 2 +- components/profile.templ | 2 +- components/providers/common/common.templ | 2 + components/providers/destination/gdrive.templ | 178 +++++ components/providers/providers_test.go | 10 + components/providers/source/gdrive.templ | 129 ++++ components/users.templ | 2 +- go.mod | 4 + go.sum | 8 + internal/api/api.go | 34 +- internal/db/db.go | 628 +++++++++++++++++- internal/db/db_test.go | 609 ++++++++++++++++- internal/db/edge_cases_test.go | 6 +- internal/db/error_handling_test.go | 4 +- internal/db/initialization_test.go | 2 +- .../add_google_drive_authenticated.go | 21 + internal/db/migrations/migrations.go | 1 + internal/db/rclone_test.go | 114 ++++ internal/db/transaction_test.go | 4 +- internal/scheduler/mock_scheduler.go | 2 +- internal/scheduler/mock_scheduler_test.go | 4 +- internal/scheduler/scheduler.go | 14 +- internal/scheduler/scheduler_test.go | 57 +- .../scheduler/webhook_integration_test.go | 30 +- internal/scheduler/webhook_test.go | 54 +- internal/testutils/testutils.go | 2 +- internal/web/handlers/admin_tools_handlers.go | 43 +- .../web/handlers/admin_tools_handlers_test.go | 58 +- internal/web/handlers/api_handlers.go | 2 +- internal/web/handlers/api_handlers_test.go | 23 +- internal/web/handlers/auth_handlers.go | 89 +-- internal/web/handlers/auth_handlers_test.go | 26 +- internal/web/handlers/config_handlers.go | 52 +- internal/web/handlers/config_handlers_test.go | 2 +- .../web/handlers/dashboard_handlers_test.go | 2 +- .../web/handlers/file_metadata_handlers.go | 11 +- .../handlers/file_metadata_handlers_test.go | 2 +- internal/web/handlers/gdrive_handlers.go | 380 +++++++++++ internal/web/handlers/import_jobs_test.go | 8 +- internal/web/handlers/job_handlers.go | 34 + internal/web/handlers/job_handlers_test.go | 68 +- internal/web/handlers/oauth_handlers.go | 239 +++++++ internal/web/handlers/routes.go | 6 + internal/web/handlers/test_utils.go | 2 +- internal/web/handlers/user_handlers.go | 11 +- internal/web/handlers/user_handlers_test.go | 2 +- internal/web/handlers/webhook_test.go | 30 +- main.go | 4 +- 52 files changed, 2795 insertions(+), 355 deletions(-) create mode 100644 components/providers/destination/gdrive.templ create mode 100644 components/providers/source/gdrive.templ create mode 100644 internal/db/migrations/add_google_drive_authenticated.go create mode 100644 internal/web/handlers/gdrive_handlers.go create mode 100644 internal/web/handlers/oauth_handlers.go diff --git a/components/admin_tools.templ b/components/admin_tools.templ index 7a51949..dae3fee 100644 --- a/components/admin_tools.templ +++ b/components/admin_tools.templ @@ -85,7 +85,8 @@ script hideDialog(id string) { } script submitFormAndHideDialog(formId string, dialogId string) { - document.getElementById(formId).submit(); + // Use HTMX's API to trigger the request instead of bypassing it + htmx.trigger(document.getElementById(formId), 'submit'); document.getElementById(dialogId).classList.add("hidden"); } diff --git a/components/config_form.templ b/components/config_form.templ index ae4f575..d392ba8 100644 --- a/components/config_form.templ +++ b/components/config_form.templ @@ -75,6 +75,7 @@ func getInitialData(config *db.TransferConfig) string { skipProcessedFiles := true maxConcurrentTransfers := 4 rcloneFlags := "" + useBuiltinAuth := true // If editing an existing config, populate with those values if config != nil { @@ -96,7 +97,7 @@ func getInitialData(config *db.TransferConfig) string { sourceEndpoint = config.SourceEndpoint sourceShare = config.SourceShare sourceDomain = config.SourceDomain - sourcePassiveMode = config.SourcePassiveMode + sourcePassiveMode = config.GetSourcePassiveMode() sourceClientId = config.SourceClientID sourceClientSecret = config.SourceClientSecret sourceDriveId = config.SourceDriveID @@ -122,18 +123,21 @@ func getInitialData(config *db.TransferConfig) string { destEndpoint = config.DestEndpoint destShare = config.DestShare destDomain = config.DestDomain - destPassiveMode = config.DestPassiveMode + destPassiveMode = config.GetDestPassiveMode() destClientId = config.DestClientID destClientSecret = config.DestClientSecret destDriveId = config.DestDriveID destTeamDrive = config.DestTeamDrive archivePath = config.ArchivePath - archiveEnabled = config.ArchiveEnabled - deleteAfterTransfer = config.DeleteAfterTransfer + archiveEnabled = config.GetArchiveEnabled() + deleteAfterTransfer = config.GetDeleteAfterTransfer() skipProcessedFiles = config.GetSkipProcessedFiles() maxConcurrentTransfers = config.MaxConcurrentTransfers rcloneFlags = config.RcloneFlags + if destClientId != "" || destClientSecret != "" { + useBuiltinAuth = false + } } // Return the JSON-formatted string with all the data @@ -183,6 +187,7 @@ func getInitialData(config *db.TransferConfig) string { destClientSecret: '%s', destDriveId: '%s', destTeamDrive: '%s', + useBuiltinAuth: %v, archivePath: '%s', archiveEnabled: %v, @@ -198,6 +203,7 @@ func getInitialData(config *db.TransferConfig) string { destinationType, destinationPath, destHost, destPort, destUser, destPassword, destKeyFile, destAuthType, destBucket, destRegion, destAccessKey, destSecretKey, destEndpoint, destShare, destDomain, destPassiveMode, destClientId, destClientSecret, destDriveId, destTeamDrive, + useBuiltinAuth, archivePath, archiveEnabled, deleteAfterTransfer, skipProcessedFiles, maxConcurrentTransfers, rcloneFlags) } @@ -277,6 +283,9 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) { @source.NextCloudSourceForm() + @common.FilePatternFields() @@ -316,6 +325,10 @@ templ ConfigForm(ctx context.Context, data ConfigFormData) { + + diff --git a/components/configs.templ b/components/configs.templ index f685849..a15ded9 100644 --- a/components/configs.templ +++ b/components/configs.templ @@ -68,6 +68,9 @@ script triggerConfigDelete(dialogId string, configID uint, configName string) { type ConfigsData struct { Configs []db.TransferConfig + Error string + ErrorDetails string + Status string } templ Configs(ctx context.Context, data ConfigsData) { @@ -106,6 +109,27 @@ templ Configs(ctx context.Context, data ConfigsData) { console.log("Notyf initialized:", window.notyf); } + // Show status messages based on URL parameters + document.addEventListener('DOMContentLoaded', function() { + // Check for error message + const urlParams = new URLSearchParams(window.location.search); + const errorMsg = urlParams.get('error'); + const errorDetails = urlParams.get('details'); + const status = urlParams.get('status'); + + if (errorMsg) { + let message = errorMsg; + if (errorDetails) { + message += ": " + errorDetails; + } + window.notyf.error(message); + } + + if (status === 'gdrive_auth_success') { + window.notyf.success("Google Drive authentication completed successfully"); + } + }); + // Track all HTMX events for debugging document.addEventListener('htmx:beforeRequest', function(event) { @@ -244,8 +268,32 @@ templ Configs(ctx context.Context, data ConfigsData) {

{ config.Name }

+ + + if (config.DestinationType == "gdrive" || config.SourceType == "gdrive" || config.DestinationType == "drive") && !config.GetGoogleDriveAuthenticated() { + + + Authentication Required + + } + + + if (config.DestinationType == "gdrive" || config.SourceType == "gdrive" || config.DestinationType == "drive") && config.GetGoogleDriveAuthenticated() { + + + Authenticated + + }
+ + if (config.DestinationType == "gdrive" || config.SourceType == "gdrive") && !config.GetGoogleDriveAuthenticated() { + + + Authenticate + + } + Edit @@ -304,6 +352,14 @@ templ Configs(ctx context.Context, data ConfigsData) { Configurations define how files are transferred between systems

+ + +
+

+ + Google Drive configurations require authentication. Click the "Authenticate" button to complete setup. +

+
} diff --git a/components/job_form.templ b/components/job_form.templ index 92ff20e..9e09ee3 100644 --- a/components/job_form.templ +++ b/components/job_form.templ @@ -191,16 +191,17 @@ templ JobForm(ctx context.Context, data JobFormData) {
+ if data.Job.GetEnabled() { + checked + } + class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-secondary-300 dark:border-secondary-700 rounded" />
-

- - Disabled jobs will not run automatically. +

+ Jobs that are not enabled will not run automatically on schedule.

@@ -215,12 +216,15 @@ templ JobForm(ctx context.Context, data JobFormData) {
+ if data.Job.GetWebhookEnabled() { + checked + } + class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-secondary-300 dark:border-secondary-700 rounded" />
@@ -293,7 +297,9 @@ templ JobForm(ctx context.Context, data JobFormData) { id="notify_on_success" name="notify_on_success" value="true" - checked + if data.Job.GetNotifyOnSuccess() { + checked + } class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-secondary-300 dark:border-secondary-700 rounded"/>