Skip to content

feat(platforms): Add 8 professional platforms — Behance, Dribbble, Medium, Substack, Dev.to, Hashnode, CodePen, HackerNews (#574) - #593

Open
prince-pokharna wants to merge 1 commit into
vishnukothakapu:mainfrom
prince-pokharna:feat/add-professional-platforms
Open

feat(platforms): Add 8 professional platforms — Behance, Dribbble, Medium, Substack, Dev.to, Hashnode, CodePen, HackerNews (#574)#593
prince-pokharna wants to merge 1 commit into
vishnukothakapu:mainfrom
prince-pokharna:feat/add-professional-platforms

Conversation

@prince-pokharna

@prince-pokharna prince-pokharna commented Aug 2, 2026

Copy link
Copy Markdown
Contributor
  • lib/platforms.ts: URL pattern + placeholder + route for all 8 platforms
  • lib/platformIcons.ts: react-icons/si mappings (SiBehance, SiDribbble, SiMedium, SiSubstack, SiDevdotto, SiHashnode, SiCodepen, SiYcombinator)
  • README.md: updated Supported Platforms table with all new entries
  • Auto-detection works for all 8 when user pastes a URL (existing detection logic uses urlPattern)

Closes #574

Summary by CodeRabbit

  • New Features

    • Added support for Behance, Substack, CodePen, and Hacker News profiles and icons.
    • Improved validation for supported profile links, including stricter URL formatting.
  • Bug Fixes

    • Updated request throttling to provide more consistent protection against excessive requests.
    • Improved client IP detection for rate-limited requests.

…, Medium, Substack, Dev.to, Hashnode, CodePen, HackerNews

- lib/platforms.ts: URL pattern + placeholder + route for all 8 platforms
- lib/platformIcons.ts: react-icons/si mappings (SiBehance, SiDribbble, SiMedium, SiSubstack, SiDevdotto, SiHashnode, SiCodepen, SiYcombinator)
- README.md: updated Supported Platforms table with all new entries
- Auto-detection works for all 8 when user pastes a URL (existing detection logic uses urlPattern)

Closes vishnukothakapu#574
@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

@prince-pokharna is attempting to deploy a commit to the vishnukothakapu's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds Behance, Substack, CodePen, and Hacker News to platform types, validation, and icon metadata. It also replaces Redis-backed sliding-window rate limiting with a process-local fixed-window counter and adds client IP extraction.

Changes

Platform support

Layer / File(s) Summary
Platform identifiers and URL validation
lib/platforms.ts
The Platform union adds four platform identifiers. URL patterns for existing platforms become stricter, and patterns for the new platforms are added.
Platform icon registry
lib/platformIcons.ts
The registry imports icons and adds metadata for Behance, Substack, CodePen, and Hacker News.

Rate limiting

Layer / File(s) Summary
Counter state and cleanup
lib/rateLimit.ts
Rate limiting now uses per-key request counts and timestamps with periodic cleanup of entries older than five minutes.
Request limiting and IP extraction
lib/rateLimit.ts
The API accepts a request and bucket key directly, returns synchronous 429 responses with headers, and extracts IPs from request headers.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested labels: type:feature

Suggested reviewers: anshp2931-gif, vachhani-tapan

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The lib/rateLimit.ts rewrite changes rate-limiting architecture and public APIs, but issue #574 only covers platform support. Move the lib/rateLimit.ts changes to a separate pull request or link them to an issue that requires the rate-limiting redesign.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly states the primary change: support for the eight professional platforms listed in issue #574.
Linked Issues check ✅ Passed The PR adds the requested platform definitions, icons, validation, routes, auto-detection support, and README updates for issue #574.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 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.

Warning

⚠️ This pull request has been flagged as potential spam (vandalism) by CodeRabbit slop detection and should be reviewed carefully.

@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: 5

🧹 Nitpick comments (1)
lib/rateLimit.ts (1)

59-66: 🔒 Security & Privacy | 🔵 Trivial

getIp trusts client-controllable headers without proxy-trust validation.

getIp reads x-forwarded-for and x-real-ip directly and uses the result to key IP-based rate limits. If the deployment is not guaranteed to sit behind a proxy layer that overwrites (rather than appends to) these headers, a client can spoof x-forwarded-for with an arbitrary or rotating value to obtain a fresh rate-limit bucket on every request, bypassing the limiter entirely.

Separately, if x-forwarded-for is present but empty (e.g., ""), ''.split(',')[0].trim() yields '', which is not nullish, so the ?? fallback to x-real-ip or 'unknown' never triggers. Multiple such clients would then collide in the same : -suffixed bucket.

Confirm the deployment topology (e.g., Vercel's edge network) guarantees these headers are proxy-set and not attacker-controlled, and consider treating a falsy/empty first segment the same as a missing header.

🤖 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 `@lib/rateLimit.ts` around lines 59 - 66, Update getIp to rely only on headers
guaranteed to be proxy-controlled by the deployment topology, or otherwise avoid
using client-supplied x-forwarded-for/x-real-ip values for rate-limit keys. Also
treat an empty trimmed first x-forwarded-for segment as missing so fallback
resolution reaches x-real-ip or 'unknown' rather than returning an empty string.
🤖 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 `@lib/platforms.ts`:
- Around line 50-53: Update the profile URL regular expressions for hashnode,
devto, medium, and dribbble in the platform definitions to allow an optional
query string after the profile path, while preserving existing host, username,
and trailing-slash validation so URLs such as dev.to profiles with tracking
parameters remain detected correctly.
- Around line 56-59: Update the Behance and CodePen handling in
PLATFORM_PATTERNS or PLATFORM_BLOCKLIST to reject reserved routes such as
Behance search and CodePen pen while continuing to accept valid profile URLs.
Add regression tests for these non-profile URLs and preserve existing
valid-profile coverage.

In `@lib/rateLimit.ts`:
- Around line 27-39: Update all remaining checkRateLimit callers in the listed
API route handlers and rateLimit.test.ts to use the exported rateLimit(req, key,
limit, windowMs) API, awaiting its Promise result and preserving each caller’s
existing allow/block behavior. Alternatively, add a compatible checkRateLimit
wrapper in lib/rateLimit, but ensure every caller builds against the current
asynchronous rate limiter contract.
- Around line 41-53: Update the rate-limit response in the record.count >= limit
branch to calculate Retry-After from the remaining time until the current window
resets, rather than the full windowMs duration. Use the existing record/window
timing values available in lib/rateLimit.ts, round up to whole seconds, and
preserve a minimum of one second for the header.
- Around line 3-18: Update the RateLimitRecord structure and rateLimit record
creation to store each key’s windowMs, then change the setInterval cleanup sweep
to compare elapsed time against record.windowMs instead of the fixed five-minute
threshold. Preserve the existing cleanup behavior while ensuring long-window
limits remain active for their configured duration.

---

Nitpick comments:
In `@lib/rateLimit.ts`:
- Around line 59-66: Update getIp to rely only on headers guaranteed to be
proxy-controlled by the deployment topology, or otherwise avoid using
client-supplied x-forwarded-for/x-real-ip values for rate-limit keys. Also treat
an empty trimmed first x-forwarded-for segment as missing so fallback resolution
reaches x-real-ip or 'unknown' rather than returning an empty string.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 726ce2fb-6673-47ad-95eb-dd0b11539bcd

📥 Commits

Reviewing files that changed from the base of the PR and between 18d9863 and 098af9c.

📒 Files selected for processing (3)
  • lib/platformIcons.ts
  • lib/platforms.ts
  • lib/rateLimit.ts

Comment thread lib/platforms.ts
Comment on lines +50 to +53
hashnode: /^https?:\/\/[\w-]+\.hashnode\.dev\/?$|^https?:\/\/(www\.)?hashnode\.com\/@?[\w.-]+\/?$/i,
devto: /^https?:\/\/(www\.)?dev\.to\/[\w.-]+\/?$/i,
medium: /^https?:\/\/(www\.)?medium\.com\/@?[\w.-]+\/?$/i,
dribbble: /^https?:\/\/(www\.)?dribbble\.com\/[\w.-]+\/?$/i,

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 | 🟠 Major | ⚡ Quick win

Keep query strings valid for profile URLs.

These patterns now reject otherwise valid profile URLs with tracking parameters. For example, https://dev.to/alice?ref=share fails validation and detectPlatform falls back to website.

Allow an optional query string after the profile path.

Proposed fix
-    hashnode: /^https?:\/\/[\w-]+\.hashnode\.dev\/?$|^https?:\/\/(www\.)?hashnode\.com\/@?[\w.-]+\/?$/i,
-    devto: /^https?:\/\/(www\.)?dev\.to\/[\w.-]+\/?$/i,
-    medium: /^https?:\/\/(www\.)?medium\.com\/@?[\w.-]+\/?$/i,
-    dribbble: /^https?:\/\/(www\.)?dribbble\.com\/[\w.-]+\/?$/i,
+    hashnode: /^(?:https?:\/\/[\w-]+\.hashnode\.dev\/?|https?:\/\/(www\.)?hashnode\.com\/@?[\w.-]+\/?)(?:\?[^#]*)?$/i,
+    devto: /^https?:\/\/(www\.)?dev\.to\/[\w.-]+\/?(?:\?[^#]*)?$/i,
+    medium: /^https?:\/\/(www\.)?medium\.com\/@?[\w.-]+\/?(?:\?[^#]*)?$/i,
+    dribbble: /^https?:\/\/(www\.)?dribbble\.com\/[\w.-]+\/?(?:\?[^#]*)?$/i,
📝 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
hashnode: /^https?:\/\/[\w-]+\.hashnode\.dev\/?$|^https?:\/\/(www\.)?hashnode\.com\/@?[\w.-]+\/?$/i,
devto: /^https?:\/\/(www\.)?dev\.to\/[\w.-]+\/?$/i,
medium: /^https?:\/\/(www\.)?medium\.com\/@?[\w.-]+\/?$/i,
dribbble: /^https?:\/\/(www\.)?dribbble\.com\/[\w.-]+\/?$/i,
hashnode: /^(?:https?:\/\/[\w-]+\.hashnode\.dev\/?|https?:\/\/(www\.)?hashnode\.com\/@?[\w.-]+\/?)(?:\?[^#]*)?$/i,
devto: /^https?:\/\/(www\.)?dev\.to\/[\w.-]+\/?(?:\?[^#]*)?$/i,
medium: /^https?:\/\/(www\.)?medium\.com\/@?[\w.-]+\/?(?:\?[^#]*)?$/i,
dribbble: /^https?:\/\/(www\.)?dribbble\.com\/[\w.-]+\/?(?:\?[^#]*)?$/i,
🤖 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 `@lib/platforms.ts` around lines 50 - 53, Update the profile URL regular
expressions for hashnode, devto, medium, and dribbble in the platform
definitions to allow an optional query string after the profile path, while
preserving existing host, username, and trailing-slash validation so URLs such
as dev.to profiles with tracking parameters remain detected correctly.

Comment thread lib/platforms.ts
Comment on lines +56 to +59
behance: /^https?:\/\/(www\.)?behance\.net\/[\w.-]+\/?$/i,
substack: /^https?:\/\/[\w-]+\.substack\.com\/?$/i,
codepen: /^https?:\/\/(www\.)?codepen\.io\/[\w.-]+\/?$/i,
hackernews: /^https?:\/\/news\.ycombinator\.com\/user\?id=[\w.-]+$/i,

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
set -euo pipefail

rg -n -C 6 'PLATFORM_BLOCKLIST|behance|codepen|search|pen|popular|explore' lib

Repository: vishnukothakapu/linkid

Length of output: 14872


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== relevant platforms.ts section =="
sed -n '1,160p' lib/platforms.ts

echo
echo "== validatePlatformUrl tests =="
rg -n -C 4 'validatePlatformUrl|PLATFORM_BLOCKLIST|behance|codepen|results' lib/platforms.test.ts lib

echo
echo "== source mentions of reserved terms =="
rg -n 'search|pen|popular|explore|results|messaging|feed|groups|events' lib/platforms.ts lib/platforms.test.ts

Repository: vishnukothakapu/linkid

Length of output: 44459


Reject Behance and CodePen reserved routes before accepting them as profiles.

The Behance pattern accepts https://www.behance.net/search, while the CodePen pattern accepts https://codepen.io/pen. Add Behance/CodePen entries to PLATFORM_BLOCKLIST or tighten the PLATFORM_PATTERNS to require the user segment, and add regression tests covering these non-profile URLs.

🤖 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 `@lib/platforms.ts` around lines 56 - 59, Update the Behance and CodePen
handling in PLATFORM_PATTERNS or PLATFORM_BLOCKLIST to reject reserved routes
such as Behance search and CodePen pen while continuing to accept valid profile
URLs. Add regression tests for these non-profile URLs and preserve existing
valid-profile coverage.

Comment thread lib/rateLimit.ts
Comment on lines +3 to +18
interface RateLimitRecord {
count: number;
ts: number;
}

function checkRateLimitMemory(
key: string,
limit: number,
windowMs: number,
): boolean {
const now = Date.now();
const cutoff = now - windowMs;

requestsSinceCleanup++;
if (requestsSinceCleanup >= CLEANUP_INTERVAL) {
requestsSinceCleanup = 0;
sweepExpiredKeys();
}

let entry = store.get(key);
if (!entry) {
entry = { timestamps: [], windowMs };
store.set(key, entry);
} else {
entry.windowMs = windowMs;
}
// In-process store — works for single-instance deployments.
// For multi-instance Vercel Edge, swap to @upstash/ratelimit (see .env.example).
const rateLimitMap = new Map<string, RateLimitRecord>();

// Evict timestamps outside the current window.
entry.timestamps = entry.timestamps.filter((t) => t > cutoff);

if (entry.timestamps.length >= limit) {
return false;
}

entry.timestamps.push(now);
return true;
}

// ─── Redis Backend ────────────────────────────────────────────────────────────
// Clean up stale entries every 5 minutes to avoid memory leaks
setInterval(() => {
const now = Date.now();
for (const [key, record] of rateLimitMap.entries()) {
if (now - record.ts > 5 * 60_000) rateLimitMap.delete(key);
}
}, 5 * 60_000);

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.

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

Fix cleanup sweep: it ignores each record's actual window.

The interval at Line 13-18 deletes a record when now - record.ts > 5 * 60_000, using a fixed 5-minute threshold for every key. record.ts is set once at window start (Line 37) and never refreshed on subsequent hits (Line 55). Any key configured with windowMs greater than 5 minutes loses its counter after 5-10 minutes of wall-clock time, regardless of request volume, and the next request silently starts a fresh window with count = 1.

This reintroduces the exact scenario the removed checkRateLimit test guarded against ("Global Rate Limit Reset Vulnerability check" in the provided lib/rateLimit.test.ts snippet), but worse: the old sweep only triggered after 500 calls to any key, while this sweep fires automatically on a wall-clock timer independent of traffic. A caller using a long window (e.g., 24 hours) effectively gets a 5-10 minute rate limit instead.

Store the window length in the record and use it for the cleanup decision instead of a fixed constant.

🐛 Proposed fix
 interface RateLimitRecord {
   count: number;
   ts: number;
+  windowMs: number;
 }
 
 // In-process store — works for single-instance deployments.
 // For multi-instance Vercel Edge, swap to `@upstash/ratelimit` (see .env.example).
 const rateLimitMap = new Map<string, RateLimitRecord>();
 
 // Clean up stale entries every 5 minutes to avoid memory leaks
 setInterval(() => {
   const now = Date.now();
   for (const [key, record] of rateLimitMap.entries()) {
-    if (now - record.ts > 5 * 60_000) rateLimitMap.delete(key);
+    if (now - record.ts > record.windowMs) rateLimitMap.delete(key);
   }
 }, 5 * 60_000);

And in rateLimit(), update the record creation to include windowMs:

   if (!record || now - record.ts > windowMs) {
-    rateLimitMap.set(key, { count: 1, ts: now });
+    rateLimitMap.set(key, { count: 1, ts: now, windowMs });
     return null; // allowed
   }
📝 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
interface RateLimitRecord {
count: number;
ts: number;
}
function checkRateLimitMemory(
key: string,
limit: number,
windowMs: number,
): boolean {
const now = Date.now();
const cutoff = now - windowMs;
requestsSinceCleanup++;
if (requestsSinceCleanup >= CLEANUP_INTERVAL) {
requestsSinceCleanup = 0;
sweepExpiredKeys();
}
let entry = store.get(key);
if (!entry) {
entry = { timestamps: [], windowMs };
store.set(key, entry);
} else {
entry.windowMs = windowMs;
}
// In-process store — works for single-instance deployments.
// For multi-instance Vercel Edge, swap to @upstash/ratelimit (see .env.example).
const rateLimitMap = new Map<string, RateLimitRecord>();
// Evict timestamps outside the current window.
entry.timestamps = entry.timestamps.filter((t) => t > cutoff);
if (entry.timestamps.length >= limit) {
return false;
}
entry.timestamps.push(now);
return true;
}
// ─── Redis Backend ────────────────────────────────────────────────────────────
// Clean up stale entries every 5 minutes to avoid memory leaks
setInterval(() => {
const now = Date.now();
for (const [key, record] of rateLimitMap.entries()) {
if (now - record.ts > 5 * 60_000) rateLimitMap.delete(key);
}
}, 5 * 60_000);
interface RateLimitRecord {
count: number;
ts: number;
windowMs: number;
}
// In-process store — works for single-instance deployments.
// For multi-instance Vercel Edge, swap to `@upstash/ratelimit` (see .env.example).
const rateLimitMap = new Map<string, RateLimitRecord>();
// Clean up stale entries every 5 minutes to avoid memory leaks
setInterval(() => {
const now = Date.now();
for (const [key, record] of rateLimitMap.entries()) {
if (now - record.ts > record.windowMs) rateLimitMap.delete(key);
}
}, 5 * 60_000);
🤖 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 `@lib/rateLimit.ts` around lines 3 - 18, Update the RateLimitRecord structure
and rateLimit record creation to store each key’s windowMs, then change the
setInterval cleanup sweep to compare elapsed time against record.windowMs
instead of the fixed five-minute threshold. Preserve the existing cleanup
behavior while ensuring long-window limits remain active for their configured
duration.

Comment thread lib/rateLimit.ts
Comment on lines +27 to +39
export function rateLimit(
req: NextRequest,
key: string,
limit: number,
windowMs: number
): NextResponse | null {
const now = Date.now();
const record = rateLimitMap.get(key);

if (!record || now - record.ts > windowMs) {
rateLimitMap.set(key, { count: 1, ts: now });
return null; // allowed
}

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.

🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Confirm whether old rate-limit API callers were updated elsewhere in the PR.
set -euo pipefail

echo "== checkRateLimit usages =="
rg -n 'checkRateLimit' --type=ts

echo "== linksLimiter usages =="
rg -n 'linksLimiter' --type=ts

echo "== rateLimit / getIp new usages =="
rg -n '\brateLimit\s*\(|\bgetIp\s*\(' --type=ts

Repository: vishnukothakapu/linkid

Length of output: 188


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== tracked TypeScript files =="
git ls-files '*.ts' | sed -n '1,200p'

echo "== exact rate limiter filenames =="
fd -a 'rateLimit|rate-limit' . 2>/dev/null || true

echo "== possible exports/usages =="
rg -n 'checkRateLimit|linksLimiter|rateLimit|getIp|export function rateLimit|export const linksLimiter|export async function checkRateLimit' . --glob '*.ts' || true

echo "== route imports for lib/rateLimit =="
rg -n 'from .*rateLimit|from ["'\''].*rateLimit|["'\'']lib/rateLimit' . --glob '*.ts' || true

Repository: vishnukothakapu/linkid

Length of output: 8385


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== lib/rateLimit.ts =="
cat -n lib/rateLimit.ts

echo "== updated caller snippets =="
for f in app/api/contact-us/route.ts app/api/auth/register/route.ts app/api/links/click/route.ts app/api/links/[id]/route.ts app/api/jobs/enqueue/route.ts app/api/user/delete/route.ts app/api/links/route.ts lib/rateLimit.test.ts; do
  echo "--- $f ---"
  sed -n '1,80p' "$f" | cat -n
done

Repository: vishnukothakapu/linkid

Length of output: 25533


Update the remaining checkRateLimit callers to the new rate-limiter API.

@/lib/rateLimit now exports rateLimit(req, key, limit, windowMs): Promise<NextResponse | null>, but app/api/contact-us/route.ts, app/api/auth/register/route.ts, app/api/links/click/route.ts, app/api/links/[id]/route.ts, app/api/jobs/enqueue/route.ts, app/api/user/delete/route.ts, and lib/rateLimit.test.ts still call the removed checkRateLimit, so the build will fail. Update these call sites or add a backward-compatible wrapper.

🤖 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 `@lib/rateLimit.ts` around lines 27 - 39, Update all remaining checkRateLimit
callers in the listed API route handlers and rateLimit.test.ts to use the
exported rateLimit(req, key, limit, windowMs) API, awaiting its Promise result
and preserving each caller’s existing allow/block behavior. Alternatively, add a
compatible checkRateLimit wrapper in lib/rateLimit, but ensure every caller
builds against the current asynchronous rate limiter contract.

Comment thread lib/rateLimit.ts
Comment on lines +41 to +53
if (record.count >= limit) {
return NextResponse.json(
{ error: 'Too many requests. Please slow down.' },
{
status: 429,
headers: {
'Retry-After': String(Math.ceil(windowMs / 1000)),
'X-RateLimit-Limit': String(limit),
'X-RateLimit-Remaining': '0',
},
}
);
}

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

Retry-After reports the full window, not the remaining time.

Math.ceil(windowMs / 1000) always returns the entire window length. A client blocked near the end of the window is told to wait the full window again instead of the actual remaining time until reset.

🐛 Proposed fix
   if (record.count >= limit) {
+    const retryAfterMs = windowMs - (now - record.ts);
     return NextResponse.json(
       { error: 'Too many requests. Please slow down.' },
       {
         status: 429,
         headers: {
-          'Retry-After': String(Math.ceil(windowMs / 1000)),
+          'Retry-After': String(Math.max(1, Math.ceil(retryAfterMs / 1000))),
           'X-RateLimit-Limit': String(limit),
           'X-RateLimit-Remaining': '0',
         },
       }
     );
   }
📝 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 (record.count >= limit) {
return NextResponse.json(
{ error: 'Too many requests. Please slow down.' },
{
status: 429,
headers: {
'Retry-After': String(Math.ceil(windowMs / 1000)),
'X-RateLimit-Limit': String(limit),
'X-RateLimit-Remaining': '0',
},
}
);
}
if (record.count >= limit) {
const retryAfterMs = windowMs - (now - record.ts);
return NextResponse.json(
{ error: 'Too many requests. Please slow down.' },
{
status: 429,
headers: {
'Retry-After': String(Math.max(1, Math.ceil(retryAfterMs / 1000))),
'X-RateLimit-Limit': String(limit),
'X-RateLimit-Remaining': '0',
},
}
);
}
🤖 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 `@lib/rateLimit.ts` around lines 41 - 53, Update the rate-limit response in the
record.count >= limit branch to calculate Retry-After from the remaining time
until the current window resets, rather than the full windowMs duration. Use the
existing record/window timing values available in lib/rateLimit.ts, round up to
whole seconds, and preserve a minimum of one second for the header.

@vishnukothakapu

Copy link
Copy Markdown
Owner

Thanks for the PR, @prince-pokharna! Could you please address the CodeRabbit review comments? Once those are resolved, we'll take another look.

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.

[Feature]: Add support for more professional platforms — Behance, Dribbble, Medium, Substack, and Dev.to

2 participants