Skip to main content
Organization administrators manage connections under Settings → Connections → Cloud storage. They can browse existing files, import a file into Assets, or select a folder for recurring imports. Assets already supports attaching imported documents to gigs through its existing picker.

Configure

Provision OAuth web applications in Google Cloud and the Dropbox App Console. Enable the Google Drive API. For Dropbox, select Full Dropbox access so customers can browse existing files. Register the exact web callback URL in both applications: https://<soundcheck-web-host>/settings/organization/cloud-storage This legacy callback URL redirects to Connections and preserves OAuth query parameters. Existing provider redirect URI registrations remain valid. Set these API environment variables using the deployment secret store: Both integrations require CREDENTIAL_ENCRYPTION_KEY, a base64 encoded 32-byte AES key. Connections remain unavailable without it. OAuth credentials stay on the backend. Refresh tokens are encrypted with AES-GCM and authenticated against their connection ID. Keep the encryption key stable across deployments and replicas. Google requests drive.readonly. Follow Google’s scope verification requirements before making a production OAuth app available broadly. Dropbox requests files.metadata.read and files.content.read, with offline access. Account owners grant consent through the provider’s own sign-in page. Apply the new Prisma migration and the existing events Terraform module to provision cloudStorageSync from api-events/scheduled-jobs.json. All three Encore runtime configurations include its topic and subscription. Configuring credentials without applying the scheduler infrastructure does not enable recurring sync.

Deployment wiring

The events Terraform module provisions and mounts these Secret Manager entries with the environment prefix (dev-, qa-, or prod-): The existing CredentialEncryptionKey secret supplies token encryption. Do not rotate it as part of this rollout. New credential secrets are initially seeded with PLACEHOLDER; Soundcheck treats those values as unconfigured. Callback URLs are already declared in each environment’s Terraform configuration:
  • Development: https://dev.app.soundchecklive.io/settings/organization/cloud-storage
  • QA: https://qa.app.soundchecklive.io/settings/organization/cloud-storage
  • Production: https://app.soundchecklive.io/settings/organization/cloud-storage
From the repository-approved deployment environment, review and apply the target events stack, populate the four credential secrets, then deploy the API and web changes through the existing branch pipelines. API startup applies the Prisma migration. Check the cloudStorageSync scheduler and subscription after deployment. In the target app, connect each provider as an organization administrator, import a file, select a folder, and verify a later source revision is imported automatically. Disconnect and verify the imported Asset remains.

Behavior

  • Each organization has one connection per provider, accessible to organization administrators.
  • Imports create or update OrganizationDocument records and use Soundcheck’s private app file storage (below) and existing authorized download routes. No public cloud sharing links are created.
  • Folder sync is one-way, from the provider into Assets. It checks the selected folder’s direct children. Select nested folders separately.
  • The scheduler runs every five minutes. Completed folders are due again after fifteen minutes; large folders take multiple bounded runs. Page cursors and per-file revision mappings survive restarts.
  • Individual files are limited to 50 MB. Google Docs, Sheets, Slides and Drawings export as PDFs. Unsupported native Google types and oversized files are reported as skipped; other files continue importing.
  • Source deletions never delete Soundcheck copies. Existing gig attachments remain snapshots; updating an Asset does not rewrite a previous gig attachment’s blob reference.
  • Reconnecting refreshes credentials while retaining folder selections and import identity. Connecting a different account may make previously selected folders inaccessible; remove those selections and choose folders from the new account.
  • Disconnecting removes credentials and folder sync configuration. Imported documents remain. Access can also be revoked in the provider’s connected-app settings.

Reliability

