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
1 change: 1 addition & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ jobs:
target: https://api.casecomp.xyz/docs/spec.json
format: openapi
fail_action: false
allow_issue_writing: false
cmd_options: '-a'
- name: Upload DAST report
if: always()
Expand Down
8 changes: 8 additions & 0 deletions .superset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"setup": [
"yarn install",
"[ ! -f .env ] && cp .env.example .env"
],
"teardown": [],
"run": []
}
13 changes: 5 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,6 @@ curl "https://api.casecomp.xyz/api/portfolio/set/sv8a?demo=true"
curl -H "Authorization: Bearer $CASECOMP_KEY" \
"https://api.casecomp.xyz/api/search?q=Pikachu+ex+SAR&source=magi&format=slab&slab_provider=PSA&slab_grade=10"

curl -H "Authorization: Bearer $CASECOMP_KEY" \
"https://api.casecomp.xyz/v1/drops"

curl -X POST -H "Authorization: Bearer $CASECOMP_KEY" \
-H "Content-Type: application/json" \
-d '{"email":"you@email.com","query":"Umbreon ex SAR 217/187","type":"arbitrage","spreadThreshold":10}' \
Expand All @@ -128,7 +125,7 @@ curl -X POST -H "Authorization: Bearer $CASECOMP_KEY" \
- **SBOM** - Syft SPDX JSON generated per deploy, uploaded as build artifact
- **Vulnerability scanning** - Grype scans SBOM for CVEs, SARIF uploaded to GitHub Security tab
- **SAST** - CodeQL static analysis on every PR + weekly schedule
- **Binary Authorization** - GCP policy on both Cloud Run services (audit mode)
- **Binary Authorization** - GCP policy enforced on both Cloud Run services
- **Reproducible builds** - Kaniko `--reproducible` flag, pinned version
- **Multi-region** - Cloud Run in asia-south1 + us-central1, global LB auto geo-routes to nearest
- **Custom base image** - Wolfi + apko Node 24 image, manual rebuild, zero CVEs by design
Expand Down Expand Up @@ -214,13 +211,13 @@ Load unpacked from `extension/` in `chrome://extensions`.

## Tests

329 tests across three layers. CI required checks: unit + codeql. Smoke is non-blocking.
434 tests across three layers. CI required checks: unit + codeql. Smoke is non-blocking.

| Suite | Count | Command | Covers |
|-------|------:|---------|--------|
| **Unit** | 151 | `yarn test:unit` | Filters, grading, query builder, card identity, condition detection, image preprocessing, email alerts, portfolio ROI, CSV export, autocomplete, JWT auth, price trends |
| **API** | 104 | `yarn test:api` | Search, sold, PSA, grade, auth, admin keys, arbitrage, price history, alerts, share pages, portfolio CRUD, card view, upload-url, analytics, collection tracking |
| **Smoke** | 74 | `yarn test:smoke` | API root page, detail panel, tabs, PSA stats, arbitrage, mobile viewport, portfolio, autocomplete, search filters |
| **Unit** | 266 | `yarn test:unit` | Filters, grading, query builder, card identity, condition detection, image preprocessing, email alerts, portfolio ROI, CSV export, autocomplete, JWT auth, price trends |
| **API** | 97 | `yarn test:api` | Search, sold, PSA, grade, auth, admin keys, arbitrage, price history, alerts, share pages, portfolio CRUD, card view, upload-url, analytics, collection tracking |
| **Smoke** | 71 | `yarn test:smoke` | API root page, detail panel, tabs, PSA stats, arbitrage, mobile viewport, portfolio, autocomplete, search filters |

## Contributing

Expand Down
9 changes: 9 additions & 0 deletions api.js
Original file line number Diff line number Diff line change
Expand Up @@ -2369,6 +2369,15 @@ app.post("/api/keys/rotate", async (req, res) => {
}
});

app.use((req, res) => {
res.status(404).json({ error: "Not found" });
});

app.use((err, req, res, _next) => {
logError("unhandled", err.message, req.originalUrl, req.requestId);
res.status(500).json({ error: safeErrorMessage(err), requestId: req.requestId });
});

