Note (2026-04-24): After this document was written, legal_entity was renamed to tenant and the old tenant was renamed to organization. Read references to these terms with the pre-rename meaning.

Google Drive File Store Integration

Summary

Replace the admin-configured file store with a user self-service Google Drive integration. Users connect their Google Drive via OAuth on the settings page, select a folder via Google Picker, and the app reads files from that folder for FP&A analysis. Each legal entity gets its own Google Drive connection.

Data Model

DB Migration

  1. Rename legal_entity_integrationlegal_entity_datev_integration

  2. New enum oauth_integration_type with value 'google_drive'

  3. New table legal_entity_oauth_integration:

Column Type Constraints
id UUID PK, DEFAULT gen_random_uuid()
legal_entity_id UUID NOT NULL, FK → legal_entity(id) ON DELETE CASCADE
integration_type oauth_integration_type NOT NULL
is_active BOOLEAN NOT NULL DEFAULT true
connected_by_user_id UUID NOT NULL, FK → orcha_user(id)
refresh_token_encrypted BYTEA
refresh_token_expires_at TIMESTAMPTZ
config JSONB NOT NULL DEFAULT '{}'
metadata JSONB NOT NULL DEFAULT '{}'
disconnect_reason TEXT
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
UNIQUE(legal_entity_id, integration_type)

Data Storage

Local Dev File Store

The resolve-file-store function checks for a dev override in config before querying the DB. Config example:

;; config.edn
{:dev-file-store {:protocol "file" :path "/data/acme/"}}

OAuth Flow

GCP Setup

Flow

User clicks "Connect Google Drive"
  → GET /settings/integrations/google-drive/authorize?legal_entity_id=...
  → Build Google OAuth URL with HMAC-signed state (legal_entity_id + user_id)
  → Redirect to Google consent screen
  → User grants drive.readonly
  → GET /settings/integrations/google-drive/callback?code=...&state=...
  → Exchange code for tokens
  → Encrypt refresh token with KMS
  → Store in legal_entity_oauth_integration (no folder yet)
  → Set fpna_data_source = {"protocol": "google-drive"}
  → Redirect to settings page
  → Settings page detects "connected but no folder" state
  → Renders Google Picker inline
  → User selects a folder
  → POST /settings/integrations/google-drive/select-folder {folder_id, folder_name}
  → Store folder in config JSONB
  → Section updates to show "Connected — folder: X"

Connection States

  1. Not connected — "Connect Google Drive" button
  2. Connected, no folder — Google Picker shown inline for folder selection
  3. Connected with folder — Shows folder name, "Change folder" and "Disconnect" actions

Disconnect

FileStore Implementation

New Namespace: com.getorcha.link.mcp.file-store.google-drive

Implements FileStore protocol for "google-drive" protocol:

Constructor receives a map with :folder-id and :access-token.

resolve-file-store

New function in com.getorcha.link.mcp.file-store. Receives the full MCP tool context map.

  1. Reads fpna_data_source from the legal entity
  2. If protocol is "google-drive": queries legal_entity_oauth_integration, decrypts refresh token, exchanges for a fresh access token via Google's token endpoint, passes access token + folder ID to make-file-store
  3. If protocol is "file": checks dev config override first, falls through to make-file-store
  4. MCP tools call resolve-file-store instead of make-file-store directly

Settings Page UI

Added to the Integrations area of /settings/integrations. Unlike email/DATEV (which use primary-le-id), Google Drive renders a card per legal entity since each LE gets its own connection.

Google Picker

Routes

GET  /settings/integrations/google-drive/authorize       — Initiate OAuth
GET  /settings/integrations/google-drive/callback         — OAuth callback
POST /settings/integrations/google-drive/select-folder    — Store selected folder
POST /settings/integrations/google-drive/disconnect       — Remove connection

Config & Infrastructure

SSM Parameters

Parameter Purpose
/orcha/{env}/google-drive/client-id OAuth client ID
/orcha/{env}/google-drive/client-secret OAuth client secret
/orcha/{env}/google-drive/api-key Google Picker API key

Infrastructure Changes

config.edn

Codebase Changes Summary

  1. Migration: Rename legal_entity_integrationlegal_entity_datev_integration, create legal_entity_oauth_integration table + enum
  2. DATEV references: Update all code referencing legal-entity-integration to legal-entity-datev-integration
  3. Google Drive OAuth: New routes + handlers in com.getorcha.app.http.settings.integrations (or new namespace)
  4. Google Drive FileStore: New com.getorcha.link.mcp.file-store.google-drive namespace
  5. resolve-file-store: New function in com.getorcha.link.mcp.file-store
  6. MCP tools: Update fpna/list_files and fpna/excel to use resolve-file-store
  7. Settings page UI: Google Drive connection section per legal entity
  8. SSM/config/infra: CDK stack, init_aws.clj, test fixtures
  9. Admin panel: Leave as-is (read-only view of file store config)