Skip to content

Team DoD: Unified Valkey E-Commerce Challenges 1-14#8

Open
Vasanthadithya-mundrathi wants to merge 11 commits into
opensource-for-valkey:mainfrom
Vasanthadithya-mundrathi:TeamDOD
Open

Team DoD: Unified Valkey E-Commerce Challenges 1-14#8
Vasanthadithya-mundrathi wants to merge 11 commits into
opensource-for-valkey:mainfrom
Vasanthadithya-mundrathi:TeamDOD

Conversation

@Vasanthadithya-mundrathi

Copy link
Copy Markdown

Summary

Team DoD unified implementation for the Valkey e-commerce hackathon challenges 1-14.

  • Adds integrated Valkey-backed backend APIs for auth, catalog, cart, trending, ads, full-text search, vector search, analytics, observability, checkout, delivery, rate limiting, recommendations, and agentic search.
  • Adds frontend challenge navigation and demo pages for the full end-to-end app.
  • Adds consistent product SVG assets and a Team DoD Valkey logo for polished demo rendering.
  • Documents the unified challenge coverage and API surface.

Validation

  • cd backend/checkout && npm run build
  • cd backend/checkout && npm test - 6 files, 25 tests passed
  • cd frontend && CI=true npm test -- --watchAll=false - 6 tests passed
  • cd frontend && npm run build
  • Chrome smoke check on http://localhost:3001/growth verified rendered challenge UI, product images, updated logo, and no localhost console errors on the checked page.

Notes

This implementation uses Valkey Bundle services only for the datastore/runtime. The Node dependency ioredis remains the RESP client used by BullMQ and the backend to talk to Valkey.

Copilot AI review requested due to automatic review settings May 24, 2026 11:31

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR adds an integrated Valkey-powered backend (Challenges 1–14) plus new frontend “challenge” pages and API bindings to drive the demo experience end-to-end.

Changes:

  • Adds a comprehensive Node/TS backend under backend/checkout with Valkey-backed auth, catalog/cart, search (FT + vector), checkout (BullMQ), observability, delivery geo, rate limiting, recommendations, and agentic search.
  • Adds a Python FastAPI embeddings service and a docker-compose.yml to run Valkey Bundle + OpenSearch + embeddings locally.
  • Extends the React frontend with challenge routes/pages, shared navigation, cart context, service client, and updates tests/assets/styles.

Reviewed changes

