Skip to content

fix: enforce maximum query limit on public doubt board#780

Merged
knoxiboy merged 2 commits into
knoxiboy:mainfrom
anshul23102:fix/755-limit-query
Jul 8, 2026
Merged

fix: enforce maximum query limit on public doubt board#780
knoxiboy merged 2 commits into
knoxiboy:mainfrom
anshul23102:fix/755-limit-query

Conversation

@anshul23102

@anshul23102 anshul23102 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

User description

Prevents resource exhaustion attacks on public doubt board.

  • Add optional max parameter to parsePositiveInt for limit enforcement
  • Enforce max limit of 100 records per query to prevent resource exhaustion
  • Prevents attackers from requesting millions of records in single query

Closes #755

Summary by CodeRabbit

  • Bug Fixes
    • Capped the limit value for the doubts API at 100 to prevent overly large requests and keep results consistent.

CodeAnt-AI Description

Cap public doubt board results to prevent oversized requests

What Changed

  • Public doubt board searches now return at most 100 records, even if a larger limit is requested
  • Requests with invalid or missing limit values still fall back to the default result size

Impact

✅ Fewer oversized doubt board requests
✅ Lower risk of slow public board loads
✅ More reliable load handling on popular subjects

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

@vercel

vercel Bot commented Jul 6, 2026

Copy link
Copy Markdown

@anshul23102 is attempting to deploy a commit to the Karan Mani Tripathi 's projects Team on Vercel.

A member of the Team first needs to authorize it.

@codeant-ai

codeant-ai Bot commented Jul 6, 2026

Copy link
Copy Markdown

CodeAnt AI is reviewing your PR.

@codeant-ai

codeant-ai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The parsePositiveInt utility function is extended with an optional max parameter that caps the returned value when the parsed number exceeds it. The GET /api/doubts route now passes 100 as this new upper bound when parsing the limit query parameter.

Changes

Doubts Query Limit Clamp

Layer / File(s) Summary
parsePositiveInt max clamp and doubts limit enforcement
src/lib/utils/utils.ts, src/app/api/doubts/route.ts
parsePositiveInt accepts an optional max parameter and returns max when the parsed value exceeds it; the doubts GET route now passes 100 as this cap for the limit query parameter.

Estimated code review effort: 1 (Trivial) | ~3 minutes

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant DoubtsRoute as GET /api/doubts
    participant ParsePositiveInt

    Client->>DoubtsRoute: request with limit query param
    DoubtsRoute->>ParsePositiveInt: parsePositiveInt(limitStr, 20, 100)
    ParsePositiveInt-->>ParsePositiveInt: validate, parse, clamp to max
    ParsePositiveInt-->>DoubtsRoute: clamped limit value
    DoubtsRoute-->>Client: doubts list bounded by limit
Loading

Possibly related PRs

  • knoxiboy/DoubtDesk#821: Adopts the same parsePositiveInt utility with a max upper bound applied to admin pagination routes.
  • knoxiboy/DoubtDesk#822: Uses parsePositiveInt with a max cap to limit /api/bookmarks?limit to 100.

Suggested labels: gssoc:approved

