Skip to content

Convenor Routes updated Dev Routes corrected and tested #16

Merged
Debatreya merged 8 commits intoDebatreya:masterfrom
Agrawal-Vansh:master
Mar 1, 2026
Merged

Convenor Routes updated Dev Routes corrected and tested #16
Debatreya merged 8 commits intoDebatreya:masterfrom
Agrawal-Vansh:master

Conversation

@Agrawal-Vansh
Copy link
Copy Markdown

@Agrawal-Vansh Agrawal-Vansh commented Jan 23, 2026

Summary by CodeRabbit

  • New Features

    • Fetch, update, and delete individual convenor profiles; add or edit a single co-convenor.
  • Changes

    • Replaced bulk co-convenor replacement with per-co-convenor add/edit flows; tech now derived automatically.
    • ADD operations no longer move entries to history; EDIT does not alter history.
    • Response shapes standardized (consistent field ordering, top-level society/tech).
    • FAQ and developer payload/response fields renamed and formatting improved.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 23, 2026

📝 Walkthrough

Walkthrough

Refactors convenor/co-convenor APIs from bulk replace to per-co-convenor add/edit via PATCH, removes the bulk /co-convenors/all endpoint, introduces formatter utilities and DTOs, updates models and GET/PATCH/DELETE handlers across convenors, developers, and FAQ routes, and adjusts request/response contracts and schemas.

Changes

Cohort / File(s) Summary
README / Docs
README.md
Update to reflect endpoint change (POST /co-convenors/all → PATCH /co-convenors), ADD vs EDIT request/response shapes, and removal of client-supplied tech.
Convenor single resource
app/api/convenors/[id]/route.ts
Added GET handler; PATCH for profile updates (name,imgurl,password with hashing) restricted to ADMIN or same convenor; DELETE to remove convenor; changed handler params shape.
Co-convenor collection
app/api/convenors/co-convenors/route.ts
POST now replaces co-convenors array with validation and tech derived from current convenor; new PATCH supports single co-convenor ADD (append) or EDIT (by coConvenorId) with derived tech.
Removed bulk endpoint
app/api/convenors/co-convenors/all/route.ts
File deleted — removes previous POST bulk-replace flow that moved current co-convenors to history and accepted client tech.
Convenors listing
app/api/convenors/route.ts
Refactored GET to use new formatSocietyConvenors formatter, consolidated history inclusion logic and response shape.
Convenor formatters & DTOs
lib/formatters/convenor.ts, types/dto/convenor.ts
New formatter utilities (groupByTechSorted, formatConvenor/CoConvenor/Histories, formatSocietyConvenors) and new DTO interfaces for convenor/co-convenor responses.
Society model changes
lib/models/Society.ts
Added auto-generated _id (ObjectId) to currentCoConvenors subdocuments to expose identifiers.
Developer endpoints & DTOs
app/api/developers/route.ts, app/api/developers/[id]/route.ts, types/dto/developer.ts
Switched to ID-based DB ops (findById, findByIdAndUpdate/Delete); formatDeveloper returns id (no _id); removed id from DeveloperPayload; relaxed POST validation and duplicate-id checks.
Developer model
lib/models/Developer.ts
Rewrote schema to Schema<IDeveloper> with explicit fields, removed public id field, added trimming and linkedin field, enabled timestamps.
FAQ endpoints, model & DTOs
app/api/faq/route.ts, app/api/faq/[id]/route.ts, lib/models/FAQ.ts, types/dto/faq.ts
Introduced formatFAQ helper and ID param unwrapping; GET/PATCH/DELETE signatures updated to use params Promise; POST/PATCH use formatted responses and field renames (faqQuestion, faqAnswer); FAQ model and DTOs updated accordingly.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API as /api/convenors/co-convenors
    participant DB as Database
    participant Formatter as formatSocietyConvenors

    rect rgba(200,100,150,0.5)
    Note over Client,Formatter: ADD/EDIT single co-convenor via PATCH

    Client->>API: PATCH { societyName, coConvenor / coConvenorId? }
    activate API

    API->>DB: Find society by name (populate currentConvenor)
    activate DB
    DB-->>API: society + currentConvenor
    deactivate DB

    alt coConvenorId provided
        API->>DB: Update existing co-convenor in society.currentCoConvenors
        DB-->>API: Updated society
    else no coConvenorId
        API->>DB: Append new co-convenor to society.currentCoConvenors (derive tech)
        DB-->>API: Saved society
    end

    API->>Formatter: formatSocietyConvenors(society, currentConvenor, includeHistory)
    activate Formatter
    Formatter-->>API: SocietyConvenorsResponse
    deactivate Formatter

    API-->>Client: { success: true, message, society, tech, coConvenors: [...] }
    deactivate API
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Added developers APIs #15 — Modifies developers API routes and developer DTOs (same files updated here).
  • Feat/faq #13 — Touches FAQ endpoints, FAQ model, and FAQ DTOs overlapping with these changes.