Copilot reviewed 68 out of 81 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
frontend/src/setupTests.js Adds matchMedia mock for Jest/jsdom compatibility.
frontend/src/services/valkeyApi.js Introduces frontend API client for the integrated backend.
frontend/src/pages/*Page.jsx Adds challenge pages for catalog, growth, semantic search, analytics, observability, delivery, rate limiting, recommendations, agentic search.
frontend/src/context/CartContext.jsx Adds a cart provider backed by backend cart endpoints.
frontend/src/components/ValkeyChallengeNav.jsx Adds nav bar for challenge routes.
frontend/src/App.js Wires new routes and wraps app in CartProvider.
frontend/src/App.test.js Updates routing tests to cover new pages.
frontend/src/index.scss Adds demo-specific media/panel styles + fixes import indentation.
docker-compose.yml Adds local stack for Valkey Bundle, OpenSearch, and embeddings service.
backend/embeddings/* Adds FastAPI embeddings service + Dockerfile.
backend/checkout/* Adds integrated backend implementation, seed script, and Vitest unit/integration tests.
README.md Updates root documentation for the integrated demo stack.
.gitignore Adds repo-level ignores for node/python artifacts and env files.
Comments suppressed due to low confidence (4)

frontend/src/pages/RecommendationsPage.jsx:1

  • recordView/addRecommended are invoked from event handlers but don’t handle failures; any error from recordRecommendationEvent, addItem, or refresh will reject and surface as an unhandled promise rejection. Wrap these bodies in try/catch and report via setMessage(...) (and optionally avoid calling refresh() when the first step fails) to keep the UI stable.
    frontend/src/App.js:1
  • In React Router v6, the exact prop is not used (matching is “exact” by default for path segments). Keeping exact is misleading and can cause confusion during maintenance; remove exact from all <Route ...> declarations.
    frontend/src/pages/SemanticSearchPage.jsx:1
  • The label isn’t programmatically associated with its input (htmlFor + id), which reduces usability for screen readers and form navigation. Add stable id attributes to inputs/selects and set htmlFor on the corresponding <label>s (applies similarly to other new forms like Catalog/Growth/AgenticSearch/Observability pages).
    docker-compose.yml:1
  • This hard-codes an admin password into version control (even though the security plugin is disabled). Move the password to an environment variable (e.g., via a .env file) or remove it when security is disabled to avoid credential leakage and reduce the chance of accidental reuse in non-local environments.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +200 to +204
await client.call("JSON.SET", productKey(productId), "$", JSON.stringify(updated));
await indexProduct(client, updated);
return updated;
}

Comment on lines +153 to +169
const raw = (await client.call(
"FT.SEARCH",
PRODUCT_VECTOR_INDEX,
`*=>[KNN ${limit} @embedding $query_vector AS vector_score]`,
"PARAMS",
"2",
"query_vector",
toFloat32Buffer(vector),
"SORTBY",
"vector_score",
"ASC",
"RETURN",
"1",
"vector_score",
"DIALECT",
"2"
)) as unknown[];

@rlunar rlunar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add screenshots / video

@Vasanthadithya-mundrathi

Copy link
Copy Markdown
Author

UPDATE 487DDDF. ADDED INTEGRATIONS DASHBOARD ENDPOINT AND PAGE. VALIDATION PASSED BACKEND BUILD TEST 26 FRONTEND TEST BUILD CHROME SMOKE. NO H1 H2 OR HACKERRANK FOLDERS.

@rlunar

rlunar commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

@greptileai

@rlunar rlunar self-assigned this Jun 6, 2026
@greptile-apps

greptile-apps Bot commented Jun 6, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a full-stack Valkey-backed e-commerce backend (challenges 1–14) covering auth, catalog, cart, checkout queues, trending, ads, full-text and vector search, analytics, observability, delivery, rate limiting, recommendations, and agentic search, plus corresponding React demo pages and product SVG assets.

  • The BullMQ five-stage checkout pipeline (reserve → payment → confirm → release → dispatch), idempotency layer, and session-based auth are well-structured and use Valkey primitives correctly.
  • The product write endpoints (POST /api/products, PATCH /api/products/:id) sit behind an auth middleware that accepts the client-supplied X-User-Id header as valid authentication with no role check, allowing any HTTP client to create or overwrite catalog data.
  • The rate-limit identify function also treats an unvalidated X-User-Id header as authenticated, so anonymous clients can claim higher quotas and exhaust the quota of other users by sending requests under a known user ID.

Confidence Score: 3/5

The checkout pipeline and data layer are well-implemented, but the product catalog can be mutated by any HTTP client and anonymous rate-limit quotas can be trivially upgraded — both issues stem from the same demo auth shortcut.

Two distinct auth-boundary gaps are present on separate code paths: the product write endpoints accept an unauthenticated header as a valid session, and the rate-limit module uses the same header to grant elevated quotas without session verification. Together they mean catalog integrity and throttling assumptions can be broken by any client with no credentials.

backend/checkout/src/server.ts (product write middleware) and backend/checkout/src/rateLimit.ts (identity function) need the most attention before this is deployed in any shared environment.

Security Review

  • Unauthenticated product mutation (backend/checkout/src/server.ts lines 856–875): POST /api/products and PATCH /api/products/:id are gated by a middleware that accepts the client-supplied X-User-Id header as a valid session with no role enforcement. Any HTTP client can create or overwrite product records.
  • Rate-limit identity spoofing (backend/checkout/src/rateLimit.ts lines 78–87): The identify function marks any request carrying X-User-Id as authenticated, granting higher rate-limit quotas without session validation and enabling quota exhaustion against other users by sending requests under their known ID.

Important Files Changed

Filename Overview
backend/checkout/src/server.ts 1182-line Express server wiring all challenge routes; product write endpoints lack admin role enforcement and the CORS fallback returns the first configured origin for unmatched requests; SSE order-events fan-out reads the entire global stream per connection
backend/checkout/src/rateLimit.ts Sliding-window rate limiter using Valkey sorted sets; identity resolution trusts the client-supplied X-User-Id header as authenticated without session validation, and the check-then-add is split across two non-atomic pipelines
backend/checkout/src/auth.ts Session-based auth with bcrypt passwords stored in Valkey JSON; brute-force lockout implemented correctly; demo bypass path (allowUserIdHeader) synthesises sessions without DB validation and is the root cause of the product-write exposure
backend/checkout/src/queues.ts BullMQ-backed five-queue checkout pipeline (reserve → payment → confirm → release → dispatch) with correct retry/backoff and reservation TTL cleanup
backend/checkout/src/idempotency.ts 24-hour idempotency layer using a per-user/key lock in Valkey with polling fallback; logic is sound for single-node Valkey deployments
backend/checkout/src/store.ts Core order and product persistence using Valkey JSON; ownership check on requireOwnedOrder is correct; SCAN-based listing could be slow on large key sets but acceptable for a demo
backend/checkout/src/embeddings.ts Deterministic local embedding fallback with SHA-256 tokenisation; remote embedding client has 1.5-second timeout and graceful fallback to local embeddings
frontend/src/services/valkeyApi.js Frontend API client; generates a random demo user-id stored in localStorage and sends it as X-User-Id for demo flows; this is intentional for the hackathon but directly feeds the server-side identity bypass
frontend/src/App.js Adds ten new challenge routes wrapped in CartProvider; straightforward routing change with no issues
backend/checkout/src/agent.ts Rule-based agentic search with Valkey JSON conversation store; keyword extraction is deterministic and safe; feedback stored per-product without user scoping (minor)

Comments Outside Diff (1)

  1. backend/checkout/src/server.ts, line 1161-1183 (link)

    P2 CORS middleware falls back to first allowed origin for unmatched requests

    When requestOrigin is not in configuredOrigins, allowOrigin resolves to configuredOrigins[0] (e.g. http://localhost:3000). Browsers compare the actual Origin header against this reflected value and will still block the request, so this is not a direct bypass — but it sends a misleading header value and causes CORS failures from legitimate origins that were simply not listed (e.g. a second dev port). The unmatched branch should omit the Access-Control-Allow-Origin header or return the request origin only when it is explicitly allowed.

Reviews (1): Last reviewed commit: "add images" | Re-trigger Greptile

Comment on lines +856 to +875
}
});

app.patch("/api/products/:id", async (request: AuthedRequest, response, next) => {
try {
const product = await patchCatalogProduct(context.client, request.params.id, request.body as Record<string, unknown>);
if (!product) {
throw new ApiError(404, "product_not_found", "Product was not found.");
}
response.json({ product });
} catch (error) {
next(error);
}
});

app.post("/api/checkout/start", async (request: AuthedRequest, response, next) => {
try {
const userId = request.userId!;
const key = idempotencyKeyFrom(request);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Missing admin role check on product write endpoints

POST /api/products and PATCH /api/products/:id sit behind the middleware at line 845 which calls authenticateRequest with allowUserIdHeader: true. That option synthesises a valid session for any request that includes an X-User-Id header — no password, no real token needed. As a result, any HTTP client that adds X-User-Id: anything can create or overwrite product records in Valkey. There is no role === "admin" (or equivalent) check on either handler.

Comment on lines +78 to +87
if (path.startsWith("/api/checkout/start")) return "/api/checkout/start";
if (path.startsWith("/api/auth/login")) return "/api/auth/login";
return "default";
}

function identify(request: Request): { id: string; authenticated: boolean } {
const userId = request.header("X-User-Id");
if (userId) return { id: userId, authenticated: true };
const authorization = request.header("Authorization");
if (authorization) return { id: `session:${authorization.slice(-32)}`, authenticated: true };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Client-controlled X-User-Id bypasses anonymous rate limits

The identify function marks any request that includes an X-User-Id header as authenticated: true and uses the header value directly as the rate-limit bucket key. Because X-User-Id is never validated against an actual session, an anonymous client can: (1) set X-User-Id: anything to receive the higher authenticated quota (e.g. 6 vs. 3 on /api/ratelimit/test, 120 vs. 60 on the default bucket); and (2) DoS a legitimate user's quota by sending requests under that user's well-known ID. The session token or the Authorization header should be the authoritative source for determining authenticated identity.

Comment on lines +55 to +74
return;
}

await client
.multi()
.zadd(key, now, `${now}:${Math.random().toString(36).slice(2)}`)
.expire(key, rule.window)
.exec();

response.setHeader("X-RateLimit-Remaining", String(limit - count - 1));
next();
} catch {
next();
}
};
}

export function endpointGroup(path: string): string {
if (path === "/api/ratelimit/test") return path;
if (path === "/api/search/semantic") return path;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Rate-limit check and admission are not atomic

The sliding-window implementation runs two separate multi() pipelines: one to trim old entries and read the current count, and a second to add the new entry and refresh the TTL. In between those two round-trips, concurrent requests on the same key can all observe a count below the limit and all be admitted. The correct pattern is a single Lua script (or a single pipeline that includes ZADD and re-reads the count) so the check and the increment are atomic.

Comment on lines +1090 to +1140
response.setHeader("Connection", "keep-alive");
response.flushHeaders?.();

let active = true;
let lastId = "$";
request.on("close", () => {
active = false;
});

while (active) {
const rows = (await context.client.xread(
"BLOCK",
1000,
"STREAMS",
ORDER_STREAM_KEY,
lastId
)) as Array<[string, Array<[string, string[]]>]> | null;

if (!rows) {
continue;
}

for (const [, entries] of rows) {
for (const [entryId, fields] of entries) {
lastId = entryId;
const event = Object.fromEntries(
Array.from({ length: fields.length / 2 }, (_, index) => [
fields[index * 2],
fields[index * 2 + 1],
])
);
if (event.orderId === request.params.id) {
response.write(`id: ${entryId}\n`);
response.write(`event: order\n`);
response.write(`data: ${JSON.stringify(event)}\n\n`);
}
}
}
}

response.end();
} catch (error) {
next(error);
}
});

app.use((error: unknown, request: TraceRequest, response: Response, _next: express.NextFunction) => {
const apiError = toApiError(error);
void recordLog(context.client, {
level: apiError.status >= 500 ? "error" : "warn",
event: "api_error",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 SSE order-events endpoint scans the entire global order stream per connection

The XREAD BLOCK call at line 1106 reads every new entry written to ORDER_STREAM_KEY for the lifetime of the SSE connection and then discards entries that don't match request.params.id. Under concurrent connections, each connection independently processes the full event fan-out. A per-order consumer group or a dedicated per-order list/channel (e.g. XREAD on a per-order stream key like stream:orders:{orderId}) would avoid the fan-out overhead and make the filter exact at the Valkey level.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@rlunar rlunar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Vasanthadithya-mundrathi please review all comments made by Greptile and address them to be able to merge and publish on valkey-io org.

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.

4 participants