Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,28 @@ Initial public beta.
### Added
- Web dashboard with AI pre-grading detail panel, PSA signal bar, source filters
- Multi-source slab search: compare PSA 10 prices across eBay, magi.camp, Yahoo Auctions
- AI pre-grading from listing photos (centering, corners, edges, surface + confidence)
- Per-subgrade AI grading: centering, corners, edges, surface graded independently in parallel
- Front + back image analysis with subgradeDetails (score, confidence, detail per attribute)
- PSA tier recommendations (Value/Regular/Express) with reasoning per card value
- REST API with CC_LIVE_ key auth, rate limiting (60/min auth, 20/min sample data), error monitoring
- REST API with CC_LIVE_ key auth + CC_LIVE_SANDBOX_ public sandbox key
- Rate limiting: 60/min auth, 20/min sample data, 5/min sandbox
- Firestore caching with stale-while-revalidate, per-key cache isolation
- Magi search migrated from Playwright to fetch+cheerio (~10x faster)
- eBay sold scrape retry with backoff on 503
- OAuth token pre-fetched on server startup
- Security: helmet headers, error sanitization, request IDs, trust proxy
- 105 tests (63 unit + 42 API integration)
- GitHub Actions CI on push/PR
- GitHub Actions CI on push/PR, auto-deploy on merge to main
- Chrome extension: queue auto-join for Pokemon Center, Walmart, Costco, Target
- Claude Code `/casecomp` skill for plain-English card search
- GitHub release v1.0.0-beta.1 with Chrome extension zip

### Infrastructure
- GCP Cloud Run (asia-south1), Firestore, HTTPS load balancer, managed SSL
- Secret Manager for API keys (EBAY, ANTHROPIC, PSA, CASECOMP)
- Cloud Monitoring alert policy (email on >5 errors/5min)
- Cloud Run `casecomp-api` (API) + `casecomp-site` (frontend SSR with Cloud CDN)
- HTTPS LB routes by host: casecomp.xyz → site, api.casecomp.xyz → API
- Firestore, managed SSL certs, Secret Manager (incl. sandbox key)
- Cloud Monitoring: error alerts + uptime check on /api/health
- Terraform with GCS state backend
- Workload Identity Federation for GitHub Actions → GCP (no stored keys)
- Kaniko layer caching for Cloud Build
- Branch protection on main: CI required before merge
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ lib/
demo.js Sample data (3 cards with real listings)
swagger.js OpenAPI 3.0.3 spec
extension/ Chrome extension: queue auto-join, drop intel
terraform/ GCP infra: Cloud Run, Firestore, LB, Secret Manager
terraform/ GCP infra: Cloud Run ×2, Firestore, LB + CDN, Secret Manager
test/
unit-test.js 63 unit tests (filters, grading, query, demo data)
api-test.js 42 API integration tests
Expand Down Expand Up @@ -172,6 +172,7 @@ EBAY_CLIENT_SECRET= # required
ANTHROPIC_API_KEY= # AI grading + magi translation
PSA_AUTH_TOKEN= # PSA pop reports
CASECOMP_API_KEY= # API auth (CC_LIVE_ prefix)
CASECOMP_SANDBOX_KEY= # public sandbox key (CC_LIVE_SANDBOX_ prefix, 5/min)
```

In production, secrets are stored in GCP Secret Manager and referenced by Cloud Run.
Expand All @@ -191,7 +192,7 @@ All caches use Firestore (shared across Cloud Run instances, persists across dep

## Infrastructure

GCP (Terraform managed): Cloud Run (asia-south1), Firestore, HTTPS load balancer with managed SSL, Secret Manager, Cloud Monitoring alerts. State stored in GCS bucket. See `terraform/`.
GCP (Terraform managed): Cloud Run `casecomp-api` (API) + `casecomp-site` (frontend SSR), Firestore, HTTPS load balancer with Cloud CDN, managed SSL, Secret Manager, Cloud Monitoring alerts. State in GCS bucket. Same LB IP routes by host: `casecomp.xyz` → site, `api.casecomp.xyz` → API. See `terraform/`.

## Chrome Extension

Expand Down
30 changes: 22 additions & 8 deletions terraform/README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,37 @@
# Terraform — Casecomp Infrastructure

GCP infrastructure for the Casecomp API. State is stored in a GCS bucket (`casecomp-terraform-state`).
GCP infrastructure for Casecomp. State is stored in a GCS bucket (`casecomp-terraform-state`).

## Resources

| Resource | Purpose |
|----------|---------|
| Cloud Run (`cardscrapebot`) | API + dashboard, asia-south1, scales to 20 instances |
| Cloud Run `casecomp-api` | API + dashboard, asia-south1, scales to 20 instances |
| Cloud Run `casecomp-site` | Frontend SSR (TanStack Start), scales to 10 instances |
| Firestore | Grade logs, drops, webhooks, alerts, all caches |
| HTTPS Load Balancer | Global IP, managed SSL cert, URL map, backend service |
| Secret Manager | EBAY_CLIENT_ID/SECRET, ANTHROPIC_API_KEY, PSA_AUTH_TOKEN, CASECOMP_API_KEY |
| Cloud Monitoring | Log-based metric on `[ERROR]`, email alert on >5 errors/5min |
| HTTPS Load Balancer | Global IP (`34.107.143.136`), URL map routes by host |
| Cloud CDN | Caches static assets from frontend Cloud Run |
| SSL Certificates | Managed certs for `api.casecomp.xyz` and `casecomp.xyz` + `www` |
| GCS Bucket `casecomp-site` | (Legacy) Static site bucket, replaced by Cloud Run SSR |
| Secret Manager | EBAY_CLIENT_ID/SECRET, ANTHROPIC_API_KEY, PSA_AUTH_TOKEN, CASECOMP_API_KEY, CASECOMP_SANDBOX_KEY |
| Cloud Monitoring | Log-based metric on `[ERROR]`, error + uptime alerts → email |
| APIs enabled | Cloud Run, Compute, Firestore, Cloud Build, Secret Manager, Monitoring |

## Routing

Same LB IP, routed by hostname:
- `casecomp.xyz` / `www.casecomp.xyz` → Cloud Run `casecomp-site` (CDN enabled)
- `api.casecomp.xyz` → Cloud Run `casecomp-api`

## Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `project_id` | `casecomp-495718` | GCP project |
| `region` | `asia-south1` | Deploy region |
| `domain` | `api.casecomp.xyz` | SSL cert domain |
| `container_image` | `gcr.io/casecomp-495718/cardscrapebot` | Docker image |
| `api_domain` | `api.casecomp.xyz` | API SSL cert domain |
| `site_domain` | `casecomp.xyz` | Frontend SSL cert domain |
| `container_image` | `gcr.io/casecomp-495718/casecomp-api` | API Docker image |
| `alert_email` | *(sensitive, in terraform.tfvars)* | Monitoring alert recipient |

## Usage
Expand All @@ -37,7 +48,10 @@ If resources were created manually (outside Terraform), import them before apply

```bash
terraform import google_cloud_run_v2_service.api \
"projects/casecomp-495718/locations/asia-south1/services/cardscrapebot"
"projects/casecomp-495718/locations/asia-south1/services/casecomp-api"