Poem

🐰 Hop-hop, a PATCH in the glen,
Tech now gleaned from convenor when,
Co-convenors carry _id bright,
Formatters stitch responses right,
I nibble DTO carrots in delight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The PR title is vague and covers multiple distinct changes (convenor routes, developer routes, FAQ routes, and type definitions) without highlighting the most important change. Terms like 'updated' and 'corrected' lack specificity. Provide a more specific title that captures the primary intent, such as 'Refactor convenor API to use PATCH for add/edit operations' or 'Consolidate API route patterns and add comprehensive formatting utilities'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 84.62% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@app/api/convenors/`[id]/route.ts:
- Around line 166-186: The deletion code calls User.findByIdAndDelete(id)
without verifying the convenor isn't referenced by any
Society.currentConvenor.userId; before deleting (in the handler that uses
User.findById, User.findByIdAndDelete), query the Society model for any
documents where currentConvenor.userId equals the convenor id and either return
a 400/409 error preventing deletion or update those Society records to
clear/replace currentConvenor (e.g., set currentConvenor=null or assign a new
convenor) and save before calling User.findByIdAndDelete; reference the
User.findById, User.findByIdAndDelete calls and the
Society.currentConvenor.userId field when making the changes.

In `@app/api/developers/`[id]/route.ts:
- Around line 44-57: Update the GET, DELETE, and PATCH route handlers to accept
params as a Promise and await it before use: change the signature type from {
params }: { params: { id: string } } to { params }: { params: Promise<{ id:
string }> } in the GET, DELETE, and PATCH functions, then do const { id } =
await params at the top of each handler and replace all usages of params.id with
id; ensure this change is applied inside the GET, DELETE, and PATCH functions in
route.ts (e.g., the exported GET function and its DELETE/PATCH counterparts).

In `@lib/formatters/convenor.ts`:
- Around line 22-33: The formatConvenor function currently ignores its tech
parameter and reads user.tech instead; update formatConvenor to use the
passed-in tech value (falling back to user.tech and then 0) for the returned
ConvenorDTO's tech field so callers' society.currentConvenor.tech is
respected—change the tech assignment in formatConvenor to prefer the function
parameter (e.g., tech ?? user.tech ?? 0) while keeping the same function
signature and other fields unchanged.
- Around line 10-20: The function groupByTechSorted currently only groups items
but doesn't perform the promised descending sort; update it to produce groups
ordered by tech descending: after the reduce that builds acc, get the tech keys
(Object.keys(acc).map(Number)), sort them descending (sort((a,b) => b - a)),
then build and return a new Record by iterating the sorted keys and assigning
newAcc[tech] = acc[tech]; ensure you reference the existing function name
groupByTechSorted and variables acc/items so the change is applied in that
function.
🧹 Nitpick comments (9)
app/api/convenors/route.ts (2)

54-59: Use proper type assertion instead of as any.

The as any cast bypasses type safety. Since SOCIETY_NAMES is an enum, cast to the enum type directly.

♻️ Suggested fix
-    if (!Object.values(SOCIETY_NAMES).includes(societyName as any)) {
+    if (!Object.values(SOCIETY_NAMES).includes(societyName as SOCIETY_NAMES)) {

You'll also need to import or reference SOCIETY_NAMES as a type in the check, or use a type guard:

function isValidSocietyName(name: string): name is SOCIETY_NAMES {
  return Object.values(SOCIETY_NAMES).includes(name as SOCIETY_NAMES);
}

45-51: Inconsistent indentation.

The return block has extra indentation compared to the similar block at lines 84-90. Consider aligning for consistency.

♻️ Suggested fix
-          return NextResponse.json(
-            {
-              success: true,
-              data,
-            },
-            { status: 200 }
-          );
+    return NextResponse.json(
+      {
+        success: true,
+        data,
+      },
+      { status: 200 }
+    );
app/api/developers/[id]/route.ts (2)

20-41: Consider typing the doc parameter more specifically.

Using any defeats TypeScript's type safety. The document could be typed using Mongoose's document type.

♻️ Suggested fix
-function formatDeveloper(doc: any): DeveloperResponse {
+import type { IDeveloper } from '@/lib/models/Developer';
+import type { Document, Types } from 'mongoose';
+
+type DeveloperDocument = Document<Types.ObjectId, {}, IDeveloper> & IDeveloper;
+
+function formatDeveloper(doc: DeveloperDocument | Record<string, unknown>): DeveloperResponse {

Alternatively, a simpler approach if you need flexibility:

function formatDeveloper(doc: { toObject?: () => Record<string, unknown> } & Record<string, unknown>): DeveloperResponse {

146-150: Truthy checks prevent clearing optional fields.

Using if (developer.name) ignores empty strings. If a user wants to clear a social link (e.g., set github to ""), this logic would skip the update.

Consider checking for undefined explicitly if clearing is allowed:

♻️ Suggested fix (if clearing should be allowed)
-    if (developer.name) updates.name = developer.name;
-    if (developer.imgURL) updates.imgURL = developer.imgURL;
-    if (developer.github) updates.github = developer.github;
-    if (developer.insta) updates.insta = developer.insta;
-    if (developer.linkedin) updates.linkedin = developer.linkedin;
+    if (developer.name !== undefined) updates.name = developer.name;
+    if (developer.imgURL !== undefined) updates.imgURL = developer.imgURL;
+    if (developer.github !== undefined) updates.github = developer.github;
+    if (developer.insta !== undefined) updates.insta = developer.insta;
+    if (developer.linkedin !== undefined) updates.linkedin = developer.linkedin;

If empty strings should be rejected, consider explicit validation instead of silent skipping.

app/api/developers/route.ts (2)

16-17: Unused VALID_YEARS and VALID_ROLES Sets.

These Sets are defined at lines 16-17 but the POST validation at lines 107-119 uses Object.values().includes() instead. Either use the Sets for consistency (as done in [id]/route.ts PATCH) or remove the unused declarations.

♻️ Suggested fix (use the Sets)
-    if (!Object.values(YEAR_LEVELS).includes(year)) {
+    if (!VALID_YEARS.has(year)) {
       return NextResponse.json(
         { message: 'Invalid year value' },
         { status: 400 }
       );
     }

-    if (!Object.values(DEVELOPER_ROLES).includes(role)) {
+    if (!VALID_ROLES.has(role)) {
       return NextResponse.json(
         { message: 'Invalid role value' },
         { status: 400 }
       );
     }

Also applies to: 107-119


19-40: Duplicate formatDeveloper function.

This function is duplicated in app/api/developers/[id]/route.ts. Consider extracting it to a shared formatter module (e.g., lib/formatters/developer.ts) similar to the formatSocietyConvenors pattern used for convenors.

♻️ Suggested extraction

Create lib/formatters/developer.ts:

import type { DeveloperResponse } from '@/types/dto/developer';

export function formatDeveloper(doc: any): DeveloperResponse {
  const obj = doc?.toObject ? doc.toObject() : doc;

  return {
    id: obj._id?.toString?.() ?? obj._id,
    name: obj.name,
    imgURL: obj.imgURL,
    year: obj.year,
    role: obj.role,
    github: obj.github,
    insta: obj.insta,
    linkedin: obj.linkedin,
    createdAt:
      obj.createdAt instanceof Date
        ? obj.createdAt.toISOString()
        : obj.createdAt,
    updatedAt:
      obj.updatedAt instanceof Date
        ? obj.updatedAt.toISOString()
        : obj.updatedAt,
  };
}

Then import in both route files:

import { formatDeveloper } from '@/lib/formatters/developer';
app/api/convenors/co-convenors/route.ts (2)

46-62: Replace any types with a proper interface.

The static analysis correctly flags the any types. Define an interface matching the subdocument shape.

Proposed fix
+interface CoConvenorSubdoc {
+  name: string;
+  imgurl: string;
+  tech: number;
+}

-    society.currentCoConvenors.forEach((cc: { name: any; imgurl: any; tech: any; }) => {
+    society.currentCoConvenors.forEach((cc: CoConvenorSubdoc) => {

96-108: Consider validating coConvenor object exists before destructuring.

If coConvenor is undefined, destructuring { name, imgurl } yields undefined values which then fail validation. This works but could provide a clearer error message.

Proposed improvement
     const { societyName, coConvenorId, coConvenor } = await req.json();
+
+    if (!coConvenor || typeof coConvenor !== 'object') {
+      return NextResponse.json(
+        { message: 'coConvenor object is required' },
+        { status: 400 }
+      );
+    }
+
     const { name, imgurl } = coConvenor || {};
app/api/convenors/[id]/route.ts (1)

7-14: Unused import ConvenorActionResponse.

ConvenorActionResponse is imported but never used in this file.

Proposed fix
 import type {
   GetSingleConvenorResponse,
-  ConvenorActionResponse,
   ConvenorErrorResponse,
 } from '@/types/dto/convenor';

Comment on lines +166 to +186
try {
const { id } = await context.params; // ✅ FIX

await connectDB();

const convenor = await User.findById(id);

if (!convenor || convenor.role !== 'CONVENOR') {
return NextResponse.json(
{ message: 'Convenor not found' },
{ status: 404 }
);
}

// ❗ Preserve history – only remove user
await User.findByIdAndDelete(id);

return NextResponse.json(
{ message: 'Convenor deleted successfully' },
{ status: 200 }
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

rg -n "currentConvenor" --type ts -C 3

Repository: Debatreya/AdminPanel

Length of output: 6984


🏁 Script executed:

# Let's also search for the Society model definition
rg -n "interface Society|type Society|class Society" --type ts -C 5

Repository: Debatreya/AdminPanel

Length of output: 549


🏁 Script executed:

# Check for any cleanup/cascading logic when users are deleted
rg -n "findByIdAndDelete|deleteOne|deleteMany" --type ts -C 3

Repository: Debatreya/AdminPanel

Length of output: 1276


🏁 Script executed:

# Look for any validation when deleting convenors
fd -e ts -e js | xargs grep -l "Society" | head -20

Repository: Debatreya/AdminPanel

Length of output: 434


🏁 Script executed:

# Get the full DELETE endpoint context around line 166-186
sed -n '150,200p' app/api/convenors/[id]/route.ts

Repository: Debatreya/AdminPanel

Length of output: 1031


🏁 Script executed:

# Check User model for any delete hooks or validation
cat lib/models/index.ts

Repository: Debatreya/AdminPanel

Length of output: 934


🏁 Script executed:

# Look at the complete User model
fd -name "User.ts" --type f

Repository: Debatreya/AdminPanel

Length of output: 294


🏁 Script executed:

# Find User model file
fd User.ts

Repository: Debatreya/AdminPanel

Length of output: 82


🏁 Script executed:

# Check if there are any pre-delete hooks or validation in User model
rg -n "pre\(|post\(|findByIdAndDelete" lib/models/User.ts -C 5

Repository: Debatreya/AdminPanel

Length of output: 46


🏁 Script executed:

# Read the User model to see its complete definition
cat lib/models/User.ts

Repository: Debatreya/AdminPanel

Length of output: 1148


Validate convenor is not the active convenor of any society before deletion.

Deleting the user will leave Society.currentConvenor.userId as an orphaned reference if this convenor is currently assigned to any society. This breaks:

  • Authorization middleware that calls .equals() on currentConvenor.userId (line 67 of convenorAuth.ts)
  • Populate queries that will return null for the user
  • Data consistency

Either check that the convenor is not assigned as currentConvenor in any society before deletion, or update all societies where they are the current convenor before removing the user.

🤖 Prompt for AI Agents
In `@app/api/convenors/`[id]/route.ts around lines 166 - 186, The deletion code
calls User.findByIdAndDelete(id) without verifying the convenor isn't referenced
by any Society.currentConvenor.userId; before deleting (in the handler that uses
User.findById, User.findByIdAndDelete), query the Society model for any
documents where currentConvenor.userId equals the convenor id and either return
a 400/409 error preventing deletion or update those Society records to
clear/replace currentConvenor (e.g., set currentConvenor=null or assign a new
convenor) and save before calling User.findByIdAndDelete; reference the
User.findById, User.findByIdAndDelete calls and the
Society.currentConvenor.userId field when making the changes.

Comment on lines 44 to +57
export async function GET(
_request: Request,
{ params }: { params: { id: string } }
) {
try {
await connectDB()
await connectDB();

const developer = await findDeveloper(params.id)
const developer = await Developer.findById(params.id);

if (!developer) {
return NextResponse.json<DeveloperErrorResponse>(
{ message: 'Developer not found' },
{ status: 404 }
)
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Next.js 15 route handler params async change

💡 Result:

In Next.js 15, Route Handler params became async: the route handler context now provides params as a Promise, so you must await it before reading properties. This matches the broader Next.js 15 change where route params are now Promises. [1][2][3]

Before (Next.js 14 and earlier)

type Params = { slug: string }

export async function GET(_req: Request, ctx: { params: Params }) {
  const slug = ctx.params.slug
}

After (Next.js 15)

type Params = Promise<{ slug: string }>

export async function GET(_req: Request, ctx: { params: Params }) {
  const { slug } = await ctx.params
}

[1][2]

Typed helper (optional)

Next.js 15 also documents using RouteContext for strongly typed params (still awaited):

export async function GET(_req: Request, ctx: RouteContext<'/users/[id]'>) {
  const { id } = await ctx.params
  return Response.json({ id })
}

[2]

If you’re upgrading, the Next.js 15 upgrade guide includes this exact migration pattern for Route Handlers. [1]

Sources: Next.js v15 upgrade guide (Route Handlers) [1], Route Handlers docs [2], Next.js error explanation for async params [3].


🏁 Script executed:

# Check Next.js version in the project
if [ -f "package.json" ]; then
  echo "=== package.json Next.js dependency ==="
  grep -A 2 "\"next\"" package.json || echo "Next.js not found in dependencies"
fi

# Check the actual file to understand the params usage
echo -e "\n=== File: app/api/developers/[id]/route.ts ==="
if [ -f "app/api/developers/[id]/route.ts" ]; then
  wc -l "app/api/developers/[id]/route.ts"
fi

Repository: Debatreya/AdminPanel

Length of output: 260


🏁 Script executed:

# Read the full file to see all params usages and function signatures
if [ -f "app/api/developers/[id]/route.ts" ]; then
  cat -n "app/api/developers/[id]/route.ts"
fi

Repository: Debatreya/AdminPanel

Length of output: 6842


Update route handlers to await params for Next.js 16 compatibility.

Next.js 16 requires params to be awaited in route handlers. Update the type annotations and all three route handlers (GET, DELETE, PATCH) to await params before accessing properties:

export async function GET(
  _request: Request,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  // ... use id instead of params.id
}

This affects lines 46 (GET), 79 (DELETE), and 116 (PATCH) - change { params }: { params: { id: string } } to { params }: { params: Promise<{ id: string }> } and update all uses of params.id to id after destructuring from the awaited params object.

🤖 Prompt for AI Agents
In `@app/api/developers/`[id]/route.ts around lines 44 - 57, Update the GET,
DELETE, and PATCH route handlers to accept params as a Promise and await it
before use: change the signature type from { params }: { params: { id: string }
} to { params }: { params: Promise<{ id: string }> } in the GET, DELETE, and
PATCH functions, then do const { id } = await params at the top of each handler
and replace all usages of params.id with id; ensure this change is applied
inside the GET, DELETE, and PATCH functions in route.ts (e.g., the exported GET
function and its DELETE/PATCH counterparts).

Comment on lines +10 to +20
// Groups entries by tech year and sorts descending

export function groupByTechSorted<T extends { tech: number }>(
items: T[]
): Record<number, T[]> {
return items.reduce<Record<number, T[]>>((acc, item) => {
if (!acc[item.tech]) acc[item.tech] = [];
acc[item.tech].push(item);
return acc;
}, {});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Comment claims sorting but no sorting is implemented.

The comment says "sorts descending" but groupByTechSorted only groups by tech without any sorting. Either implement sorting or fix the comment.

Option 1: Fix the comment
-//  Groups entries by tech year and sorts descending
+//  Groups entries by tech year
Option 2: Implement sorting
 export function groupByTechSorted<T extends { tech: number }>(
   items: T[]
 ): Record<number, T[]> {
-  return items.reduce<Record<number, T[]>>((acc, item) => {
+  const grouped = items.reduce<Record<number, T[]>>((acc, item) => {
     if (!acc[item.tech]) acc[item.tech] = [];
     acc[item.tech].push(item);
     return acc;
   }, {});
+  // Sort keys descending and reconstruct object
+  return Object.keys(grouped)
+    .map(Number)
+    .sort((a, b) => b - a)
+    .reduce<Record<number, T[]>>((acc, key) => {
+      acc[key] = grouped[key];
+      return acc;
+    }, {});
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Groups entries by tech year and sorts descending
export function groupByTechSorted<T extends { tech: number }>(
items: T[]
): Record<number, T[]> {
return items.reduce<Record<number, T[]>>((acc, item) => {
if (!acc[item.tech]) acc[item.tech] = [];
acc[item.tech].push(item);
return acc;
}, {});
}
// Groups entries by tech year
export function groupByTechSorted<T extends { tech: number }>(
items: T[]
): Record<number, T[]> {
return items.reduce<Record<number, T[]>>((acc, item) => {
if (!acc[item.tech]) acc[item.tech] = [];
acc[item.tech].push(item);
return acc;
}, {});
}
Suggested change
// Groups entries by tech year and sorts descending
export function groupByTechSorted<T extends { tech: number }>(
items: T[]
): Record<number, T[]> {
return items.reduce<Record<number, T[]>>((acc, item) => {
if (!acc[item.tech]) acc[item.tech] = [];
acc[item.tech].push(item);
return acc;
}, {});
}
// Groups entries by tech year and sorts descending
export function groupByTechSorted<T extends { tech: number }>(
items: T[]
): Record<number, T[]> {
const grouped = items.reduce<Record<number, T[]>>((acc, item) => {
if (!acc[item.tech]) acc[item.tech] = [];
acc[item.tech].push(item);
return acc;
}, {});
// Sort keys descending and reconstruct object
return Object.keys(grouped)
.map(Number)
.sort((a, b) => b - a)
.reduce<Record<number, T[]>>((acc, key) => {
acc[key] = grouped[key];
return acc;
}, {});
}
🤖 Prompt for AI Agents
In `@lib/formatters/convenor.ts` around lines 10 - 20, The function
groupByTechSorted currently only groups items but doesn't perform the promised
descending sort; update it to produce groups ordered by tech descending: after
the reduce that builds acc, get the tech keys (Object.keys(acc).map(Number)),
sort them descending (sort((a,b) => b - a)), then build and return a new Record
by iterating the sorted keys and assigning newAcc[tech] = acc[tech]; ensure you
reference the existing function name groupByTechSorted and variables acc/items
so the change is applied in that function.

Comment on lines +22 to +33
// Formats active convenor (User + tech)
export function formatConvenor(
user: any,
tech: number
): ConvenorDTO {
return {
id: user._id.toString(),
name: user.name,
imgurl: user.imgurl,
tech: user.tech??0,
};
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unused tech parameter - function uses user.tech instead.

The tech parameter is never used; the return value uses user.tech ?? 0. This is likely a bug since callers pass society.currentConvenor.tech expecting it to be used.

Proposed fix
 export function formatConvenor(
   user: any,
   tech: number
 ): ConvenorDTO {
   return {
     id: user._id.toString(),
     name: user.name,
     imgurl: user.imgurl,
-    tech: user.tech??0,
+    tech,
   };
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Formats active convenor (User + tech)
export function formatConvenor(
user: any,
tech: number
): ConvenorDTO {
return {
id: user._id.toString(),
name: user.name,
imgurl: user.imgurl,
tech: user.tech??0,
};
}
// Formats active convenor (User + tech)
export function formatConvenor(
user: any,
tech: number
): ConvenorDTO {
return {
id: user._id.toString(),
name: user.name,
imgurl: user.imgurl,
tech,
};
}
🧰 Tools
🪛 ESLint

[error] 24-24: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

🤖 Prompt for AI Agents
In `@lib/formatters/convenor.ts` around lines 22 - 33, The formatConvenor function
currently ignores its tech parameter and reads user.tech instead; update
formatConvenor to use the passed-in tech value (falling back to user.tech and
then 0) for the returned ConvenorDTO's tech field so callers'
society.currentConvenor.tech is respected—change the tech assignment in
formatConvenor to prefer the function parameter (e.g., tech ?? user.tech ?? 0)
while keeping the same function signature and other fields unchanged.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
app/api/faq/route.ts (1)

15-35: Extract formatFAQ to a shared module and add proper typing.

The formatFAQ function is duplicated in both app/api/faq/route.ts and app/api/faq/[id]/route.ts. The comment acknowledges this should be moved to lib/formatters/faq.ts. Additionally, the any type should be replaced with proper typing for better type safety.

♻️ Proposed refactor to lib/formatters/faq.ts
// lib/formatters/faq.ts
import type { IFAQ } from '@/lib/models/FAQ';
import type { FAQResponse } from '@/types/dto/faq';

export function formatFAQ(doc: IFAQ): FAQResponse {
  const obj = doc.toObject ? doc.toObject() : doc;

  return {
    _id: obj._id.toString(),
    faqQuestion: obj.faqQuestion,
    faqAnswer: obj.faqAnswer,
    createdAt:
      obj.createdAt instanceof Date
        ? obj.createdAt.toISOString()
        : String(obj.createdAt),
    updatedAt:
      obj.updatedAt instanceof Date
        ? obj.updatedAt.toISOString()
        : String(obj.updatedAt),
  };
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/faq/route.ts` around lines 15 - 35, Move the duplicated formatFAQ
into a shared module (create lib/formatters/faq.ts) and replace the loose any
with proper types: change the function signature to formatFAQ(doc: IFAQ):
FAQResponse (import IFAQ from '@/lib/models/FAQ' and FAQResponse from
'@/types/dto/faq'), keep the toObject check but ensure createdAt/updatedAt are
coerced to strings (use String(...) when not Date), export the function, and
update both app/api/faq/route.ts and app/api/faq/[id]/route.ts to import and use
this single exported formatFAQ while removing the local duplicates.
app/api/faq/[id]/route.ts (1)

