Skip to content

feat: Google Drive Integration#612

Draft
nora-weisser wants to merge 1 commit into
mainfrom
feature/drive
Draft

feat: Google Drive Integration#612
nora-weisser wants to merge 1 commit into
mainfrom
feature/drive

Conversation

@nora-weisser

Copy link
Copy Markdown
Contributor

Description

  • Describe your changes in detail.
  • Why is this change required?
  • What problem does it solve?
  • Add Notes if anything is left unclear or not completed

Related Issue

Please link to the issue here

Change Type

  • Bug Fix
  • New Feature
  • Code Refactor
  • Documentation
  • Test
  • Other

Screenshots

Pull request checklist

Please check if your PR fulfills the following requirements:

@vercel

vercel Bot commented Apr 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
platform-admin Ignored Ignored Apr 8, 2026 8:05pm

@nora-weisser nora-weisser changed the title feat: re-wrote code related to Google Drive integration feat: Google Drive Integration Apr 8, 2026
@sonarqubecloud

sonarqubecloud Bot commented Apr 8, 2026

Copy link
Copy Markdown

private static final List<String> SCOPES = Collections.singletonList(DriveScopes.DRIVE);
private static final String CREDS_FILE_PATH = "/credentials.json";
private static final String TOKENS_DIR_PATH = "tokens";
private static final String SERVICE_ACCOUNT_PATH = "/service-account.json";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on what I checked that is not the best solution because we need to upload the json account for each deployment in fly.io so it is not so simple to make this.

# Production Plan for Google Drive and Email on Fly.io

## Goal

This document describes the production-ready approach for running Google Drive
storage and transactional email in Fly.io without relying on local-only
workarounds such as:

- committing `credentials.json` into the image
- manually copying files into a running Fly machine
- using browser-based OAuth flows on the server
- relying on placeholder mail configuration in `application.yml`

It also explains the code changes needed in the backend and the operational
setup for both development and production environments.

## Short Answer

Google Drive and email are not the same situation in this codebase.

### Google Drive today

The current Google Drive implementation in
`src/main/java/com/wcc/platform/repository/googledrive/GoogleDriveFileStorageRepository.java`
expects:

- a classpath resource `/credentials.json`
- a writable local `tokens/` directory
- an interactive OAuth browser flow if no token exists

That is not production-safe on Fly.io.

## Target Production Architecture

### Google Drive

Replace desktop OAuth credentials with a Google service account and load the
credentials from a filesystem path or environment-provided secret at runtime.

Target outcome:

- no browser auth on the server
- no `tokens/` directory required
- no `credentials.json` bundled into the application JAR
- credentials provided through Fly secrets and written to a runtime path
- Drive folders shared explicitly with the service account identity

## Required Code Changes

### 1. Google Drive: stop loading credentials from the classpath

Current implementation:

- `CREDS_FILE_PATH = "/credentials.json"`
- `GoogleDriveFileStorageRepository.class.getResourceAsStream(CREDS_FILE_PATH)`

This should be replaced by configuration-driven loading.

#### Proposed new properties

Add properties such as:

