wip: add volatility data#2340
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a new /volatility API endpoint: query function, controller with x-internal-key validation against YIELDS_INTERNAL_API_KEY, route registration, in-memory TTL cache, export of the query, and serverless/env wiring. (≤50 words) Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant App as API_App
participant Controller as Volatility_Controller
participant Env as Config_YIELDS_INTERNAL_API_KEY
participant Queries as Yield_Queries
participant DB as Database
Client->>App: GET /volatility (x-internal-key)
App->>Controller: forward request
Controller->>Env: validate x-internal-key
Env-->>Controller: valid / invalid
alt valid
Controller->>Queries: getVolatility30d()
Queries->>DB: SELECT ... FROM volatility
DB-->>Queries: rows
Queries-->>Controller: formatted {configID: [apy_avg, apy_median, apy_std, cv]}
Controller-->>Client: 200 OK + JSON
else invalid
Controller-->>Client: 401 Unauthorized
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/handlers/triggerEnrichment.js`:
- Around line 91-102: The Promise.all call makes the whole enrichment pipeline
fail if getVolatility30d rejects; change to a resilient pattern (e.g., use
Promise.allSettled or catch getVolatility30d separately) so getYieldAvg30d still
resolves and volatilityData defaults to an empty object on error; specifically,
keep calling getYieldAvg30d as before, call getVolatility30d in a way that
captures errors and sets volatilityData = {} on failure, then continue the
dataEnriched = dataEnriched.map(...) step so apyMedian30d/apyStd30d/apyCv30d
fall back to null.
In `@src/queries/yield.js`:
- Around line 321-351: The getVolatility30d function currently returns new
AppError(...) on failure which treats the error as a successful return value;
change this to throw the AppError (or throw a plain Error) so callers can catch
it, and optionally log the failure before throwing; locate the early check in
getVolatility30d (after const response = await conn.query(query)) and replace
the "return new AppError(...)" with throwing the error (e.g., throw new
AppError(...)) so Promise.all consumers and triggerEnrichment.js will receive a
rejection instead of an AppError object.
🧹 Nitpick comments (1)
src/queries/yield.js (1)
325-332: Hardcoded table namevolatilitydeviates from the parameterized pattern used elsewhere.All other queries in this file use
$<table:name>or$<yieldTable:name>for table references, which guards against SQL injection and keeps naming consistent. Consider parameterizing:Suggested fix
const query = ` SELECT "configID", apy_median_30d as "apyMedian30d", apy_std_30d as "apyStd30d", cv_30d as "apyCv30d" - FROM volatility + FROM $<table:name> `; - const response = await conn.query(query); + const response = await conn.query(query, { table: 'volatility' });
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@src/api/app.js`:
- Line 48: The redisCache middleware currently runs before the volatility route
and serves cached responses, allowing requests to bypass the x-internal-key
check; update the routing/middleware order so that the auth check runs before
redisCache or explicitly skip caching for the volatility endpoint: move the
volatility route's authentication logic into a dedicated middleware (e.g.,
validateInternalKey) and register that middleware before redisCache, or modify
redisCache to detect the volatility route (or the volatility route handler name)
and short-circuit caching for paths handled by the volatility controller so
unauthenticated requests cannot receive cached responses.
In `@src/api/controllers/volatility.js`:
- Around line 17-19: The current check if (!response) after calling conn.query()
is ineffective because conn.query returns a truthy array/Result even when empty;
change the conditional in the volatility controller to detect an empty result
set (e.g., response.length === 0 or check rows.length if using pg Result) and
return res.status(404).json({ error: "Couldn't get volatility data" }) when no
rows are found; update the logic around the response variable (and any use of
response.rows) in the function handling the query so an empty query result
triggers the 404.
- Around line 5-31: The getVolatility async handler can throw (e.g., conn.query)
and lacks error handling; wrap the body of getVolatility in a try/catch, call
conn.query inside the try, and on catch log the error and return
res.status(500).json({ error: 'Internal Server Error' }) (or include the
error.message if acceptable), ensuring any early 401/404 returns remain
unchanged and the catch covers all unexpected failures from conn.query or other
awaited operations.
🧹 Nitpick comments (1)
src/api/controllers/volatility.js (1)
7-8: Timing-safe comparison recommended for API key validation.Using
!==for secret comparison is susceptible to timing attacks. Consider usingcrypto.timingSafeEqualfor the key comparison.Proposed fix
+const crypto = require('crypto'); + +function safeEqual(a, b) { + if (a.length !== b.length) return false; + return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b)); +} + const getVolatility = async (req, res) => { const apiKey = req.headers['x-internal-key']; - if (!INTERNAL_KEY || !apiKey || apiKey !== INTERNAL_KEY) { + if (!INTERNAL_KEY || !apiKey || !safeEqual(apiKey, INTERNAL_KEY)) { return res.status(401).json({ error: 'Unauthorized' }); }
| return res.status(401).json({ error: 'Unauthorized' }); | ||
| } | ||
|
|
||
| if (cache.data && Date.now() - cache.ts < CACHE_TTL) { |
There was a problem hiding this comment.
one thing about our APIs, we use both cloudflare and cloutfront as CDN.
we need to prevent that route from being cached on CDN cause otherwise a subsequent request without key would just go through. also, fyi: any route without cache headers would trigger our cache defaults of i think 24hours
| try { | ||
| const query = ` | ||
| SELECT "configID", apy_avg_30d, apy_median_30d, apy_std_30d, cv_30d | ||
| FROM volatility |
There was a problem hiding this comment.
cv_30d can be null, lets add WHERE cv_30d IS NOT NULL cause these won't matter on the ui, better to fetch less rows
Summary by CodeRabbit
New Features
Improvements
Chores