const PORT = process.env.API_PORT || 3000;
app.listen(PORT, async () => {
console.log(`Casecomp API listening on http://localhost:${PORT}`);
Expand Down
5 changes: 4 additions & 1 deletion docs/env-vars.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,10 @@ Copy **`.env.example`** to **`.env`** and fill in the required values.
| `API_URL` | `http://localhost:3000` | API base URL (used in responses) |
| `CASECOMP_API_KEY` | *(none)* | Owner API key (`CC_LIVE_` prefix, 60 req/min) |
| `CASECOMP_SANDBOX_KEY` | *(none)* | Public sandbox key (`CC_LIVE_SANDBOX_` prefix, 5 req/min) |
| `GOOGLE_OAUTH_CLIENT_ID` | *(none)* | Google OAuth client ID (configured but not yet implemented) |
| `CASECOMP_JWT_SECRET` | *(none)* | Secret for signing/verifying JWT tokens (HS256) |
| `CASECOMP_ADMIN_SUB` | *(none)* | Google account `sub` claim for admin access |
| `GOOGLE_OAUTH_CLIENT_ID` | *(none)* | Google OAuth client ID for sign-in (popup flow) |
| `TOGETHER_API_KEY` | *(none)* | Together AI key for card detection (GLM-4.6V, falls back to Claude Sonnet) |

## Email notifications

Expand Down
29 changes: 15 additions & 14 deletions docs/internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,22 @@ lib/
preprocessing.js Card detection, corner crops, SSRF-safe image fetch
psa.js PSA pop reports, cert lookup, grading signal
psaTiers.js PSA submission tier data
data/
firestore.js Firestore: grade logs, drops, webhooks, cache
auth/
auth.js Google OAuth token verification, JWT (HS256)
api-keys.js Developer key management
card-identity.js Canonical IDs, set resolution, SET_TOTAL_MAP
cards/
card-database.js TCGdex card DB (29K EN+JP cards), set browser, rarity
price-history.js Sold comp tracking + TCGPlayer seeding
card-identity.js Canonical IDs, set resolution, SET_TOTAL_MAP
demo.js Sample data (3 multi-source cards)
grading-dataset.js ML slab image collection from eBay sold listings
price-history.js Sold comp tracking + TCGPlayer seeding
data/
analytics.js Request analytics (Firestore, 30d TTL)
cache.js File-based cache (legacy CLI)
redis-cache.js Redis cache (optional)
email.js Alert emails via Resend
csv.js CSV export helpers
portfolio.js Portfolio CRUD (Firestore subcollection)
analytics.js Request analytics (Firestore, 30d TTL)
auth.js Google OAuth token verification, JWT (HS256)
grading-dataset.js ML slab image collection from eBay sold listings
email.js Alert emails via Resend
firestore.js Firestore: grade logs, drops, webhooks, cache
redis-cache.js Redis cache (optional)
search/
filters.js Language, relevance, condition detection, outlier flagging
listingQuery.js eBay search query builder (raw vs slab)
Expand All @@ -47,9 +48,9 @@ public/admin/ Admin panel (keys, stats, errors)
extension/ Chrome extension: queue auto-join, drop intel
terraform/ GCP infra (Cloud Run, Firestore, LB, CDN, Scheduler)
test/
unit-test.js 172 unit tests
api-test.js ~130 API integration tests
smoke-test.js 74 Playwright UI smoke tests
unit-test.js 266 unit tests
api-test.js 97 API integration tests
smoke-test.js 71 Playwright UI smoke tests
```

## API server
Expand All @@ -58,7 +59,7 @@ test/

- **Auth middleware**: owner key (`CC_LIVE_`) → sandbox → JWT (Google OAuth) → Firestore developer keys (30s cache). `apiAuthMiddleware` adds demo bypass.
- **Rate limiting**: 60/min authenticated, 360/min demo, 5/min sandbox, 10/min auth endpoint.
- **Security**: Helmet headers, trust proxy = 1, request IDs, compression, `safeErrorMessage()` on all errors.
- **Security**: Helmet headers, trust proxy = 1, request IDs, compression, `safeErrorMessage()` on all errors. Global JSON 404 catch-all + error handler at bottom of file.
- **CORS**: wildcard `*` — API key is the access control layer.
- **Dashboard**: static files from `public/` served at `/` and `/admin`.
- **Docs**: Swagger UI at `/docs`, spec at `/docs/spec.json`.
Expand Down
Loading