Suggested reviewers: knoxiboy

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The route now caps doubt queries at 100, preventing unbounded fetches and addressing the timeout bug.
Out of Scope Changes check ✅ Passed The changes stay focused on limit enforcement and the shared integer parser helper.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: capping public doubt board query limits.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added gssoc'26 GSSoC program issue level:intermediate Intermediate level task type:bug Bug fix review-needed labels Jul 6, 2026
@github-actions
github-actions Bot requested a review from knoxiboy July 6, 2026 06:23
@codeant-ai codeant-ai Bot added the size:XS label Jul 6, 2026
@github-actions github-actions Bot removed the size:XS label Jul 6, 2026
Comment thread src/lib/utils.ts Outdated
Comment on lines 13 to 14
if (!value || !/^d+$/.test(value.trim())) {
return fallback;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The numeric validation regex is misspelled as ^d+$, so normal digit strings like 10 never pass validation and the function always returns the fallback for valid numeric input. Replace it with a digit regex so valid query params are actually parsed. [typo]

Severity Level: Major ⚠️
- ❌ Doubt board pagination ignores user-specified limit values.
- ❌ Notifications pagination ignores page and offset parameters.
- ⚠️ Max limit enforcement relies on hardcoded defaults, not input.
- ⚠️ Client UX degraded; numeric query params have no effect.
Steps of Reproduction ✅
1. The GET handler for notifications at `src/app/api/notifications/route.ts:8-26` reads
`limitStr`, `offsetStr`, and `pageStr` from the URL and calls `parsePositiveInt(limitStr,
50)` and `parsePositiveInt(pageStr, 1)` to derive pagination parameters.

2. The public doubt board GET handler at `src/app/api/doubts/route.ts:17-26` similarly
reads `pageStr`, `offsetStr`, and `limitStr` and calls `parsePositiveInt(limitStr, 20,
100)` and `parsePositiveInt(offsetStr, 0)` / `parsePositiveInt(pageStr, 1)` to compute
`limit`, `offset`, and `page`.

3. Both handlers use the shared utility `parsePositiveInt` implemented at
`src/lib/utils.ts:8-27`, which validates the string with `if (!value ||
!/^d+$/.test(value.trim())) { return fallback; }` (lines 13-14) before converting it with
`Number(value)`.

4. When a client calls either endpoint with a normal numeric query, e.g. `GET
/api/doubts?limit=10&page=3` or `GET /api/notifications?limit=25&page=2`, the string
`"10"` or `"25"` fails the regex `^d+$` at `src/lib/utils.ts:13` (since it matches only
repeated `'d'` characters, not digits), causing `parsePositiveInt` to immediately return
the fallback (20 or 50) instead of the requested value. As a result, user-supplied numeric
pagination parameters are silently ignored and replaced with defaults on every request.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/lib/utils.ts
**Line:** 13:14
**Comment:**
	*Typo: The numeric validation regex is misspelled as `^d+$`, so normal digit strings like `10` never pass validation and the function always returns the fallback for valid numeric input. Replace it with a digit regex so valid query params are actually parsed.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@codeant-ai

codeant-ai Bot commented Jul 6, 2026

Copy link
Copy Markdown

CodeAnt AI finished reviewing your PR.

@anshul23102
anshul23102 force-pushed the fix/755-limit-query branch from d5c19a3 to 5aa7a57 Compare July 6, 2026 06:29
@github-actions github-actions Bot added size/xl and removed size/xs labels Jul 6, 2026
@anshul23102
anshul23102 force-pushed the fix/755-limit-query branch from 5aa7a57 to 80bbed2 Compare July 6, 2026 07:38
@github-actions github-actions Bot added size/l and removed size/xl labels Jul 6, 2026
@codeant-ai

codeant-ai Bot commented Jul 7, 2026

Copy link
Copy Markdown

CodeAnt AI is running Incremental review

@codeant-ai

codeant-ai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai Bot added the size:L label Jul 7, 2026
@github-actions github-actions Bot removed the size:L label Jul 7, 2026
@codeant-ai

codeant-ai Bot commented Jul 7, 2026

Copy link
Copy Markdown

CodeAnt AI Incremental review completed.

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (12)
src/configs/schema.ts (2)

48-48: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Membership role has no DB-level constraint.

role is a free-form varchar(20) with a default of "member", but valid values (e.g., owner, admin, teacher, member) are only enforced in application code (src/app/api/analytics/route.ts line 49 checks ['owner', 'admin', 'teacher'].includes(...)). A DB check constraint or Postgres enum would prevent invalid roles from being inserted directly or via future code paths that skip this app-level check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/configs/schema.ts` at line 48, The Membership `role` field in the schema
is only validated in application code, so add a DB-level constraint in the
schema definition to restrict it to the allowed roles. Update the `role` column
in `src/configs/schema.ts` (and any related migration/schema setup) to use
either a Postgres enum or a check constraint so inserts through any path cannot
store invalid values. Keep the existing default of member and ensure the
constraint matches the role values used by the analytics route and other
membership logic.

34-63: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Consider referencing usersTable.id instead of usersTable.email for membership FK.

organizationMembershipsTable.userEmail is a valid FK target since usersTable.email is unique, but referencing the surrogate primary key (usersTable.id) is more idiomatic and avoids issues if emails are ever updated (email changes would need cascading updates across FK-referencing tables). Also, organizationsTable.ownerEmail has no FK constraint at all, so a stale/nonexistent owner email is possible.

Not blocking — matches existing repo convention (e.g. classroomsTable.teacherEmail) of loosely-typed email references without constraints.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/configs/schema.ts` around lines 34 - 63, Update the schema in
organizationsTable and organizationMembershipsTable to use proper user
references: change organizationMembershipsTable’s user FK to point at
usersTable.id through a userId-style column if needed, rather than
usersTable.email, and add a foreign key for organizationsTable.ownerEmail to the
users table so the owner is constrained too. Keep the existing table definitions
and unique constraints intact, and adjust any related column names or references
in the schema accordingly.
src/app/api/doubts/route.ts (2)

128-128: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

any typing added across map/reduce callbacks.

Same pattern as in src/app/api/analytics/route.ts — these callbacks operate on already-typed Drizzle query results (userBookmarks, userLikes, tagRows, doubts), so casting to any isn't necessary and loses compile-time field checking. Not blocking, but worth reverting where feasible for real typing hardening rather than typing suppression.

Also applies to: 209-210, 222-223, 239-242, 252-252, 263-263

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/api/doubts/route.ts` at line 128, Remove the unnecessary any
annotations from the map/reduce callbacks in the doubts route and rely on the
inferred Drizzle result types instead. Update the callbacks around
userBookmarks, userLikes, tagRows, and doubts to use the existing typed objects
directly so fields like doubtId, userId, and tag data remain compile-time
checked. Keep the logic unchanged in the route handlers and helper expressions,
just restore proper typing in the affected callbacks and reducers.

139-145: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Limit cap verified correct; offset remains unbounded.

The limit cap of 100 correctly addresses the unbounded-query issue. However, offset (line 140-141) has no max and can still be arbitrarily large; a very large OFFSET on doubtsTable can itself be slow on Postgres/Neon even with a capped LIMIT, since the DB must still scan/skip that many rows. Given the linked issue's concern is specifically about avoiding Neon's 5s timeout, consider also capping offset or moving toward the cursor/keyset pagination the issue mentions as a longer-term fix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/api/doubts/route.ts` around lines 139 - 145, The pagination logic in
route.ts still allows an unbounded OFFSET because `offset` is derived directly
from `offsetStr` or `pageStr` without any maximum, so large queries can still
hit Postgres/Neon timeouts even with `limit` capped. Update the pagination flow
around `parsePositiveInt`, `offset`, and `page` to either enforce a maximum
offset or switch the `doubtsTable` query to cursor/keyset pagination, keeping
the existing `limit` cap as-is and ensuring the query cannot request arbitrarily
large skips.
src/app/api/analytics/route.ts (1)

58-58: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Widespread any typing weakens type safety instead of hardening it.

Drizzle's db.select({...}) already returns strongly-typed rows, so callbacks like orgClassrooms.map((c: any) => c.id), userMemberships.map((m: any) => m.classroomId), mostAskedTopics.map((topic: any, index: any) => ...), weakTopics.filter((t: any) => ...), and topContributors.map((c: any) => ...) don't need any. This discards compiler-checked field names/types on the very data the queries were just typed to return.

♻️ Example fix
-            activeClassroomIds = orgClassrooms.map((c: any) => c.id);
+            activeClassroomIds = orgClassrooms.map((c) => c.id);

Also applies to: 70-70, 202-202, 267-268

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/api/analytics/route.ts` at line 58, Remove the unnecessary `any`
annotations in `route.ts` callbacks so the typed query results from
`db.select(...)` keep their inferred field types. Update the array helpers in
`orgClassrooms.map`, `userMemberships.map`, `mostAskedTopics.map`,
`weakTopics.filter`, and `topContributors.map` to use the inferred element types
from those variables instead of `any`, keeping the same logic but allowing
TypeScript to validate property access like `id`, `classroomId`, and `topic`.
src/app/api/admin/moderation/route.ts (1)

40-43: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer inferred/explicit types over any.

flagsPerDay's shape is already known from the preceding db.select({ date, count }) query (Lines 35-38). Annotating the callback parameter as any here (and in the similar patterns across this cohort) discards that inference and the type-checking it provides, rather than just omitting the annotation to let TypeScript infer it, or using typeof flagsPerDay[number].

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/api/admin/moderation/route.ts` around lines 40 - 43, The
`flagsPerDay.map` callback is using `any`, which throws away the inferred shape
from the preceding `db.select({ date, count })` query. Remove the `any`
annotation on the callback parameter (or replace it with the inferred item type
such as `typeof flagsPerDay[number]`) so the `formattedFlagsPerDay` mapping in
`route.ts` keeps TypeScript’s type-checking while preserving the existing
date/count transformation.
src/app/api/rooms/join/route.ts (1)

47-47: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer explicit string typing over any.

allowedEmailDomains is presumably a string[] column; annotating the callback param as any discards that inference instead of typing it as string.

♻️ Suggested fix
-            if (!emailDomain || !classroom.allowedEmailDomains.some((d: any) => emailDomain === d.toLowerCase())) {
+            if (!emailDomain || !classroom.allowedEmailDomains.some((d: string) => emailDomain === d.toLowerCase())) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/api/rooms/join/route.ts` at line 47, The callback in the allowed
email domain check is typed with any, which drops the inferred string type from
allowedEmailDomains. Update the some() callback in the join route to use an
explicit string type for the domain element, and keep the lowercase comparison
logic intact so the check remains type-safe in the join handler.
src/app/api/rooms/members/route.ts (1)

91-95: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

any casts discard the inferred select shape.

members is a well-typed Drizzle result (id, userEmail, role, joinedAt); casting the mapped element to any loses that safety without a corresponding need (destructuring { id, ...m } works fine with the inferred type).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/api/rooms/members/route.ts` around lines 91 - 95, The `any` casts in
the `members.map` branches are unnecessary and discard the inferred Drizzle
select shape. Remove the `any` annotations from the mapped element in the
members mapping logic and rely on the existing inferred types from the `members`
query; keep the `{ id, ...m }` destructuring and the `isOwner` mapping in place
so the result stays type-safe.
src/app/api/teacher/analytics/route.ts (1)

32-33: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

any typing widens previously-inferred DB result types.

teacherMembershipIds/selectedClassroomIds derive from typed Drizzle selects; typing the callback params as any (rather than the actual row shape, e.g. { classroomId: number; role: string } / { id: number; ... }) loses compile-time safety. If this was needed to satisfy let classroomsList = []'s implicit-any inference, annotating that declaration (e.g. let classroomsList: { id: number; name: string; university: string }[] = [];) would be a more targeted fix than casting each callback to any.

Also applies to: 95-95

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/api/teacher/analytics/route.ts` around lines 32 - 33, The `any`
annotations in the `teacherMembershipIds` and `selectedClassroomIds` filter/map
callbacks are widening the typed Drizzle results and removing compile-time
safety. Replace those callback parameter types with the actual row shapes
inferred from the selects, and fix the root cause by typing `classroomsList`
explicitly in the route so it no longer forces `any`. Keep the change localized
to the `route.ts` data-processing code and preserve the existing `.filter(...)`
/ `.map(...)` flow.
src/inngest/functions.ts (1)

233-233: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicate any-typed .map for extracting id.

Both digest functions cast p to any just to read .id, which is already typed on the pending select result (id: pendingNotificationsTable.id). No need to widen the type here.

Also applies to: 317-317

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/inngest/functions.ts` at line 233, The `pending.map((p: any) => p.id)`
usage in the digest functions is unnecessarily widening the type, since the
selected `pending` result already includes `id`; remove the `any` cast and use
the existing inferred item type so `id` is read directly. Update both
occurrences in the digest flow (the `notificationIds` extraction in the related
functions, including the second matching `pending.map` site) and keep the
mapping logic the same while preserving type safety.
src/app/api/teacher/insights/route.ts (1)

102-102: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Intermediate row typing loosened to any.

row/r/t come from typed Drizzle selects ({ topic, subject, unresolvedCount } / { id } / { topic, subject, totalCount }). Casting to any here means a mismatch against the final WeakTopic[] shape (e.g. topic: string | null) wouldn't be caught by the compiler.

Also applies to: 117-123

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/api/teacher/insights/route.ts` at line 102, The intermediate row
typing was loosened to any in the insights route, which hides type mismatches
between the Drizzle select results and the final WeakTopic[] shape. Update the
mapping callbacks in the teacher insights handler (including the
unresolvedPerTopic and related topic rows) to use the inferred select result
types instead of any, so fields like topic, subject, and count remain
compiler-checked. Keep the transformations in the same functions/mappings, but
preserve the typed row shape all the way through to the WeakTopic construction.
src/__tests__/inngest/digest-functions.test.ts (1)

119-119: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Use the supported Inngest test API instead of .fn sendDailyDigest.fn({ step }) reaches into an internal property, so these tests can break on an inngest upgrade. Switch to @inngest/test (InngestTestEngine/t.execute()) or export the handler separately. Same applies to the other .fn call below.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/__tests__/inngest/digest-functions.test.ts` at line 119, The digest tests
are calling the internal `.fn` property on `sendDailyDigest`, which is not
supported and can break on Inngest upgrades. Update these tests to use the
supported `@inngest/test` API via `InngestTestEngine` and `t.execute()`, or
refactor to export the handler separately and invoke that instead. Apply the
same change to both `sendDailyDigest.fn` usages in `digest-functions.test`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/__tests__/inngest/digest-functions.test.ts`:
- Line 119: The digest tests are calling the internal `.fn` property on
`sendDailyDigest`, which is not supported and can break on Inngest upgrades.
Update these tests to use the supported `@inngest/test` API via
`InngestTestEngine` and `t.execute()`, or refactor to export the handler
separately and invoke that instead. Apply the same change to both
`sendDailyDigest.fn` usages in `digest-functions.test`.

In `@src/app/api/admin/moderation/route.ts`:
- Around line 40-43: The `flagsPerDay.map` callback is using `any`, which throws
away the inferred shape from the preceding `db.select({ date, count })` query.
Remove the `any` annotation on the callback parameter (or replace it with the
inferred item type such as `typeof flagsPerDay[number]`) so the
`formattedFlagsPerDay` mapping in `route.ts` keeps TypeScript’s type-checking
while preserving the existing date/count transformation.

In `@src/app/api/analytics/route.ts`:
- Line 58: Remove the unnecessary `any` annotations in `route.ts` callbacks so
the typed query results from `db.select(...)` keep their inferred field types.
Update the array helpers in `orgClassrooms.map`, `userMemberships.map`,
`mostAskedTopics.map`, `weakTopics.filter`, and `topContributors.map` to use the
inferred element types from those variables instead of `any`, keeping the same
logic but allowing TypeScript to validate property access like `id`,
`classroomId`, and `topic`.

In `@src/app/api/doubts/route.ts`:
- Line 128: Remove the unnecessary any annotations from the map/reduce callbacks
in the doubts route and rely on the inferred Drizzle result types instead.
Update the callbacks around userBookmarks, userLikes, tagRows, and doubts to use
the existing typed objects directly so fields like doubtId, userId, and tag data
remain compile-time checked. Keep the logic unchanged in the route handlers and
helper expressions, just restore proper typing in the affected callbacks and
reducers.
- Around line 139-145: The pagination logic in route.ts still allows an
unbounded OFFSET because `offset` is derived directly from `offsetStr` or
`pageStr` without any maximum, so large queries can still hit Postgres/Neon
timeouts even with `limit` capped. Update the pagination flow around
`parsePositiveInt`, `offset`, and `page` to either enforce a maximum offset or
switch the `doubtsTable` query to cursor/keyset pagination, keeping the existing
`limit` cap as-is and ensuring the query cannot request arbitrarily large skips.

In `@src/app/api/rooms/join/route.ts`:
- Line 47: The callback in the allowed email domain check is typed with any,
which drops the inferred string type from allowedEmailDomains. Update the some()
callback in the join route to use an explicit string type for the domain
element, and keep the lowercase comparison logic intact so the check remains
type-safe in the join handler.

In `@src/app/api/rooms/members/route.ts`:
- Around line 91-95: The `any` casts in the `members.map` branches are
unnecessary and discard the inferred Drizzle select shape. Remove the `any`
annotations from the mapped element in the members mapping logic and rely on the
existing inferred types from the `members` query; keep the `{ id, ...m }`
destructuring and the `isOwner` mapping in place so the result stays type-safe.

In `@src/app/api/teacher/analytics/route.ts`:
- Around line 32-33: The `any` annotations in the `teacherMembershipIds` and
`selectedClassroomIds` filter/map callbacks are widening the typed Drizzle
results and removing compile-time safety. Replace those callback parameter types
with the actual row shapes inferred from the selects, and fix the root cause by
typing `classroomsList` explicitly in the route so it no longer forces `any`.
Keep the change localized to the `route.ts` data-processing code and preserve
the existing `.filter(...)` / `.map(...)` flow.

In `@src/app/api/teacher/insights/route.ts`:
- Line 102: The intermediate row typing was loosened to any in the insights
route, which hides type mismatches between the Drizzle select results and the
final WeakTopic[] shape. Update the mapping callbacks in the teacher insights
handler (including the unresolvedPerTopic and related topic rows) to use the
inferred select result types instead of any, so fields like topic, subject, and
count remain compiler-checked. Keep the transformations in the same
functions/mappings, but preserve the typed row shape all the way through to the
WeakTopic construction.

In `@src/configs/schema.ts`:
- Line 48: The Membership `role` field in the schema is only validated in
application code, so add a DB-level constraint in the schema definition to
restrict it to the allowed roles. Update the `role` column in
`src/configs/schema.ts` (and any related migration/schema setup) to use either a
Postgres enum or a check constraint so inserts through any path cannot store
invalid values. Keep the existing default of member and ensure the constraint
matches the role values used by the analytics route and other membership logic.
- Around line 34-63: Update the schema in organizationsTable and
organizationMembershipsTable to use proper user references: change
organizationMembershipsTable’s user FK to point at usersTable.id through a
userId-style column if needed, rather than usersTable.email, and add a foreign
key for organizationsTable.ownerEmail to the users table so the owner is
constrained too. Keep the existing table definitions and unique constraints
intact, and adjust any related column names or references in the schema
accordingly.

In `@src/inngest/functions.ts`:
- Line 233: The `pending.map((p: any) => p.id)` usage in the digest functions is
unnecessarily widening the type, since the selected `pending` result already
includes `id`; remove the `any` cast and use the existing inferred item type so
`id` is read directly. Update both occurrences in the digest flow (the
`notificationIds` extraction in the related functions, including the second
matching `pending.map` site) and keep the mapping logic the same while
preserving type safety.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b7279b17-0440-453c-bfcc-4172ba0d6103

📥 Commits

Reviewing files that changed from the base of the PR and between 956018a and cae03f3.

📒 Files selected for processing (38)
  • drizzle/0017_audit_logs.sql
  • drizzle/0018_silky_gateway.sql
  • drizzle/meta/_journal.json
  • src/__tests__/api/teacher-insights.test.ts
  • src/__tests__/configs/db.test.ts
  • src/__tests__/inngest/digest-functions.test.ts
  • src/app/api/admin/moderation/route.ts
  • src/app/api/admin/overview/route.ts
  • src/app/api/analytics/export/route.ts
  • src/app/api/analytics/personal/route.ts
  • src/app/api/analytics/route.ts
  • src/app/api/bookmarks/route.ts
  • src/app/api/classrooms/[id]/export/route.ts
  • src/app/api/doubts/[id]/upvote/route.ts
  • src/app/api/doubts/action/[id]/route.ts
  • src/app/api/doubts/check-similarity/route.ts
  • src/app/api/doubts/route.ts
  • src/app/api/karma/route.ts
  • src/app/api/organizations/route.ts
  • src/app/api/profile/route.ts
  • src/app/api/recommendations/route.ts
  • src/app/api/replies/vote/route.ts
  • src/app/api/resume-analyzer/history/route.ts
  • src/app/api/roadmap/history/route.ts
  • src/app/api/rooms/join/route.ts
  • src/app/api/rooms/members/route.ts
  • src/app/api/rooms/route.ts
  • src/app/api/teacher/analytics/route.ts
  • src/app/api/teacher/insights/route.ts
  • src/configs/db.tsx
  • src/configs/schema.ts
  • src/inngest/functions.ts
  • src/inngest/karma.ts
  • src/lib/ai/embeddings.ts
  • src/lib/karma-utils.ts
  • src/lib/moderation.ts
  • src/lib/notifications/service.ts
  • src/lib/utils.ts

@anshul23102
anshul23102 force-pushed the fix/755-limit-query branch from cae03f3 to 0f1d2be Compare July 7, 2026 12:49
@github-actions github-actions Bot added size/m and removed size/l labels Jul 7, 2026
@codeant-ai

codeant-ai Bot commented Jul 8, 2026

Copy link
Copy Markdown

CodeAnt AI Incremental review completed.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@jest.setup.ts`:
- Line 89: The Jest mock for "`@/lib/ratelimit/ratelimit`" is missing the
aiDailyLimiter export, so imports from that module can resolve to undefined.
Update the mock in jest.setup.ts to include aiDailyLimiter alongside the other
mocked limiters, using the same mocking pattern as the existing exports so tests
depending on it receive a valid mocked value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 66e71fbb-73ef-4f0b-a923-76f8e71c5f2e

📥 Commits

Reviewing files that changed from the base of the PR and between 0f1d2be and 83cb3ad.

📒 Files selected for processing (9)
  • drizzle/0008_audit_logs.sql
  • drizzle/0013_silky_gateway.sql
  • jest.setup.ts
  • src/__tests__/api/teacher-insights.test.ts
  • src/__tests__/configs/db.test.ts
  • src/__tests__/inngest/digest-functions.test.ts
  • src/app/api/doubts/route.ts
  • src/app/api/invites/[token]/join/route.ts
  • src/lib/utils/utils.ts
💤 Files with no reviewable changes (2)
  • drizzle/0008_audit_logs.sql
  • drizzle/0013_silky_gateway.sql
✅ Files skipped from review due to trivial changes (3)
  • src/app/api/invites/[token]/join/route.ts
  • src/tests/api/teacher-insights.test.ts
  • src/tests/configs/db.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/app/api/doubts/route.ts
  • src/tests/inngest/digest-functions.test.ts

Comment thread jest.setup.ts
}));