17-43: Same formatFAQ duplication applies here.

This is the same formatter duplicated from app/api/faq/route.ts. Once extracted to a shared module (as suggested in the other file), import it here instead.

The getId helper is a good pattern for handling Next.js 15's async params.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/faq/`[id]/route.ts around lines 17 - 43, Remove the duplicated local
formatter by deleting the local formatFAQ function and instead import the shared
formatFAQ from the common formatter module you created for the other PR; update
any uses in this file (e.g., responses in the route handlers) to call the
imported formatFAQ, ensuring the imported function accepts the same doc shape
and returns stringified _id/ISO dates as before. Keep the getId helper as-is
(async function getId(params: Promise<{ id: string }>)) since it’s the intended
pattern for Next.js async params. Ensure types/signature match the shared
formatter so no call sites need changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Line 309: The heading "📤 Expected Response Format" is missing the markdown
level-3 prefix; update the README.md by changing that line to a level-3 heading
(prepend "### ") so it matches other response-format headings (e.g., the heading
at line with "###" style) and keeps consistency in the document structure.

---

Nitpick comments:
In `@app/api/faq/`[id]/route.ts:
- Around line 17-43: Remove the duplicated local formatter by deleting the local
formatFAQ function and instead import the shared formatFAQ from the common
formatter module you created for the other PR; update any uses in this file
(e.g., responses in the route handlers) to call the imported formatFAQ, ensuring
the imported function accepts the same doc shape and returns stringified _id/ISO
dates as before. Keep the getId helper as-is (async function getId(params:
Promise<{ id: string }>)) since it’s the intended pattern for Next.js async
params. Ensure types/signature match the shared formatter so no call sites need
changes.