OAuth state is hashed, caller/organization/provider bound, expires after ten minutes and is consumed atomically. The authenticated callback rechecks administrator access. Sync rechecks the connecting administrator’s membership on each run. The database owns the folder selection, due time, cursor, source revision and imported document identity. The shared scheduled-job lock coordinates scans, and per-file locks coordinate manual imports with scheduled imports. File import commits lock and check the current connection and optional folder, preventing detached workers from committing after removal. A durable upload intent is stored before blob upload and removed atomically with the document commit. Failed uploads attempt immediate cleanup; the scheduler reclaims unreferenced intents older than one hour under the same file lock, including after a disconnect. Manual imports are attributed to the requesting administrator; scheduled imports retain the connecting administrator identity. Previous blob versions remain because existing gig attachments can reference them. A crash can replay an unfinished page. Committed revisions are skipped, and document upserts preserve identity. Provider errors retain the cursor and retry after fifteen minutes. Oversized or unsupported files are surfaced in the folder status. Expired credentials require reconnecting. The Cloud storage section in Connections displays connection setup, sync progress and errors.

App file storage

Imported files, gig attachments, contracts, receipts, documents and photos live in one private object store. The database keeps object names, not URLs, and api-events signs a short-lived URL when a client needs one.
  • Store. api-events uses the Cloud Storage bucket <project>-app-files when GCS_BUCKET is set, and the Azure Blob container gig-attachments otherwise. GCS_BUCKET says which configured store holds the files; it is not a rollout flag. Every caller goes through the storage port in api-events/src/clients/objectStorage.ts; gcsObjectStorage.ts and azureClient.ts are the two implementations. The Azure one is removed once the copy has finished and the rollback window has closed.
  • Bucket. infra-gcp/modules/storage creates it per environment: uniform bucket-level access, public access prevention, soft delete (30 days in prod, 7 in dev and qa), CORS for direct uploads from the web origins, and object access for the api-events runtime account only.
  • Signed URLs. Cloud Run has no key file, so api-events signs V4 URLs through IAM signBlob as its runtime account. Read URLs are aligned to 5-minute windows and cached per instance. Direct uploads are create-only (x-goog-if-generation-match: 0) and size-capped (x-goog-content-length-range). The upload response keeps its upload_url + headers shape, and web and mobile send the returned headers unchanged.
  • Downloads. The web download proxy (web/src/app/api/attachments/download/urlValidation.ts) fetches the signed URL server-side, so it only accepts HTTPS URLs on trusted storage hosts (storage.googleapis.com and *.blob.core.windows.net) and rejects private, loopback and link-local IP literals. The check runs on the hostname before DNS, so every allowed host must be one its provider fully controls.
  • Local and CI. npm run dev:infra starts fake-gcs-server on 127.0.0.1:4443; npm run dev:emulator and the web E2E runner set GCS_BUCKET=app-files and GCS_EMULATOR_HOST. In emulator mode api-events creates the bucket and signs with a throwaway key. npm run test:gcs-emulator (api-events) exercises the adapter against the emulator; the encore-e2e CI job runs it and the API E2E suite against fake-gcs-server.
  • Moving data. The Azure to GCS copy, verification and cutover order are in infra-gcp/modules/storage/README.md.

Validation

Run focused unit tests in api-events for cloudStorageClient.test.ts and cloudStorageOperations.test.ts. Web tests cover connection setup, missing configuration, file import and callbacks after an organization switch. For real database checks, run node scripts/cloud-storage/testDatabase.mjs from api-events with local test PostgreSQL on port 5434. Optionally supply CLOUD_STORAGE_TEST_DATABASE_URL for a dedicated test server. This runner creates and removes its own database and uses minimal parent-table fixtures. It verifies migration replay, concurrent OAuth consumption, concurrent import deduplication and disconnect/folder-removal fencing. It does not replace a full migrated application or live-provider smoke test.

Files SDK assessment

Files SDK’s Google Drive adapter lists files carrying its fsdkKey app property and excludes existing files created outside the SDK. Its Dropbox adapter filters folder entries from recursive listings. This integration uses the provider APIs directly to browse customers’ existing files and folder trees. Files SDK remains an option for a separate object-storage abstraction; it does not replace Soundcheck’s consent, organization authorization or durable sync state.