jest.mock("@/lib/ratelimit", () => {
jest.mock("@/lib/ratelimit/ratelimit", () => {

@coderabbitai coderabbitai Bot Jul 8, 2026

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect the full mock factory return object in jest.setup.ts
sed -n '89,120p' jest.setup.ts

Repository: knoxiboy/DoubtDesk

Length of output: 799


Add aiDailyLimiter to the Jest mock. The mock currently omits this export, so anything importing it from @/lib/ratelimit/ratelimit will receive undefined.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jest.setup.ts` at line 89, The Jest mock for "`@/lib/ratelimit/ratelimit`" is
missing the aiDailyLimiter export, so imports from that module can resolve to
undefined. Update the mock in jest.setup.ts to include aiDailyLimiter alongside
the other mocked limiters, using the same mocking pattern as the existing
exports so tests depending on it receive a valid mocked value.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@anshul23102 Apply this

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.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -p 'ratelimit' src/lib/ratelimit
cat src/lib/ratelimit/ratelimit.ts 2>/dev/null
rg -n "aiDailyLimiter" -g '*.ts' -g '*.tsx'

Length of output: 5107


@knoxiboy, confirmed — aiDailyLimiter is exported from src/lib/ratelimit/ratelimit.ts but missing from the Jest mock. Here's the update for jest.setup.ts:

jest.mock("`@/lib/ratelimit/ratelimit`", () => {
    const createLimiter = () => ({
        limit: jest.fn().mockResolvedValue({
            success: true,
            limit: 100,
            remaining: 99,
            reset: Date.now() + 60_000,
        }),
    });

    return {
        aiLimiter: createLimiter(),
        aiDailyLimiter: createLimiter(),
        generalLimiter: createLimiter(),
        emailNotificationLimiter: createLimiter(),
        videoLimiter: createLimiter(),
        redisClient: {
            setnx: jest.fn().mockResolvedValue(1),
            del: jest.fn().mockResolvedValue(1),
            expire: jest.fn().mockResolvedValue(1),
        },
    };
});

This adds the aiDailyLimiter mock alongside the existing limiters so any code importing it from @/lib/ratelimit/ratelimit receives a valid mocked value instead of undefined.

Comment thread jest.setup.ts
}));

jest.mock("@/lib/ratelimit", () => {
jest.mock("@/lib/ratelimit/ratelimit", () => {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@anshul23102 Apply this

@knoxiboy

knoxiboy commented Jul 8, 2026

Copy link
Copy Markdown
Owner

@anshul23102 Resolve conflicts

@github-actions github-actions Bot added merge-conflict PR has merge conflicts that need resolution quality : needs-work labels Jul 8, 2026
- Both branches implemented equivalent tag-grouping logic for the
  batched tag lookup; kept upstream's version, which types the reduce
  accumulator directly instead of using inline 'as' casts
- Core fix from this PR (clamping the query limit via
  parsePositiveInt(limitStr, 20, 100)) is unaffected and intact
@codeant-ai

codeant-ai Bot commented Jul 8, 2026

Copy link
Copy Markdown

CodeAnt AI is running Incremental review

@codeant-ai

codeant-ai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@github-actions github-actions Bot added size/xs and removed merge-conflict PR has merge conflicts that need resolution size/s labels Jul 8, 2026
@codeant-ai codeant-ai Bot added the size:XS label Jul 8, 2026
@github-actions github-actions Bot removed the size:XS label Jul 8, 2026
@codeant-ai

codeant-ai Bot commented Jul 8, 2026

Copy link
Copy Markdown

CodeAnt AI Incremental review completed.

@anshul23102

Copy link
Copy Markdown
Contributor Author

@anshul23102 Resolve conflicts

done

anshul23102 added a commit to anshul23102/DoubtDesk that referenced this pull request Jul 8, 2026
- Same tag-grouping reduce block conflict as PR knoxiboy#780: kept upstream's
  cleaner typed version, both were functionally equivalent
- Core fix in this PR (atomic 3-strike moderation via
  handleModerationViolation) is unaffected and merged in cleanly
@knoxiboy
knoxiboy merged commit de5a7d1 into knoxiboy:main Jul 8, 2026
18 of 19 checks passed
@knoxiboy knoxiboy added level:beginner Beginner level task and removed level:intermediate Intermediate level task quality:clean Clean code quality labels Jul 8, 2026
@anshul23102

Copy link
Copy Markdown
Contributor Author

Hey @knoxiboy! 👋

Ready for review on #780: Enforce maximum query limit on public doubt board (issue #755).

This PR fixes a real bug in parsePositiveInt: the regex was /^d+$/ (missing backslash) so it always failed, silently falling back to defaults and completely defeating the limit-clamping logic. Now explicit limit requests are actually honored, and the max is properly enforced instead of ignored.

Suggested GSSoC labels (to help with contribution scoring):

  • gssoc:approved — for GSSoC tracking
  • type:bug-fix — critical bug (silent limit bypass)
  • type:security — rate-limiting bypass fix
  • type:performance — query optimization
  • priority:high — affects public API fairness

This one includes both a bug fix and feature hardening! 🎯

anshul23102 added a commit to anshul23102/DoubtDesk that referenced this pull request Jul 11, 2026
- Add optional max parameter to parsePositiveInt for limit enforcement
- Clamp the doubt board's limit query param to 100 to prevent Neon
  serverless timeouts on popular subjects (issue knoxiboy#755)
- Rebuilt on current upstream/main; main was restructured twice while
  this branch was open (route groups, lib/ moved into subdirectories),
  which is why earlier pushes kept showing as conflicting
- Fixed jest.setup.ts: the restructure moved lib/ratelimit.ts into
  lib/ratelimit/ratelimit.ts but left the jest.mock() path pointing at
  the old bare path, which broke all 38 test suites on main itself
  (confirmed on a clean checkout of upstream/main, unrelated to this
  branch's own change)
- Removed orphan duplicate migration files not tracked in journal.json
- Fixed pre-existing TypeScript errors (untyped reduce/generic calls,
  readonly process.env delete)
- Fixed pre-existing test expectations in teacher-insights.test.ts and
  inngest digest-functions.test.ts to match actual endpoint behavior
- Verified: tsc clean, 193/193 tests pass, production build succeeds

Signed-off-by: Anshul Jain <anshul23102@iiitd.ac.in>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gssoc:approved Approved for GSSoC gssoc'26 GSSoC program issue level:beginner Beginner level task type:bug Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: public doubt board query has no limit clause, Neon serverless connection times out on large tables

2 participants