In `@app/api/faq/route.ts`:
- Around line 15-35: Move the duplicated formatFAQ into a shared module (create
lib/formatters/faq.ts) and replace the loose any with proper types: change the
function signature to formatFAQ(doc: IFAQ): FAQResponse (import IFAQ from
'@/lib/models/FAQ' and FAQResponse from '@/types/dto/faq'), keep the toObject
check but ensure createdAt/updatedAt are coerced to strings (use String(...)
when not Date), export the function, and update both app/api/faq/route.ts and
app/api/faq/[id]/route.ts to import and use this single exported formatFAQ while
removing the local duplicates.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fcafc84 and 8ba36ae.

📒 Files selected for processing (5)
  • README.md
  • app/api/faq/[id]/route.ts
  • app/api/faq/route.ts
  • lib/models/FAQ.ts
  • types/dto/faq.ts

Comment thread README.md
</details>

### 📤 Expected Response Format
📤 Expected Response Format
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix markdown heading format.

The heading is missing the ### prefix for consistency with other response format headings in the document (e.g., line 134).

📝 Proposed fix
-📤 Expected Response Format
+### 📤 Expected Response Format
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
📤 Expected Response Format
### 📤 Expected Response Format
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` at line 309, The heading "📤 Expected Response Format" is missing
the markdown level-3 prefix; update the README.md by changing that line to a
level-3 heading (prepend "### ") so it matches other response-format headings
(e.g., the heading at line with "###" style) and keeps consistency in the
document structure.

@Debatreya Debatreya merged commit 163ac97 into Debatreya:master Mar 1, 2026
1 check passed
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.

2 participants