Skip to content

feat: map price-history records to canonical cardId#136

Merged
Pyronewbic merged 3 commits into
mainfrom
dev
May 20, 2026
Merged

feat: map price-history records to canonical cardId#136
Pyronewbic merged 3 commits into
mainfrom
dev

Conversation

@Pyronewbic
Copy link
Copy Markdown
Owner

Summary

  • Price records now store cardId (e.g. sv8a/217-187) alongside cardKey, unifying price history across different search queries for the same card
  • getPriceHistory() queries by cardId when available, falls back to cardKey for backwards compatibility
  • Threaded through all callers: /api/search, /api/track-prices, /api/price-history, portfolio enrichment, gainers/losers
  • New Firestore composite index: price-history on cardId + recordedAt desc
  • Existing data remains queryable by cardKey — new records get both fields

Test plan

  • 310 unit tests pass
  • Verify /api/price-history?q=Umbreon+ex+SAR+217/187 returns cardId in response
  • Verify price-history Firestore docs contain cardId field after search/track-prices run
  • Verify portfolio enrichment still resolves current prices

@github-actions
Copy link
Copy Markdown

Terraform Plan

Acquiring state lock. This may take a few moments...
google_project_service.cloudkms: Refreshing state... [id=casecomp-495718/cloudkms.googleapis.com]
data.google_project.current: Reading...
google_logging_metric.api_errors: Refreshing state... [id=cardscrapebot-errors]
google_project_service.monitoring: Refreshing state... [id=casecomp-495718/monitoring.googleapis.com]
google_project_service.run: Refreshing state... [id=casecomp-495718/run.googleapis.com]
google_project_service.binaryauthorization: Refreshing state... [id=casecomp-495718/binaryauthorization.googleapis.com]
google_storage_bucket.site: Refreshing state... [id=casecomp-site]
google_project_service.secretmanager: Refreshing state... [id=casecomp-495718/secretmanager.googleapis.com]
google_compute_managed_ssl_certificate.site_cert: Refreshing state... [id=projects/casecomp-495718/global/sslCertificates/casecomp-site-cert]
google_project_service.scheduler: Refreshing state... [id=casecomp-495718/cloudscheduler.googleapis.com]
data.google_secret_manager_secret_version.api_key: Reading...
google_project_iam_member.deploy_sa_occurrence_editor: Refreshing state... [id=casecomp-495718/roles/containeranalysis.occurrences.editor/serviceAccount:casecomp-deploy@casecomp-495718.iam.gserviceaccount.com]
data.google_secret_manager_secret_version.api_key: Read complete after 0s [id=projects/129850122606/secrets/CASECOMP_API_KEY/versions/1]
google_project_service.compute: Refreshing state... [id=casecomp-495718/compute.googleapis.com]
google_project_service.cloudbuild: Refreshing state... [id=casecomp-495718/cloudbuild.googleapis.com]
data.google_project.current: Read complete after 0s [id=projects/casecomp-495718]
google_project_service.artifactregistry: Refreshing state... [id=casecomp-495718/artifactregistry.googleapis.com]
google_compute_managed_ssl_certificate.api_cert: Refreshing state... [id=projects/casecomp-495718/global/sslCertificates/cardscrapebot-cert-v2]
google_project_service.containeranalysis: Refreshing state... [id=casecomp-495718/containeranalysis.googleapis.com]
google_project_iam_member.deploy_sa_note_attacher: Refreshing state... [id=casecomp-495718/roles/containeranalysis.notes.attacher/serviceAccount:casecomp-deploy@casecomp-495718.iam.gserviceaccount.com]
google_project_service.firestore: Refreshing state... [id=casecomp-495718/firestore.googleapis.com]
google_storage_bucket_iam_member.site_public: Refreshing state... [id=b/casecomp-site/roles/storage.objectViewer/allUsers]
google_secret_manager_secret.api_secrets["ANTHROPIC_API_KEY"]: Refreshing state... [id=projects/casecomp-495718/secrets/ANTHROPIC_API_KEY]
google_container_analysis_note.deploy_attestor: Refreshing state... [id=projects/casecomp-495718/notes/deploy-attestor]
google_kms_key_ring.binary_auth: Refreshing state... [id=projects/casecomp-495718/locations/global/keyRings/binary-auth]
google_cloud_scheduler_job.check_alerts: Refreshing state... [id=projects/casecomp-495718/locations/asia-south1/jobs/casecomp-check-alerts]
google_secret_manager_secret.api_secrets["CASECOMP_JWT_SECRET"]: Refreshing state... [id=projects/casecomp-495718/secrets/CASECOMP_JWT_SECRET]
google_secret_manager_secret.api_secrets["TOGETHER_API_KEY"]: Refreshing state... [id=projects/casecomp-495718/secrets/TOGETHER_API_KEY]
google_secret_manager_secret.api_secrets["CASECOMP_SANDBOX_KEY"]: Refreshing state... [id=projects/casecomp-495718/secrets/CASECOMP_SANDBOX_KEY]
google_secret_manager_secret.api_secrets["CASECOMP_API_KEY"]: Refreshing state... [id=projects/casecomp-495718/secrets/CASECOMP_API_KEY]
google_secret_manager_secret.api_secrets["EBAY_CLIENT_SECRET"]: Refreshing state... [id=projects/casecomp-495718/secrets/EBAY_CLIENT_SECRET]
google_secret_manager_secret.api_secrets["PSA_AUTH_TOKEN"]: Refreshing state... [id=projects/casecomp-495718/secrets/PSA_AUTH_TOKEN]
google_secret_manager_secret.api_secrets["RESEND_API_KEY"]: Refreshing state... [id=projects/casecomp-495718/secrets/RESEND_API_KEY]
google_secret_manager_secret.api_secrets["CASECOMP_ADMIN_SUB"]: Refreshing state... [id=projects/casecomp-495718/secrets/CASECOMP_ADMIN_SUB]
google_secret_manager_secret.api_secrets["EBAY_CLIENT_ID"]: Refreshing state... [id=projects/casecomp-495718/secrets/EBAY_CLIENT_ID]
google_secret_manager_secret.api_secrets["GOOGLE_OAUTH_CLIENT_ID"]: Refreshing state... [id=projects/casecomp-495718/secrets/GOOGLE_OAUTH_CLIENT_ID]
google_compute_global_address.api_ip: Refreshing state... [id=projects/casecomp-495718/global/addresses/cardscrapebot-ip]
google_artifact_registry_repository.casecomp_api: Refreshing state... [id=projects/casecomp-495718/locations/us/repositories/casecomp-api]
google_artifact_registry_repository.casecomp_node24: Refreshing state... [id=projects/casecomp-495718/locations/us/repositories/casecomp-node24]
google_cloud_scheduler_job.track_prices: Refreshing state... [id=projects/casecomp-495718/locations/asia-south1/jobs/casecomp-track-prices]
google_monitoring_notification_channel.email: Refreshing state... [id=projects/casecomp-495718/notificationChannels/3431772178774051140]
google_monitoring_uptime_check_config.api_uptime: Refreshing state... [id=projects/casecomp-495718/uptimeCheckConfigs/casecomp-api-health-lQkUaC0Vzb8]
google_secret_manager_secret_iam_member.cloud_run_access["TOGETHER_API_KEY"]: Refreshing state... [id=projects/casecomp-495718/secrets/TOGETHER_API_KEY/roles/secretmanager.secretAccessor/serviceAccount:129850122606-compute@developer.gserviceaccount.com]
google_secret_manager_secret_iam_member.cloud_run_access["EBAY_CLIENT_ID"]: Refreshing state... [id=projects/casecomp-495718/secrets/EBAY_CLIENT_ID/roles/secretmanager.secretAccessor/serviceAccount:129850122606-compute@developer.gserviceaccount.com]
google_secret_manager_secret_iam_member.cloud_run_access["GOOGLE_OAUTH_CLIENT_ID"]: Refreshing state... [id=projects/casecomp-495718/secrets/GOOGLE_OAUTH_CLIENT_ID/roles/secretmanager.secretAccessor/serviceAccount:129850122606-compute@developer.gserviceaccount.com]
google_secret_manager_secret_iam_member.cloud_run_access["CASECOMP_API_KEY"]: Refreshing state... [id=projects/casecomp-495718/secrets/CASECOMP_API_KEY/roles/secretmanager.secretAccessor/serviceAccount:129850122606-compute@developer.gserviceaccount.com]
google_secret_manager_secret_iam_member.cloud_run_access["RESEND_API_KEY"]: Refreshing state... [id=projects/casecomp-495718/secrets/RESEND_API_KEY/roles/secretmanager.secretAccessor/serviceAccount:129850122606-compute@developer.gserviceaccount.com]
google_secret_manager_secret_iam_member.cloud_run_access["ANTHROPIC_API_KEY"]: Refreshing state... [id=projects/casecomp-495718/secrets/ANTHROPIC_API_KEY/roles/secretmanager.secretAccessor/serviceAccount:129850122606-compute@developer.gserviceaccount.com]
google_secret_manager_secret_iam_member.cloud_run_access["CASECOMP_ADMIN_SUB"]: Refreshing state... [id=projects/casecomp-495718/secrets/CASECOMP_ADMIN_SUB/roles/secretmanager.secretAccessor/serviceAccount:129850122606-compute@developer.gserviceaccount.com]
google_secret_manager_secret_iam_member.cloud_run_access["PSA_AUTH_TOKEN"]: Refreshing state... [id=projects/casecomp-495718/secrets/PSA_AUTH_TOKEN/roles/secretmanager.secretAccessor/serviceAccount:129850122606-compute@developer.gserviceaccount.com]
google_secret_manager_secret_iam_member.cloud_run_access["CASECOMP_SANDBOX_KEY"]: Refreshing state... [id=projects/casecomp-495718/secrets/CASECOMP_SANDBOX_KEY/roles/secretmanager.secretAccessor/serviceAccount:129850122606-compute@developer.gserviceaccount.com]
google_secret_manager_secret_iam_member.cloud_run_access["EBAY_CLIENT_SECRET"]: Refreshing state... [id=projects/casecomp-495718/secrets/EBAY_CLIENT_SECRET/roles/secretmanager.secretAccessor/serviceAccount:129850122606-compute@developer.gserviceaccount.com]
google_secret_manager_secret_iam_member.cloud_run_access["CASECOMP_JWT_SECRET"]: Refreshing state... [id=projects/casecomp-495718/secrets/CASECOMP_JWT_SECRET/roles/secretmanager.secretAccessor/serviceAccount:129850122606-compute@developer.gserviceaccount.com]
google_kms_crypto_key.attestor_key: Refreshing state... [id=projects/casecomp-495718/locations/global/keyRings/binary-auth/cryptoKeys/attestor-key]
google_monitoring_alert_policy.api_error_alert: Refreshing state... [id=projects/casecomp-495718/alertPolicies/16365448047387079183]
google_monitoring_alert_policy.api_uptime_alert: Refreshing state... [id=projects/casecomp-495718/alertPolicies/14098674883088940398]
google_artifact_registry_repository_iam_member.api_cloudbuild: Refreshing state... [id=projects/casecomp-495718/locations/us/repositories/casecomp-api/roles/artifactregistry.writer/serviceAccount:129850122606@cloudbuild.gserviceaccount.com]
google_artifact_registry_repository_iam_member.api_deploy: Refreshing state... [id=projects/casecomp-495718/locations/us/repositories/casecomp-api/roles/artifactregistry.writer/serviceAccount:casecomp-deploy@casecomp-495718.iam.gserviceaccount.com]
google_kms_crypto_key_iam_member.deploy_sa_signer: Refreshing state... [id=projects/casecomp-495718/locations/global/keyRings/binary-auth/cryptoKeys/attestor-key/roles/cloudkms.signerVerifier/serviceAccount:casecomp-deploy@casecomp-495718.iam.gserviceaccount.com]
google_binary_authorization_attestor.deploy: Refreshing state... [id=projects/casecomp-495718/attestors/deploy-attestor]
google_artifact_registry_repository_iam_member.node24_deploy: Refreshing state... [id=projects/casecomp-495718/locations/us/repositories/casecomp-node24/roles/artifactregistry.writer/serviceAccount:casecomp-deploy@casecomp-495718.iam.gserviceaccount.com]
google_binary_authorization_policy.default: Refreshing state... [id=projects/casecomp-495718]
google_cloud_run_v2_service.site["asia-south1"]: Refreshing state... [id=projects/casecomp-495718/locations/asia-south1/services/casecomp-site]
google_cloud_run_v2_service.api["asia-south1"]: Refreshing state... [id=projects/casecomp-495718/locations/asia-south1/services/casecomp-api]
google_cloud_run_v2_service.api["us-central1"]: Refreshing state... [id=projects/casecomp-495718/locations/us-central1/services/casecomp-api]
google_cloud_run_v2_service.site["us-central1"]: Refreshing state... [id=projects/casecomp-495718/locations/us-central1/services/casecomp-site]
google_cloud_run_v2_service_iam_member.public["asia-south1"]: Refreshing state... [id=projects/casecomp-495718/locations/asia-south1/services/casecomp-api/roles/run.invoker/allUsers]
google_cloud_run_v2_service_iam_member.public["us-central1"]: Refreshing state... [id=projects/casecomp-495718/locations/us-central1/services/casecomp-api/roles/run.invoker/allUsers]
google_compute_region_network_endpoint_group.api_neg["us-central1"]: Refreshing state... [id=projects/casecomp-495718/regions/us-central1/networkEndpointGroups/casecomp-api-neg-us-central1]
google_compute_region_network_endpoint_group.api_neg["asia-south1"]: Refreshing state... [id=projects/casecomp-495718/regions/asia-south1/networkEndpointGroups/casecomp-api-neg]
google_cloud_run_v2_service_iam_member.site_public["us-central1"]: Refreshing state... [id=projects/casecomp-495718/locations/us-central1/services/casecomp-site/roles/run.invoker/allUsers]
google_cloud_run_v2_service_iam_member.site_public["asia-south1"]: Refreshing state... [id=projects/casecomp-495718/locations/asia-south1/services/casecomp-site/roles/run.invoker/allUsers]
google_compute_region_network_endpoint_group.site_neg["us-central1"]: Refreshing state... [id=projects/casecomp-495718/regions/us-central1/networkEndpointGroups/casecomp-site-neg-us-central1]
google_compute_region_network_endpoint_group.site_neg["asia-south1"]: Refreshing state... [id=projects/casecomp-495718/regions/asia-south1/networkEndpointGroups/casecomp-site-neg]
google_firestore_database.default: Refreshing state... [id=projects/casecomp-495718/databases/(default)]
google_compute_backend_service.api_backend: Refreshing state... [id=projects/casecomp-495718/global/backendServices/cardscrapebot-backend]
google_compute_backend_service.site_backend: Refreshing state... [id=projects/casecomp-495718/global/backendServices/casecomp-site-backend]
google_compute_url_map.api_urlmap: Refreshing state... [id=projects/casecomp-495718/global/urlMaps/cardscrapebot-urlmap]
google_compute_target_https_proxy.api_proxy: Refreshing state... [id=projects/casecomp-495718/global/targetHttpsProxies/cardscrapebot-https-proxy]
google_firestore_index.composite["grade-logs_userId_createdAt"]: Refreshing state... [id=projects/casecomp-495718/databases/(default)/collectionGroups/grade-logs/indexes/CICAgJim14AK]
google_firestore_index.composite["api-analytics_userId_ts"]: Refreshing state... [id=projects/casecomp-495718/databases/(default)/collectionGroups/api-analytics/indexes/CICAgJjF9oIK]
google_firestore_index.composite["api-keys_ownerId_createdAt"]: Refreshing state... [id=projects/casecomp-495718/databases/(default)/collectionGroups/api-keys/indexes/CICAgJiUpoMK]
google_firestore_index.composite["price-history_cardKey_recordedAt"]: Refreshing state... [id=projects/casecomp-495718/databases/(default)/collectionGroups/price-history/indexes/CICAgOjXh4EK]
google_firestore_index.composite["grade-logs_source_createdAt"]: Refreshing state... [id=projects/casecomp-495718/databases/(default)/collectionGroups/grade-logs/indexes/CICAgJj7z4EJ]
google_compute_global_forwarding_rule.api_https: Refreshing state... [id=projects/casecomp-495718/global/forwardingRules/cardscrapebot-https-rule]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # google_firestore_index.composite["price-history_cardId_recordedAt"] will be created
  + resource "google_firestore_index" "composite" {
      + api_scope   = "ANY_API"
      + collection  = "price-history"
      + database    = "(default)"
      + id          = (known after apply)
      + name        = (known after apply)
      + project     = "casecomp-495718"
      + query_scope = "COLLECTION"

      + fields {
          + field_path = "cardId"
          + order      = "ASCENDING"
        }
      + fields {
          + field_path = "recordedAt"
          + order      = "DESCENDING"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

─────────────────────────────────────────────────────────────────────────────

Saved the plan to: tfplan

To perform exactly these actions, run the following command to apply:
    terraform apply "tfplan"
Releasing state lock. This may take a few moments...

Merge to main to apply.

@Pyronewbic Pyronewbic merged commit e438683 into main May 20, 2026
12 checks passed
@Pyronewbic Pyronewbic deleted the dev branch May 20, 2026 21:07
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.

1 participant