```yaml
wcc:
  google-drive:
    auth-mode: service-account
    credentials-path: ${GOOGLE_DRIVE_CREDENTIALS_PATH:}
    application-name: WCC Backend

Optional future extension:

wcc:
  google-drive:
    credentials-json-base64: ${GOOGLE_DRIVE_CREDENTIALS_JSON_B64:}

Code changes

Modify:

  • src/main/java/com/wcc/platform/repository/googledrive/GoogleDriveFileStorageRepository.java

Add:

  • a configuration properties class, for example:
    src/main/java/com/wcc/platform/properties/GoogleDriveAuthProperties.java

Implementation direction:

  • remove the desktop OAuth AuthorizationCodeInstalledApp flow from production
  • load service account credentials from GOOGLE_DRIVE_CREDENTIALS_PATH
  • use Google credentials suitable for server-to-server access
  • build the Drive client from those credentials directly

The production code should fail fast at startup if:

  • storage.type=google
  • but credentials path is missing
  • or the credentials file cannot be parsed

2. Google Drive: support profile-based auth modes

Recommended split:

  • local: allow local filesystem storage by default
  • dev: optionally use service-account-based Google Drive if the developer has
    the credentials file locally
  • prod or flyio: only allow service-account mode

This avoids production inheriting local OAuth assumptions.

3. Add startup validation

Add validation so the app fails fast when production configuration is invalid.

Examples:

  • if storage.type=google and GOOGLE_DRIVE_CREDENTIALS_PATH is missing
  • if spring.mail.host is set but MAIL_USERNAME or MAIL_PASSWORD is blank
  • if required Drive folder IDs are missing

This can be implemented with:

  • @ConfigurationProperties
  • @Validated
  • bean initialization checks

Google Drive Service Account Setup

1. Create the service account

In Google Cloud Console:

  1. Open your Google Cloud project
  2. Go to IAM & Admin -> Service Accounts
  3. Create a new service account
  4. Give it a clear name such as wcc-backend-drive-prod
  5. Create a JSON key
  6. Download the JSON key securely

Do not commit this file to the repository.

2. Share the Drive folders with the service account

This is the critical step that replaces the desktop OAuth user flow.

For each required Google Drive folder:

  1. Open the folder in Google Drive
  2. Share it with the service account email address
  3. Grant the correct access level

Typical minimum access:

  • Editor for upload/update flows

Share:

  • the environment root folder
  • or each specific subfolder the app writes to

3. Record the folder IDs [DONE AND UPDATED TO PROD]

Use the same folder IDs already expected by the app:

  • main folder
  • resources folder
  • events folder
  • images folder
  • mentor pictures folder
  • mentor resources folder

These should remain configuration values, not code constants.

Fly.io Setup

Google Drive secrets

Store the service account JSON in Fly secrets.

Example:

fly secrets set GOOGLE_DRIVE_CREDENTIALS_JSON_B64="$(base64 < service-account.json | tr -d '\n')"

At startup:

  1. create a runtime directory such as /app/secrets
  2. decode the secret into /app/secrets/google-drive-service-account.json
  3. set:
[env]
SPRING_PROFILES_ACTIVE = "flyio"
GOOGLE_DRIVE_CREDENTIALS_PATH = "/app/secrets/google-drive-service-account.json"

This requires either:

  • an entrypoint script
  • or a launch command wrapper

Suggested Fly startup flow

Create a startup script that:

  1. creates /app/secrets
  2. decodes GOOGLE_DRIVE_CREDENTIALS_JSON_B64 into the credentials file
  3. starts the Java app

Example outline:

#!/bin/sh
set -eu

mkdir -p /app/secrets

if [ -n "${GOOGLE_DRIVE_CREDENTIALS_JSON_B64:-}" ]; then
  echo "$GOOGLE_DRIVE_CREDENTIALS_JSON_B64" | base64 -d > /app/secrets/google-drive-service-account.json
fi

exec java -jar app.jar

This is acceptable in production because:

  • the secret is injected by Fly
  • the file is created at runtime
  • the secret is not baked into the image

Recommended Development Setup

Local development for Google Drive

Best default:

  • use storage.type=local

This avoids forcing every developer to configure Google Drive credentials just
to run the app.

If a developer needs to test real Google Drive integration locally:

  • use the same service account approach as production
  • store the JSON file outside the repository
  • point GOOGLE_DRIVE_CREDENTIALS_PATH at that local file

Do not rely on desktop OAuth as the long-term supported path.

Testing Plan

Dev environment tests

Google Drive

  1. Start the app with storage.type=google
  2. Set GOOGLE_DRIVE_CREDENTIALS_PATH to a valid local service account file
  3. Upload a test file through the relevant API
  4. Confirm the file appears in the expected Drive folder
  5. Verify the returned link is accessible as expected

Negative tests:

  • missing credentials path
  • malformed JSON
  • valid credentials but folder not shared with service account

Production tests

Google Drive

  1. Deploy to Fly with service-account secret configured
  2. Verify app startup succeeds without OAuth prompts
  3. Call the upload endpoint with a small test file
  4. Confirm the file is stored in the correct production Drive folder
  5. Verify permissions and returned web link

Operational checks:

  • restart the Fly machine
  • redeploy the app
  • confirm uploads still work without any reauthorization

Recommended Implementation Sequence

  1. Refactor Google Drive auth to service-account-based loading from a configured path
  2. Remove production dependence on /credentials.json classpath resource
  3. Add startup validation for missing production secrets
  4. Add or update integration tests for Drive config loading where practical
  5. Add deployment documentation for Fly secrets and startup script [done]
  6. Optionally revisit a dedicated transactional provider if Gmail or Workspace
    limits become operationally restrictive

Final Recommendation

Google Drive

Real production fix:

  • migrate from desktop OAuth to a Google service account
  • load credentials from a Fly secret written to a runtime file
  • remove token-store and browser-auth assumptions from server code

@lauracabtay

Copy link
Copy Markdown
Contributor

Hey @dricazenck I didn't have permission to push on this branch, so created another PR: #679

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants