Skip to content

feat: Implement College Marketplace with Authentication, Media Uploads, and Seller Controls#239

Closed
Rohank3 wants to merge 3 commits intoiiitl:mainfrom
Rohank3:marketplace
Closed

feat: Implement College Marketplace with Authentication, Media Uploads, and Seller Controls#239
Rohank3 wants to merge 3 commits intoiiitl:mainfrom
Rohank3:marketplace

Conversation

@Rohank3
Copy link
Copy Markdown
Contributor

@Rohank3 Rohank3 commented Apr 12, 2026

Resolves #20

Description

What is the purpose of this pull request?
This pull request fully implements the College Marketplace module, replacing the previous dummy UI with a fully functional system aligned with the issue requirements.

Key additions include:

  • Product Listing Schema: Full backend support via Mongoose Product model with fields for title, description, price, Cloudinary image URL, and seller references.
  • Authentication & Authorization: Integrated JWT verification to ensure only authenticated users can create listings, and strictly restricted modification, deletion, and "mark as sold" actions to the original seller (seller === req.user.id).
  • RESTful API Endpoints: Fully implemented endpoints for fetching available products (GET), creating (POST), updating (PUT), marking as sold (PATCH), and deleting (DELETE).
  • Seller Controls: Sellers can manage their inventory, mark items out of stock (with an option to keep a "Sold Out" badge visible or hide it entirely), set bulk discounts, and restore items back to availability.
  • Form Validations: Added complete frontend validation for data integrity (standardized email Regex, required fields constraint, strict price/bulk discount validators).

Live Demo (if any)

image image image image image image image image image image image

Note for Maintainer

  • The Product model was strictly aligned with the requested schema.
  • Added localized state management to manage modal UX seamlessly.
  • Tested endpoints to ensure that 403 Forbidden responses properly fire when an unauthorized user attempts to modify someone else's product.

Checkout

  • I have read all the contributor guidelines for the repo.

Summary by CodeRabbit

  • New Features

    • Product listing management: create, edit, delete listings; search, filter, and pagination.
    • Commenting with replies and optional offers on product pages.
    • Sellers can mark products as sold or make them available again.
  • Chores

    • Cloudinary image hosting support and image validation for uploads.
    • General code formatting, cleanup, and minor middleware adjustments.

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 12, 2026

@Rohank3 is attempting to deploy a commit to the mrimmortal09's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 12, 2026

Walkthrough

Adds a marketplace feature: new Product Mongoose model, five product API route handlers (list/create/update/delete/mark sold/mark available) plus comments, Cloudinary image handling, and middleware/config updates to expose /api/products; several non-functional formatting/whitespace edits elsewhere.

Changes

Cohort / File(s) Summary
Product API Routes
app/api/products/route.ts, app/api/products/[id]/route.ts, app/api/products/[id]/sold/route.ts, app/api/products/[id]/available/route.ts, app/api/products/[id]/comments/route.ts
Added GET/POST for listing and creation (multipart upload → Cloudinary), PUT/DELETE for update/delete, PATCH endpoints to mark sold/available, and POST for comments/replies. Each route performs db connect, ObjectId validation, JWT auth, seller-ownership checks, input validation, error handling, and returns structured JSON.
Product Model
model/Product.ts
New Mongoose model and TypeScript interfaces (IProduct, IComment, IReply) defining fields, validation rules, embedded comments with replies, seller ref, and product state flags (is_sold, show_when_sold). Uses model reuse pattern (`mongoose.models.Product
Cloudinary & Image Config
next.config.ts
Allowed Cloudinary remote pattern (res.cloudinary.com/**) added to Next.js image remotePatterns for image optimization.
Middleware
middleware.ts
Added /api/products to public GET/allowlist endpoints; reformatted a 413 JSON response (no behavioral change).
Formatting / Minor Edits
app/layout.jsx, app/quick-reads/page.tsx, app/upload-notes/page.tsx, app/upload-papers/page.tsx, components/chat/ChatWidget.tsx, components/theme-toggler.tsx, hooks/useSemesterAutofill.ts, hooks/useChatMessages.ts, app/api/chat/messages/route.ts, lib/eventEmitter.ts, model/Message.ts
Whitespace and JSX/TS formatting changes (multi-line JSX, generic formatting, trailing blank-line removals). No logic changes.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant Server as Next.js API
    participant Auth as verifyJwt
    participant Cloudinary
    participant DB as MongoDB

    Client->>Server: POST /api/products (multipart form + image)
    Server->>Auth: verifyJwt(token)
    Auth-->>Server: userId
    Server->>Server: validate fields, image type/size
    Server->>Cloudinary: uploadOnCloudinary(tempFile)
    Cloudinary-->>Server: image_url
    Server->>DB: Product.create({...seller: userId, image_url})
    DB-->>Server: created product
    Server-->>Client: 201 { message, product }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

accepted-75

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main objective: implementing a complete college marketplace with authentication, media uploads, and seller controls, which is precisely what the changeset delivers.
Linked Issues check ✅ Passed All coding requirements from issue #20 are satisfied: Product model with required fields, JWT authentication, Cloudinary integration, validation/error handling, and all five RESTful endpoints (GET, POST, PUT, PATCH, DELETE) with proper authorization checks.
Out of Scope Changes check ✅ Passed All changes are scope-aligned with issue #20. Formatting improvements (JSX/hook call refactoring) are minor housekeeping. Middleware updates and next.config additions directly support the marketplace features. No unrelated functionality was introduced.

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

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

Copy link
Copy Markdown
Contributor

@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 (9)
app/api/products/[id]/route.ts (2)

104-113: Consider adding maxlength validation for contact_info.

Similar to description, contact_info has a 200-character schema limit that isn't validated at the route level.

♻️ Proposed fix
 if (contact_info !== undefined) {
   const trimmed = String(contact_info).trim()
   if (!trimmed) {
     return NextResponse.json(
       { message: 'Contact info cannot be empty' },
       { status: 400 }
     )
   }
+  if (trimmed.length > 200) {
+    return NextResponse.json(
+      { message: 'Contact info cannot exceed 200 characters' },
+      { status: 400 }
+    )
+  }
   updateData.contact_info = trimmed
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/products/`[id]/route.ts around lines 104 - 113, The route handles
contact_info but doesn't enforce the 200-character schema limit; update the
validation in the contact_info block (where contact_info is trimmed and assigned
to updateData.contact_info) to check trimmed.length <= 200 and return a 400
NextResponse.json with a clear message (e.g., "Contact info must be 200
characters or fewer") when the length exceeds 200, mirroring the description
maxlength validation behavior.

82-91: Consider adding maxlength validation for description.

The title field validates max 40 characters at the route level (line 73), but description doesn't validate its 1000-character limit. While the schema validator will catch this (via runValidators: true), consistent early validation provides clearer error messages.

♻️ Proposed fix
 if (description !== undefined) {
   const trimmed = String(description).trim()
   if (!trimmed) {
     return NextResponse.json(
       { message: 'Description cannot be empty' },
       { status: 400 }
     )
   }
+  if (trimmed.length > 1000) {
+    return NextResponse.json(
+      { message: 'Description cannot exceed 1000 characters' },
+      { status: 400 }
+    )
+  }
   updateData.description = trimmed
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/products/`[id]/route.ts around lines 82 - 91, Add a max-length check
for the incoming description in the request handler so it mirrors the title
validation: when description !== undefined, after trimming (the existing trimmed
variable) verify trimmed.length <= 1000 and return NextResponse.json({ message:
'Description cannot exceed 1000 characters' }, { status: 400 }) if it’s too
long; otherwise assign updateData.description = trimmed. Target the block
handling description in route.ts (the trimmed variable / updateData.description)
to implement this early validation.
app/api/products/[id]/comments/route.ts (2)

4-6: Remove unused imports.

User and IComment are imported but never used. Static analysis confirms this.

♻️ Proposed fix
-import Product, { IComment } from '@/model/Product'
-import User from '@/model/User'
+import Product from '@/model/Product'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/products/`[id]/comments/route.ts around lines 4 - 6, Remove the
unused imports to clean up the module: delete the separate import of User
(import User from '@/model/User') and remove IComment from the named imports on
the Product import (import Product, { IComment } from '@/model/Product'); leave
only the symbols actually used (e.g., Product) and keep other existing imports
like verifyJwt intact, or convert any needed type-only imports to a "type"
import if later required.

73-76: Replace any with proper typing.

The static analysis flagged multiple any usages. Use Mongoose's subdocument types.

♻️ Proposed fix
+import { Types } from 'mongoose'
+
 const parentComment = product.comments.find(
-  (c: any) => c._id && c._id.toString() === parentCommentId
-) as any
+  (c) => c._id && c._id.toString() === parentCommentId
+)
 if (!parentComment) {

For line 96, Mongoose subdocuments accept the object shape without explicit casting when the schema matches.

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

In `@app/api/products/`[id]/comments/route.ts around lines 73 - 76, The find call
uses broad any types for product.comments and parentComment which defeats type
safety; replace these anys by importing and using the Mongoose/Schema types for
your product and comment subdocument (e.g., ProductDocument / CommentSubdocument
or your defined Comment interface), type product.comments as Comment[] (or
Types.DocumentArray<CommentSubdocument>), remove the "as any" cast, and declare
parentComment with the correct type (e.g., CommentSubdocument | undefined);
update the predicate to use optional chaining (c._id?.toString() ===
parentCommentId) so the compiler can infer types correctly.
app/api/products/[id]/sold/route.ts (1)

76-93: Potential race condition on quantity decrement.

The read-check-modify-save pattern between lines 76-93 is not atomic. Two concurrent requests could both pass the sold_quantity > product.quantity check and oversell.

For a college marketplace with low traffic, this risk is minimal, but consider using an atomic update if inventory accuracy is important.

♻️ Atomic update approach
// Use findOneAndUpdate with conditions for atomicity
const result = await Product.findOneAndUpdate(
  { 
    _id: id, 
    seller: userId,
    quantity: { $gte: sold_quantity }
  },
  { 
    $inc: { quantity: -sold_quantity },
  },
  { new: true }
)

if (!result) {
  // Either not found, not owner, or insufficient quantity
  return NextResponse.json(
    { message: 'Cannot complete sale - product unavailable or insufficient quantity' },
    { status: 400 }
  )
}

// Then handle is_sold flag update if quantity reached 0
if (result.quantity <= 0) {
  result.is_sold = true
  result.show_when_sold = show_when_sold
  result.quantity = 0
  await result.save()
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/products/`[id]/sold/route.ts around lines 76 - 93, The current
read-check-modify-save flow around product.quantity and product.save() can race
and oversell; replace it with an atomic update using Product.findOneAndUpdate
where the query includes {_id: id, seller: userId, quantity: { $gte:
sold_quantity }} and the update uses $inc: { quantity: -sold_quantity } to
ensure the decrement only occurs if enough stock exists, then check if the
returned document is null (meaning not found/insufficient qty) and return the
400 response, and if result.quantity <= 0 set result.is_sold and
result.show_when_sold and result.quantity = 0 then persist (result.save()).
model/Product.ts (2)

92-99: Consider adding a compound index for visibility queries.

The GET endpoint filters products using { is_sold, show_when_sold }. A compound index would improve query performance as the collection grows.

📊 Suggested index
 is_sold: {
   type: Boolean,
   default: false,
+  index: true,
 },
 show_when_sold: {
   type: Boolean,
   default: false,
 },

Alternatively, add a compound index at the schema level:

ProductSchema.index({ is_sold: 1, show_when_sold: 1 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@model/Product.ts` around lines 92 - 99, The schema lacks a compound index for
the visibility fields used in queries; add a compound index on is_sold and
show_when_sold by calling ProductSchema.index({ is_sold: 1, show_when_sold: 1 })
after the ProductSchema definition (and before exporting the model) so
Mongo/Mongoose will create the index and speed up queries that filter by these
two fields.

65-68: Use proper typing instead of any in validator.

The static analysis flagged any usage. Since this validates bulk_discounts, use the correct type.

♻️ Proposed fix
   validate: [
-    (val: any[]) => val.length <= 10,
+    (val: { min_quantity: number; discount_per_item: number }[]) => val.length <= 10,
     'Exceeds the limit of 10 bulk discount conditions',
   ],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@model/Product.ts` around lines 65 - 68, The validator for the bulk_discounts
field uses `any[]`; replace that with the concrete type for bulk discounts
(e.g., `BulkDiscount[]` or `Array<BulkDiscount>`) and import or declare the
`BulkDiscount` interface/type if missing, then change the validator signature
from `(val: any[]) => ...` to `(val: BulkDiscount[]) => val.length <= 10`;
update the validate array on the `bulk_discounts` field in the Product model so
static analysis no longer sees `any`.
app/api/products/route.ts (2)

7-7: Remove unused import.

User is imported but never used in this file.

♻️ Proposed fix
-import User from '@/model/User'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/products/route.ts` at line 7, Remove the unused import symbol User
from the top-level imports in this module (the import line "import User from
'@/model/User'"); update the import list to only include actually used symbols
and run the linter/TS compiler to ensure no other references to User remain
before committing.

14-18: Remove dead code: export const config = { api: { bodyParser: false } } has no effect in App Router.

The bodyParser configuration is Pages Router syntax and does not apply to route handlers in the App Router. The route handler correctly uses await req.formData() for form parsing, making this export redundant and misleading.

♻️ Proposed fix
-export const config = {
-  api: {
-    bodyParser: false,
-  },
-}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/products/route.ts` around lines 14 - 18, Remove the dead Pages Router
config export by deleting the export const config = { api: { bodyParser: false }
} statement; in App Router route handlers the bodyParser option is ignored and
this file already uses await req.formData(), so remove the redundant export
(look for the symbol export const config and the bodyParser key) to avoid
misleading configuration.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/api/products/route.ts`:
- Around line 46-55: The search string is interpolated directly into MongoDB
$regex (inside the filter.$and -> $or clauses for title and description), which
risks special-character injection and unexpected regex behavior; before using
the search variable, escape regex metacharacters (e.g., replace all instances of
. * + ? ^ $ { } ( ) | [ ] \ with an escaped version) and use the escaped value
when constructing the $regex for title and description so the query matches the
literal search term safely and predictably.

---

Nitpick comments:
In `@app/api/products/`[id]/comments/route.ts:
- Around line 4-6: Remove the unused imports to clean up the module: delete the
separate import of User (import User from '@/model/User') and remove IComment
from the named imports on the Product import (import Product, { IComment } from
'@/model/Product'); leave only the symbols actually used (e.g., Product) and
keep other existing imports like verifyJwt intact, or convert any needed
type-only imports to a "type" import if later required.
- Around line 73-76: The find call uses broad any types for product.comments and
parentComment which defeats type safety; replace these anys by importing and
using the Mongoose/Schema types for your product and comment subdocument (e.g.,
ProductDocument / CommentSubdocument or your defined Comment interface), type
product.comments as Comment[] (or Types.DocumentArray<CommentSubdocument>),
remove the "as any" cast, and declare parentComment with the correct type (e.g.,
CommentSubdocument | undefined); update the predicate to use optional chaining
(c._id?.toString() === parentCommentId) so the compiler can infer types
correctly.

In `@app/api/products/`[id]/route.ts:
- Around line 104-113: The route handles contact_info but doesn't enforce the
200-character schema limit; update the validation in the contact_info block
(where contact_info is trimmed and assigned to updateData.contact_info) to check
trimmed.length <= 200 and return a 400 NextResponse.json with a clear message
(e.g., "Contact info must be 200 characters or fewer") when the length exceeds
200, mirroring the description maxlength validation behavior.
- Around line 82-91: Add a max-length check for the incoming description in the
request handler so it mirrors the title validation: when description !==
undefined, after trimming (the existing trimmed variable) verify trimmed.length
<= 1000 and return NextResponse.json({ message: 'Description cannot exceed 1000
characters' }, { status: 400 }) if it’s too long; otherwise assign
updateData.description = trimmed. Target the block handling description in
route.ts (the trimmed variable / updateData.description) to implement this early
validation.

In `@app/api/products/`[id]/sold/route.ts:
- Around line 76-93: The current read-check-modify-save flow around
product.quantity and product.save() can race and oversell; replace it with an
atomic update using Product.findOneAndUpdate where the query includes {_id: id,
seller: userId, quantity: { $gte: sold_quantity }} and the update uses $inc: {
quantity: -sold_quantity } to ensure the decrement only occurs if enough stock
exists, then check if the returned document is null (meaning not
found/insufficient qty) and return the 400 response, and if result.quantity <= 0
set result.is_sold and result.show_when_sold and result.quantity = 0 then
persist (result.save()).

In `@app/api/products/route.ts`:
- Line 7: Remove the unused import symbol User from the top-level imports in
this module (the import line "import User from '@/model/User'"); update the
import list to only include actually used symbols and run the linter/TS compiler
to ensure no other references to User remain before committing.
- Around line 14-18: Remove the dead Pages Router config export by deleting the
export const config = { api: { bodyParser: false } } statement; in App Router
route handlers the bodyParser option is ignored and this file already uses await
req.formData(), so remove the redundant export (look for the symbol export const
config and the bodyParser key) to avoid misleading configuration.

In `@model/Product.ts`:
- Around line 92-99: The schema lacks a compound index for the visibility fields
used in queries; add a compound index on is_sold and show_when_sold by calling
ProductSchema.index({ is_sold: 1, show_when_sold: 1 }) after the ProductSchema
definition (and before exporting the model) so Mongo/Mongoose will create the
index and speed up queries that filter by these two fields.
- Around line 65-68: The validator for the bulk_discounts field uses `any[]`;
replace that with the concrete type for bulk discounts (e.g., `BulkDiscount[]`
or `Array<BulkDiscount>`) and import or declare the `BulkDiscount`
interface/type if missing, then change the validator signature from `(val:
any[]) => ...` to `(val: BulkDiscount[]) => val.length <= 10`; update the
validate array on the `bulk_discounts` field in the Product model so static
analysis no longer sees `any`.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 82951500-53a6-447b-ad55-51d7b53d2922

📥 Commits

Reviewing files that changed from the base of the PR and between 00e129c and f05a238.

📒 Files selected for processing (20)
  • app/api/chat/messages/route.ts
  • app/api/products/[id]/available/route.ts
  • app/api/products/[id]/comments/route.ts
  • app/api/products/[id]/route.ts
  • app/api/products/[id]/sold/route.ts
  • app/api/products/route.ts
  • app/layout.jsx
  • app/marketplace/page.jsx
  • app/quick-reads/page.tsx
  • app/upload-notes/page.tsx
  • app/upload-papers/page.tsx
  • components/chat/ChatWidget.tsx
  • components/theme-toggler.tsx
  • hooks/useChatMessages.ts
  • hooks/useSemesterAutofill.ts
  • lib/eventEmitter.ts
  • middleware.ts
  • model/Message.ts
  • model/Product.ts
  • next.config.ts
💤 Files with no reviewable changes (5)
  • lib/eventEmitter.ts
  • components/chat/ChatWidget.tsx
  • model/Message.ts
  • hooks/useChatMessages.ts
  • app/api/chat/messages/route.ts

Comment thread app/api/products/route.ts
Copy link
Copy Markdown
Contributor

@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

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

242-244: Temp file cleanup is duplicated after Cloudinary helper cleanup.

At Line [242]-Line [244], fs.unlink repeats cleanup already handled in helpers/cloudinary.ts (finally block). Consider removing this duplicate path for clarity.

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

In `@app/api/products/route.ts` around lines 242 - 244, The final cleanup in
route.ts duplicates temp file deletion already done in the Cloudinary helper;
remove the redundant await fs.unlink(tempFilePath).catch(() => {}) from the
finally block in app/api/products/route.ts so the tempFilePath is only unlinked
by the Cloudinary helper (helpers/cloudinary.ts), or alternatively move
responsibility entirely into the route's finally and delete the unlink in
helpers/cloudinary.ts — update the code so only one of the two (route.ts's
finally or helpers/cloudinary.ts cleanup) performs fs.unlink on tempFilePath.

33-33: Whitelist sortBy fields before passing into Mongo sort.

Line [33] and Line [69] accept arbitrary field names from query params. Restricting this to known sortable fields avoids inefficient or unintended sorts.

Suggested fix
-const sortBy = searchParams.get('sortBy') || 'created_at'
+const allowedSortFields = new Set(['created_at', 'price', 'title', 'quantity'])
+const requestedSortBy = searchParams.get('sortBy') || 'created_at'
+const sortBy = allowedSortFields.has(requestedSortBy)
+  ? requestedSortBy
+  : 'created_at'

Also applies to: 69-69

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

In `@app/api/products/route.ts` at line 33, Query param values from
searchParams.get('sortBy') (stored in sortBy) are used directly in Mongo sort;
whitelist acceptable sortable fields first and map incoming values to those safe
field names before building the sort object (same check where sortBy is read and
later where the sort object is constructed around line 69). Implement a const
allowedSortFields = new Set([...]) or mapping of external names to internal DB
keys, validate sortBy against it and fall back to 'created_at' (or return a 400)
if invalid, then use the validated/mapped field when creating the Mongo sort to
prevent arbitrary field injection and inefficient sorts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/api/products/route.ts`:
- Line 7: The file imports an unused symbol; remove the unused import statement
"import User from '@/model/User'" from app/api/products/route.ts to satisfy
linting and eliminate dead code—search for that exact import line and delete it
so no reference to User remains in the module.
- Around line 30-31: Validate and clamp parsed pagination params to avoid
NaN/zero/negative values: when reading page and limit (where parseInt is
currently used to set variables page and limit), coerce NaN to defaults and
clamp page to a minimum of 1 and limit to the range 1..50; then use these
sanitized values for computing skip (used around the skip calculation) and
totalPages (used where totalPages is computed) so downstream math is safe and
cannot produce 0/negative/NaN results.
- Around line 40-44: The current branch in route.ts sets filter.seller when
seller is present but omits the visibility constraint, allowing hidden sold
listings through; always apply a visibility clause (e.g., an $or with {is_sold:
false} and {is_sold: true, show_when_sold: true}) regardless of whether seller
is provided. Update the logic around the filter variable (the seller check in
route.ts) to compose the filter by merging seller into the filter and also
adding the visibilityClause (referencing seller, filter, is_sold, and
show_when_sold) so the public endpoint always enforces unsold or
sold-and-visible constraints.
- Around line 14-18: Remove the dead export named "config" (export const config
= { api: { bodyParser: false } }) from the App Router route handler because App
Router ignores that Pages Router shape; instead, update the route handler(s)
(e.g., your GET/POST handler functions) to explicitly parse the request body
with request.json(), request.text(), or request.formData() as appropriate and
adjust any downstream code that assumed bodyParser:false.

---

Nitpick comments:
In `@app/api/products/route.ts`:
- Around line 242-244: The final cleanup in route.ts duplicates temp file
deletion already done in the Cloudinary helper; remove the redundant await
fs.unlink(tempFilePath).catch(() => {}) from the finally block in
app/api/products/route.ts so the tempFilePath is only unlinked by the Cloudinary
helper (helpers/cloudinary.ts), or alternatively move responsibility entirely
into the route's finally and delete the unlink in helpers/cloudinary.ts — update
the code so only one of the two (route.ts's finally or helpers/cloudinary.ts
cleanup) performs fs.unlink on tempFilePath.
- Line 33: Query param values from searchParams.get('sortBy') (stored in sortBy)
are used directly in Mongo sort; whitelist acceptable sortable fields first and
map incoming values to those safe field names before building the sort object
(same check where sortBy is read and later where the sort object is constructed
around line 69). Implement a const allowedSortFields = new Set([...]) or mapping
of external names to internal DB keys, validate sortBy against it and fall back
to 'created_at' (or return a 400) if invalid, then use the validated/mapped
field when creating the Mongo sort to prevent arbitrary field injection and
inefficient sorts.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8a289f7e-c2e8-4cae-8369-44f530e20261

📥 Commits

Reviewing files that changed from the base of the PR and between f05a238 and 23541a5.

📒 Files selected for processing (1)
  • app/api/products/route.ts

Comment thread app/api/products/route.ts
import path from 'path'
import { uploadOnCloudinary } from '@/helpers/cloudinary'
import Product from '@/model/Product'
import User from '@/model/User'
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.

⚠️ Potential issue | 🟡 Minor

Remove unused import to satisfy lint and reduce dead code.

Line [7] imports User but it is not used in this module.

🧰 Tools
🪛 GitHub Check: Lint

[warning] 7-7:
'User' is defined but never used

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

In `@app/api/products/route.ts` at line 7, The file imports an unused symbol;
remove the unused import statement "import User from '@/model/User'" from
app/api/products/route.ts to satisfy linting and eliminate dead code—search for
that exact import line and delete it so no reference to User remains in the
module.

Comment thread app/api/products/route.ts
Comment on lines +14 to +18
export const config = {
api: {
bodyParser: false,
},
}
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Next.js App Router (app/**/route.ts), is export const config = { api: { bodyParser: false } }supported, or is it only forpages/api routes?

💡 Result:

No, export const config = { api: { bodyParser: false } } is not supported in Next.js App Router route handlers (app//route.ts). It is only supported in Pages Router API routes (pages/api). In App Router route handlers, there is no automatic body parsing like in Pages API routes. Instead, use Web Standard Request APIs such as await request.json, await request.text, or await request.formData to read the body manually as needed. Route handlers do not use a config export for body parsing; configuration for routes uses different exports like dynamic for caching behavior. Official Next.js documentation and discussions confirm that the config.api.bodyParser is specific to Pages Router API routes. Attempts to use it in App Router do not work, as shown in community examples where users tried and failed, confirming the need to use Request methods instead.

Citations:


Remove config export—it's ignored in App Router route handlers.

This config shape is from Pages Router (pages/api) and is not supported in App Router. Next.js silently ignores it in route handlers (lines 14–18), making it dead code that can create false assumptions about request parsing. In App Router, manually parse the body using request.json(), request.text(), or request.formData() as needed.

Suggested fix
-export const config = {
-  api: {
-    bodyParser: false,
-  },
-}
📝 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
export const config = {
api: {
bodyParser: false,
},
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/products/route.ts` around lines 14 - 18, Remove the dead export named
"config" (export const config = { api: { bodyParser: false } }) from the App
Router route handler because App Router ignores that Pages Router shape;
instead, update the route handler(s) (e.g., your GET/POST handler functions) to
explicitly parse the request body with request.json(), request.text(), or
request.formData() as appropriate and adjust any downstream code that assumed
bodyParser:false.

Comment thread app/api/products/route.ts
Comment on lines +30 to +31
const page = parseInt(searchParams.get('page') || '1', 10)
const limit = Math.min(parseInt(searchParams.get('limit') || '20', 10), 50)
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.

⚠️ Potential issue | 🟠 Major

Guard page/limit to prevent invalid pagination math and query args.

Line [30]-Line [31] can produce NaN, 0, or negative values, which then affect Line [62] (skip) and Line [84] (totalPages) incorrectly.

Suggested fix
-const page = parseInt(searchParams.get('page') || '1', 10)
-const limit = Math.min(parseInt(searchParams.get('limit') || '20', 10), 50)
+const rawPage = Number.parseInt(searchParams.get('page') || '1', 10)
+const rawLimit = Number.parseInt(searchParams.get('limit') || '20', 10)
+const page = Number.isFinite(rawPage) && rawPage > 0 ? rawPage : 1
+const limit =
+  Number.isFinite(rawLimit) && rawLimit > 0 ? Math.min(rawLimit, 50) : 20

Also applies to: 62-62, 84-85

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

In `@app/api/products/route.ts` around lines 30 - 31, Validate and clamp parsed
pagination params to avoid NaN/zero/negative values: when reading page and limit
(where parseInt is currently used to set variables page and limit), coerce NaN
to defaults and clamp page to a minimum of 1 and limit to the range 1..50; then
use these sanitized values for computing skip (used around the skip calculation)
and totalPages (used where totalPages is computed) so downstream math is safe
and cannot produce 0/negative/NaN results.

Comment thread app/api/products/route.ts
Comment on lines +40 to +44
if (seller) {
filter.seller = seller
} else {
filter.$or = [{ is_sold: false }, { is_sold: true, show_when_sold: true }]
}
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.

⚠️ Potential issue | 🟠 Major

Public seller filter currently bypasses visibility constraints.

At Line [40]-Line [44], when seller is present, the query no longer applies unsold/sold-visible filtering, so hidden sold listings can be returned by this public endpoint.

Suggested fix
-    if (seller) {
-      filter.seller = seller
-    } else {
-      filter.$or = [{ is_sold: false }, { is_sold: true, show_when_sold: true }]
-    }
+    const visibilityFilter = {
+      $or: [{ is_sold: false }, { is_sold: true, show_when_sold: true }],
+    }
+
+    if (seller) {
+      filter.$and = [{ seller }, visibilityFilter]
+    } else {
+      Object.assign(filter, visibilityFilter)
+    }
📝 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
if (seller) {
filter.seller = seller
} else {
filter.$or = [{ is_sold: false }, { is_sold: true, show_when_sold: true }]
}
const visibilityFilter = {
$or: [{ is_sold: false }, { is_sold: true, show_when_sold: true }],
}
if (seller) {
filter.$and = [{ seller }, visibilityFilter]
} else {
Object.assign(filter, visibilityFilter)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/products/route.ts` around lines 40 - 44, The current branch in
route.ts sets filter.seller when seller is present but omits the visibility
constraint, allowing hidden sold listings through; always apply a visibility
clause (e.g., an $or with {is_sold: false} and {is_sold: true, show_when_sold:
true}) regardless of whether seller is provided. Update the logic around the
filter variable (the seller check in route.ts) to compose the filter by merging
seller into the filter and also adding the visibilityClause (referencing seller,
filter, is_sold, and show_when_sold) so the public endpoint always enforces
unsold or sold-and-visible constraints.

@whilstsomebody
Copy link
Copy Markdown
Contributor

@Rohank3 , your code is just hell an AI slop. We have decided not review it until you do it correctly.

Your design doesn't have a plan, you should make the UI follow the website UI code but currently it is what the AI has made by itself.

This is the last warning to you, otherwise all you contributions will be considered null and void.

@Rohank3
Copy link
Copy Markdown
Contributor Author

Rohank3 commented Apr 12, 2026

@Rohank3 , your code is just hell an AI slop. We have decided not review it until you do it correctly.

Your design doesn't have a plan, you should make the UI follow the website UI code but currently it is what the AI has made by itself.

This is the last warning to you, otherwise all you contributions will be considered null and void.

Thanks for the clarification — I’d like to explain my reasoning behind the changes.

The existing UI works for basic listing, but when I started implementing additional features like:

  • bulk discounts
  • marking items as sold / available
  • richer item details (comments, offers, etc.)

it became difficult to extend the current layout cleanly without making it cluttered or inconsistent.

Because of that, I explored a structured redesign to better support these features (modals, detailed views, action buttons, etc.).

That said, I understand that I should not have introduced a full redesign without aligning with the project’s existing UI patterns first.

I’m happy to:

  1. Revert to the current UI and implement only the required functionality within it, or
  2. Open a separate proposal/discussion for a redesign if that direction is preferred

Please let me know which approach you’d like me to take.

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.

[feat]: Implement College Marketplace Backend (Similar to OLX)

3 participants