Skip to content

Commit ee901d4

Browse files
ben-fornefeldgithub-actions[bot]cursoragent
authored
feat(dashboard-api): supabase auth users sync background runner (#2247)
* feat: supabase auth users sync background runner in dashboard api * chore: auto-commit generated changes * feat(db): enhance auth user sync triggers to retain direct operations while enqueuing for processing * feat(sync): implement user sync queue with enhanced error handling and recovery mechanisms - Updated the sync runner to use `RunWithRestart` for improved error recovery. - Introduced a new `UserSyncQueue` model to manage user synchronization tasks. - Added SQL migration for creating the `user_sync_queue` table with necessary triggers. - Implemented tests for the processor and supervisor to ensure robust handling of retries and panics. - Refactored existing queries to target the new `public.user_sync_queue` table. * feat(sync): add supabase auth user sync configuration and secrets management - Introduced `supabase_auth_user_sync_enabled` variable to control user synchronization. - Updated Nomad job configuration to include the new sync setting. - Added Google Secret Manager resources for managing the sync configuration securely. - Enhanced the dashboard API to utilize the new sync configuration in processing logic. - Refactored related components to improve error handling and logging for the sync process. * chore: remove smoke test * add: e2e runner test * chore: change dashboard-api env variable management * refactor(sync): update user sync logic to utilize new database structure - Replaced the previous `Store` implementation with a new structure that integrates both authentication and main database queries. - Updated the `Runner` and `NewRunner` functions to accommodate the new database client structure. - Removed obsolete SQL queries and migration files related to the `user_sync_queue` table. - Enhanced the test suite to reflect changes in the runner's initialization and database interactions. * fix: lint * test: apply database migrations in end-to-end test setup - Updated the `TestSupabaseAuthUserSyncRunner_EndToEnd` to apply necessary database migrations before running tests. - Refactored the `SetupDatabase` function to include a new method `ApplyMigrations` for better migration management. * feat(sync): enhance user sync processing and acknowledgment - Introduced a new process outcome `ready_to_ack` to streamline acknowledgment handling. - Refactored the `process` method to prepare for batch acknowledgment of processed items. - Added a new `AckBatch` method in the store to handle multiple acknowledgments efficiently. - Updated the `Runner` to process items in batches and finalize acknowledgments accordingly. - Removed obsolete SQL query for single item acknowledgment as part of the refactor. - Enhanced tests to cover new deletion logic and acknowledgment scenarios. * refactor(gcp): remove auth_db_connection_string resources and update references - Deleted the `auth_db_connection_string` secret and its version from the GCP configuration. - Updated references in `main.tf` and `nomad/main.tf` to use the `postgres_connection_string` instead. - Removed the corresponding variable declaration from `variables.tf` to clean up unused configurations. * chore(dashboard-api): refactor environment variable management - Updated the dashboard API module to separate base and extra environment variables. - Introduced a precondition to prevent conflicts with reserved keys in extra environment variables. - Modified the HCL job configuration to iterate over environment variables dynamically. - Adjusted variable declarations to reflect the new structure for extra environment variables. * refactor: use river for queue worker * chore: auto-commit generated changes * chore: auto-commit generated changes * chore: update pgx dependency to v5.9.1 and golang.org/x/mod to v0.34.0 across multiple packages * chore: fix lint * chore: auto-commit generated changes * improve: retrying for river jobs * refactor: enhance auth user sync worker and streamline environment variable handling - Updated the AuthUserSyncWorker to utilize OpenTelemetry metrics for better monitoring. - Refactored the Makefile to improve environment variable exportation and streamline build/run commands. - Removed outdated SQL query files related to user sync queue operations to clean up the codebase. - Adjusted the database migration script to drop the auth_custom schema if it exists, ensuring a cleaner migration process. * chore: rename config and fix test * improve: config * chore: auto-commit generated changes * fix: correct env var name in template to match Go config Use AUTH_USER_SYNC_BACKGROUND_WORKER_ENABLED instead of SUPABASE_AUTH_USER_SYNC_ENABLED to match the actual config field in packages/dashboard-api/internal/cfg/model.go Co-authored-by: Ben Fornefeld <ben-fornefeld@users.noreply.github.com> * fix: update permissions for trigger_user on river_job and sequences in auth_custom schema - Changed REVOKE statement to specify INSERT permission on river_job. - Added REVOKE for USAGE and SELECT on all sequences in the auth_custom schema for trigger_user. * refactor: update environment variable handling and improve auth user sync worker - Renamed environment variable from AUTH_USER_SYNC_BACKGROUND_WORKER_ENABLED to ENABLE_AUTH_USER_SYNC_BACKGROUND_WORKER for clarity and consistency. - Streamlined the AuthUserSyncWorker to enhance monitoring and error handling. - Adjusted the handling of environment variables in the Nomad job configuration to allow for better integration with module defaults. - Updated test cases to reflect changes in the database client naming convention from AuthDb to AuthDB for consistency across the codebase. * chore: auto-commit generated changes * fix: lint * chore: auto-commit generated changes * chore: auto-commit generated changes * refactor(dashboard-api): align auth user sync worker with review feedback * chore: auto-commit generated changes * refactor(db): split supabase auth sync source client * chore(db): address follow-up nits * fix(dashboard-api): stop river worker gracefully on shutdown * refactor(dashboard-api): simplify service lifecycle orchestration * chore: auto-commit generated changes * refactor(db): drop supabase replica client plumbing * fix: use write db for supabase db get user read * chore: fix lint * fix(dashboard-api): lazy-init supabase worker client * chore(dashboard-api): reduce supabase worker pool size * test(dashboard-api): tighten auth user sync coverage Replace the broad backlog case with smaller tests that cover stale upsert cleanup and same-email trigger behavior. This keeps the worker coverage focused on the branch's critical correctness signals without extra soak-style runtime. * chore: auto-commit generated changes * feat(dashboard-api): add supabase db config override Allow dashboard-api to use a dedicated SUPABASE_DB_CONNECTION_STRING while keeping the existing fallback to POSTGRES_CONNECTION_STRING. Thread the new setting through Terraform so deployments can configure the worker without reusing the auth DB connection string. * refactor(dashboard-api): narrow infra sync runner changes Keep dashboard-api specific env wiring explicit, restore NODE_ID and PORT in the Nomad job spec, and remove generic dashboard-api env passthrough. Also trim unrelated lint churn from the branch and scope the remaining test/migration helpers to the auth user sync worker flow. * chore: auto-commit generated changes * chore: auto-commit generated changes * refactor(supabase): drop unused trigger role setup Remove the copied trigger_user scaffolding from the Supabase River migrations because these SECURITY DEFINER functions never use that role. Keep the test bootstrap aligned with the simplified migration flow. * refactor(dashboard-api): use shared not-found helper Replace direct pgx no-rows checks in dashboard-api handlers with dberrors.IsNotFoundError so the package uses the shared database error handling consistently. * chore: remove env vars artifact * refactor(dashboard-api): tie river worker to signal context Start the auth user sync River client with the signal-aware run context so it begins graceful shutdown as soon as SIGINT or SIGTERM arrives, while preserving the explicit coordinated stop path. --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Ben Fornefeld <ben-fornefeld@users.noreply.github.com>
1 parent 0b946c4 commit ee901d4

56 files changed

Lines changed: 1250 additions & 154 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.gcp.template

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ CLICKHOUSE_CLUSTER_SIZE=1
7777

7878
# Dashboard API instance count (default: 0)
7979
DASHBOARD_API_COUNT=
80+
# Dashboard API Supabase DB connection string (default: POSTGRES_CONNECTION_STRING)
81+
SUPABASE_DB_CONNECTION_STRING=
82+
# Enable dashboard-api auth user sync background worker (default: false)
83+
ENABLE_AUTH_USER_SYNC_BACKGROUND_WORKER=
8084

8185
# Filestore cache for builds shared across cluster (default:false)
8286
FILESTORE_CACHE_ENABLED=

iac/modules/job-dashboard-api/jobs/dashboard-api.hcl

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -71,20 +71,12 @@ job "dashboard-api" {
7171
}
7272

7373
env {
74-
GIN_MODE = "release"
75-
ENVIRONMENT = "${environment}"
76-
NODE_ID = "$${node.unique.id}"
77-
PORT = "$${NOMAD_PORT_api}"
78-
POSTGRES_CONNECTION_STRING = "${postgres_connection_string}"
79-
AUTH_DB_CONNECTION_STRING = "${auth_db_connection_string}"
80-
AUTH_DB_READ_REPLICA_CONNECTION_STRING = "${auth_db_read_replica_connection_string}"
81-
CLICKHOUSE_CONNECTION_STRING = "${clickhouse_connection_string}"
82-
SUPABASE_JWT_SECRETS = "${supabase_jwt_secrets}"
83-
REDIS_URL = "${redis_url}"
84-
REDIS_CLUSTER_URL = "${redis_cluster_url}"
85-
REDIS_TLS_CA_BASE64 = "${redis_tls_ca_base64}"
86-
OTEL_COLLECTOR_GRPC_ENDPOINT = "${otel_collector_grpc_endpoint}"
87-
LOGS_COLLECTOR_ADDRESS = "${logs_collector_address}"
74+
NODE_ID = "$${node.unique.id}"
75+
PORT = "$${NOMAD_PORT_api}"
76+
77+
%{ for key, value in env ~}
78+
${key} = "${value}"
79+
%{ endfor ~}
8880
}
8981

9082
config {
Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,35 @@
1+
locals {
2+
base_env = {
3+
GIN_MODE = "release"
4+
ENVIRONMENT = var.environment
5+
POSTGRES_CONNECTION_STRING = var.postgres_connection_string
6+
AUTH_DB_CONNECTION_STRING = var.auth_db_connection_string
7+
AUTH_DB_READ_REPLICA_CONNECTION_STRING = var.auth_db_read_replica_connection_string
8+
SUPABASE_DB_CONNECTION_STRING = var.supabase_db_connection_string
9+
CLICKHOUSE_CONNECTION_STRING = var.clickhouse_connection_string
10+
SUPABASE_JWT_SECRETS = var.supabase_jwt_secrets
11+
REDIS_URL = var.redis_url
12+
REDIS_CLUSTER_URL = var.redis_cluster_url
13+
REDIS_TLS_CA_BASE64 = var.redis_tls_ca_base64
14+
ENABLE_AUTH_USER_SYNC_BACKGROUND_WORKER = tostring(var.enable_auth_user_sync_background_worker)
15+
OTEL_COLLECTOR_GRPC_ENDPOINT = "localhost:${var.otel_collector_grpc_port}"
16+
LOGS_COLLECTOR_ADDRESS = "http://localhost:${var.logs_proxy_port.port}"
17+
}
18+
}
19+
120
resource "nomad_job" "dashboard_api" {
221
jobspec = templatefile("${path.module}/jobs/dashboard-api.hcl", {
322
update_stanza = var.update_stanza
423
node_pool = var.node_pool
524
image_name = var.image
6-
environment = var.environment
725

826
count = var.count_instances
927

1028
memory_mb = 512
1129
cpu_count = 1
1230

13-
postgres_connection_string = var.postgres_connection_string
14-
auth_db_connection_string = var.auth_db_connection_string
15-
auth_db_read_replica_connection_string = var.auth_db_read_replica_connection_string
16-
clickhouse_connection_string = var.clickhouse_connection_string
17-
supabase_jwt_secrets = var.supabase_jwt_secrets
18-
redis_url = var.redis_url
19-
redis_cluster_url = var.redis_cluster_url
20-
redis_tls_ca_base64 = var.redis_tls_ca_base64
31+
env = local.base_env
2132

2233
subdomain = "dashboard-api"
23-
24-
otel_collector_grpc_endpoint = "localhost:${var.otel_collector_grpc_port}"
25-
logs_collector_address = "http://localhost:${var.logs_proxy_port.port}"
2634
})
2735
}

iac/modules/job-dashboard-api/variables.tf

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ variable "auth_db_read_replica_connection_string" {
3434
default = ""
3535
}
3636

37+
variable "supabase_db_connection_string" {
38+
type = string
39+
sensitive = true
40+
default = ""
41+
}
42+
3743
variable "clickhouse_connection_string" {
3844
type = string
3945
sensitive = true
@@ -44,6 +50,11 @@ variable "supabase_jwt_secrets" {
4450
sensitive = true
4551
}
4652

53+
variable "enable_auth_user_sync_background_worker" {
54+
type = bool
55+
default = false
56+
}
57+
4758
variable "otel_collector_grpc_port" {
4859
type = number
4960
default = 4317

iac/provider-gcp/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ tf_vars := \
7676
$(call tfvar, LOKI_BOOT_DISK_TYPE) \
7777
$(call tfvar, LOKI_USE_V13_SCHEMA_FROM) \
7878
$(call tfvar, DASHBOARD_API_COUNT) \
79+
$(call tfvar, SUPABASE_DB_CONNECTION_STRING) \
80+
$(call tfvar, ENABLE_AUTH_USER_SYNC_BACKGROUND_WORKER) \
7981
$(call tfvar, DEFAULT_PERSISTENT_VOLUME_TYPE) \
8082
$(call tfvar, PERSISTENT_VOLUME_TYPES) \
8183
$(call tfvar, DB_MAX_OPEN_CONNECTIONS) \

iac/provider-gcp/main.tf

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,9 @@ module "nomad" {
275275
otel_collector_resources_cpu_count = var.otel_collector_resources_cpu_count
276276

277277
# Dashboard API
278-
dashboard_api_count = var.dashboard_api_count
278+
dashboard_api_count = var.dashboard_api_count
279+
supabase_db_connection_string = var.supabase_db_connection_string
280+
enable_auth_user_sync_background_worker = var.enable_auth_user_sync_background_worker
279281

280282
# Docker reverse proxy
281283
docker_reverse_proxy_port = var.docker_reverse_proxy_port

iac/provider-gcp/nomad/main.tf

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,16 @@ module "dashboard_api" {
130130

131131
image = data.google_artifact_registry_docker_image.dashboard_api_image[0].self_link
132132

133-
postgres_connection_string = data.google_secret_manager_secret_version.postgres_connection_string.secret_data
134-
auth_db_connection_string = data.google_secret_manager_secret_version.postgres_connection_string.secret_data
135-
auth_db_read_replica_connection_string = trimspace(data.google_secret_manager_secret_version.postgres_read_replica_connection_string.secret_data)
136-
clickhouse_connection_string = local.clickhouse_connection_string
137-
supabase_jwt_secrets = trimspace(data.google_secret_manager_secret_version.supabase_jwt_secrets.secret_data)
138-
redis_url = local.redis_url
139-
redis_cluster_url = local.redis_cluster_url
140-
redis_tls_ca_base64 = trimspace(data.google_secret_manager_secret_version.redis_tls_ca_base64.secret_data)
133+
postgres_connection_string = data.google_secret_manager_secret_version.postgres_connection_string.secret_data
134+
auth_db_connection_string = data.google_secret_manager_secret_version.postgres_connection_string.secret_data
135+
auth_db_read_replica_connection_string = trimspace(data.google_secret_manager_secret_version.postgres_read_replica_connection_string.secret_data)
136+
supabase_db_connection_string = var.supabase_db_connection_string
137+
clickhouse_connection_string = local.clickhouse_connection_string
138+
supabase_jwt_secrets = trimspace(data.google_secret_manager_secret_version.supabase_jwt_secrets.secret_data)
139+
redis_url = local.redis_url
140+
redis_cluster_url = local.redis_cluster_url
141+
redis_tls_ca_base64 = trimspace(data.google_secret_manager_secret_version.redis_tls_ca_base64.secret_data)
142+
enable_auth_user_sync_background_worker = var.enable_auth_user_sync_background_worker
141143

142144
otel_collector_grpc_port = var.otel_collector_grpc_port
143145
logs_proxy_port = var.logs_proxy_port

iac/provider-gcp/nomad/variables.tf

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,17 @@ variable "dashboard_api_count" {
454454
default = 0
455455
}
456456

457+
variable "supabase_db_connection_string" {
458+
type = string
459+
default = ""
460+
sensitive = true
461+
}
462+
463+
variable "enable_auth_user_sync_background_worker" {
464+
type = bool
465+
default = false
466+
}
467+
457468
variable "volume_token_issuer" {
458469
type = string
459470
}

iac/provider-gcp/variables.tf

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,17 @@ variable "dashboard_api_count" {
230230
default = 0
231231
}
232232

233+
variable "supabase_db_connection_string" {
234+
type = string
235+
default = ""
236+
sensitive = true
237+
}
238+
239+
variable "enable_auth_user_sync_background_worker" {
240+
type = bool
241+
default = false
242+
}
243+
233244
variable "docker_reverse_proxy_port" {
234245
type = object({
235246
name = string

packages/api/go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ require (
3535
github.com/google/uuid v1.6.0
3636
github.com/grafana/loki/v3 v3.6.4
3737
github.com/hashicorp/nomad/api v0.0.0-20251216171439-1dee0671280e
38-
github.com/jackc/pgx/v5 v5.7.5
38+
github.com/jackc/pgx/v5 v5.9.1
3939
github.com/launchdarkly/go-sdk-common/v3 v3.3.0
4040
github.com/launchdarkly/go-server-sdk/v7 v7.13.0
4141
github.com/oapi-codegen/gin-middleware v1.0.2
@@ -386,7 +386,7 @@ require (
386386
golang.org/x/crypto v0.49.0 // indirect
387387
golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect
388388
golang.org/x/image v0.38.0 // indirect
389-
golang.org/x/mod v0.33.0 // indirect
389+
golang.org/x/mod v0.34.0 // indirect
390390
golang.org/x/oauth2 v0.34.0 // indirect
391391
golang.org/x/sys v0.42.0 // indirect
392392
golang.org/x/text v0.35.0 // indirect

0 commit comments

Comments
 (0)