terraform import google_cloud_run_v2_service.site \
"projects/casecomp-495718/locations/asia-south1/services/casecomp-site"
```

## Files
Expand Down
67 changes: 61 additions & 6 deletions terraform/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -196,15 +196,70 @@ resource "google_compute_backend_service" "api_backend" {
}
}

resource "google_compute_backend_bucket" "site_backend" {
name = "casecomp-site-backend"
bucket_name = google_storage_bucket.site.name
enable_cdn = true
resource "google_cloud_run_v2_service" "site" {
name = "casecomp-site"
location = var.region

template {
scaling {
max_instance_count = 10
}

containers {
image = "gcr.io/${var.project_id}/casecomp-site"

ports {
container_port = 8080
}

resources {
limits = {
cpu = "1000m"
memory = "512Mi"
}
}
}
}

depends_on = [google_project_service.run]
}

resource "google_cloud_run_v2_service_iam_member" "site_public" {
name = google_cloud_run_v2_service.site.name
location = var.region
role = "roles/run.invoker"
member = "allUsers"
}

resource "google_compute_region_network_endpoint_group" "site_neg" {
name = "casecomp-site-neg"
region = var.region
network_endpoint_type = "SERVERLESS"

cloud_run {
service = google_cloud_run_v2_service.site.name
}
}

resource "google_compute_backend_service" "site_backend" {
name = "casecomp-site-backend"
enable_cdn = true

cdn_policy {
cache_mode = "CACHE_ALL_STATIC"
default_ttl = 3600
max_ttl = 86400
signed_url_cache_max_age_sec = 0
}

backend {
group = google_compute_region_network_endpoint_group.site_neg.id
}
}

resource "google_compute_url_map" "api_urlmap" {
name = "cardscrapebot-urlmap"
default_service = google_compute_backend_bucket.site_backend.id
default_service = google_compute_backend_service.site_backend.id

host_rule {
hosts = [var.api_domain]
Expand All @@ -223,7 +278,7 @@ resource "google_compute_url_map" "api_urlmap" {

path_matcher {
name = "site"
default_service = google_compute_backend_bucket.site_backend.id
default_service = google_compute_backend_service.site_backend.id
}
}

Expand Down