Skip to content

Conversation

@huamanraj
Copy link
Collaborator

@huamanraj huamanraj commented Dec 11, 2025

UI examples

image image image

Summary by CodeRabbit

  • New Features

    • Testimonials gallery and client submission flow (paid-only), plus dashboard shortcut to submit
    • New testimonial API endpoints: list, my testimonial (paid-only), and submit (one-time create)
  • Performance

    • Shared Redis support enabled (infrastructure added)
  • Security / UX

    • Stricter avatar URL validation (HTTPS, host and size checks); submission gated to paid subscribers
    • Improved subscription verification with retry before redirect
  • Chores

    • Env example updated, remote image hosts added, form libraries and image testimonials data included

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Dec 11, 2025

@huamanraj is attempting to deploy a commit to the AJEET PRATAP SINGH's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 11, 2025

Walkthrough

Adds a Testimonial feature: Prisma model and relation, TRPC testimonial router, server-side avatar validator, Redis utilities and types, frontend list and submit pages (react-hook-form + zod), subscription/refetch retry logic, plus package/config updates.

Changes

Cohort / File(s) Summary
Prisma schema
apps/api/prisma/schema.prisma
Adds Testimonial model and an optional testimonial? relation on User.
API router registration
apps/api/src/routers/_app.ts
Registers new testimonial route by importing and exposing testimonialRouter.
Testimonial router
apps/api/src/routers/testimonial.ts
New testimonialRouter with getAll (public, reads DB), getMyTestimonial (protected, subscription-gated), and submit (protected, zod validation, server avatar validation, prevents duplicate submissions). No Redis caching present.
Avatar validation util
apps/api/src/utils/avatar-validator.ts
New server-side avatar URL validator enforcing HTTPS, allowlisted hosts, HEAD checks, timeouts, content-type/size checks; exports validateAvatarUrl and avatarUrlRefinement.
TRPC typing / protectedProcedure
apps/api/src/trpc.ts
Adds ProtectedContext = Context & { user: User }, updates isAuthed typings, and exports typed protectedProcedure.
API env & minor formatting
apps/api/.env.example, apps/api/package.json, apps/api/tsconfig.json
Adds REDIS_URL=redis://localhost:6379 to .env.example; minor whitespace/newline edits in package.json and tsconfig.
Redis shared utilities & deps
packages/shared/types/redis.ts, packages/shared/types/index.ts, packages/shared/package.json
Adds getRedisClient() singleton and redisCache helpers (get, set, del, delPattern) with graceful fallback when REDIS_URL missing; adds ioredis and @types/node; re-exports ./redis.
Frontend testimonials UI
apps/web/src/app/(main)/testimonials/page.tsx, apps/web/src/data/testimonials.ts
New testimonials listing page that fetches text testimonials via TRPC, merges with local imageTestimonials, and renders a Masonry-like grid; new imageTestimonials data and type.
Frontend submit page
apps/web/src/app/(main)/testimonials/submit/page.tsx
New client-only submit page with react-hook-form + zod validation, subscription gating, TRPC getMyTestimonial & submit mutation, avatar preview, and redirect on success.
Dashboard CTA
apps/web/src/app/(main)/dashboard/pro/dashboard/page.tsx
Adds TRPC query to check getMyTestimonial and shows "Submit Testimonial" button if none exists.
Web deps & image hosts
apps/web/package.json, apps/web/next.config.js
Adds react-hook-form, @hookform/resolvers, zod to web deps; adds remote image hosts i.pravatar.cc, picsum.photos, standardcoldpressedoil.com.
Subscription refetch & checkout
apps/web/src/hooks/useSubscription.ts, apps/web/src/components/checkout/CheckoutWrapper.tsx
useSubscription() now exposes refetch; CheckoutWrapper adds retry loop (max 3 attempts) and uses redirect to /pricing after retries if unpaid.
Payment typing
apps/api/src/services/payment.service.ts
Exports RazorpayOrderSuccess interface (added export).
Minor formatting/text edits
apps/web/src/app/(main)/(landing)/pitch/page.tsx, apps/api/src/routers/user.ts, apps/web/src/components/checkout/checkout-confirmation.tsx
Whitespace/formatting and minor text literal edits; no behavioral changes.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Web as Web Client
    participant API as API Server
    participant DB as Prisma/DB
    participant Auth as Subscription Service
    participant Validator as Avatar Validator

    User->>Web: Open testimonials page
    Web->>API: TRPC testimonials.getAll
    API->>DB: query Testimonial (order by createdAt desc)
    DB-->>API: return records
    API-->>Web: return testimonials
    Web->>Web: merge with local imageTestimonials & render
Loading
sequenceDiagram
    actor User
    participant Web as Web Client
    participant API as API Server
    participant Auth as Subscription Service
    participant Validator as Avatar Validator
    participant DB as Prisma/DB

    User->>Web: Click "Submit Testimonial" (form validated client-side)
    Web->>API: TRPC testimonials.submit (protected)
    API->>Auth: check subscription status (is paid)
    alt not paid
        Auth-->>API: not paid
        API-->>Web: TRPC FORBIDDEN
    else paid
        API->>Validator: validateAvatarUrl(avatarUrl) (HEAD + allowlist)
        alt avatar invalid
            Validator-->>API: validation error
            API-->>Web: TRPC BAD_REQUEST
        else valid
            API->>DB: create Testimonial (error if exists)
            DB-->>API: created record
            API-->>Web: return created testimonial
            Web->>Web: navigate to /testimonials
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title claims Redis caching for testimonials, but the actual implementation removes Redis caching (per commit message 'redis removed for testimonials'). The testimonials feature is added, but the caching aspect mentioned in the title is not implemented. Update the PR title to accurately reflect the implemented changes, such as '[Feat] Add pro users testimonials page' or '[Feat] Testimonials submission and gallery for paid users' to remove the misleading Redis caching claim.
Docstring Coverage ⚠️ Warning Docstring coverage is 62.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@cla-assistant
Copy link

cla-assistant bot commented Dec 11, 2025

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
5 out of 6 committers have signed the CLA.

✅ apsinghdev
✅ huamanraj
✅ praveenzsp
✅ SGNayak12
✅ mizurex
❌ Lucifer-0612
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
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: 8

🧹 Nitpick comments (17)
apps/web/src/app/(main)/dashboard/pro/dashboard/page.tsx (1)

113-127: Verify the new button follows design system and accessibility guidelines.

The addition of the "Submit Testimonial" button follows the existing pattern and uses design system colors (bg-brand-purple, hover:bg-brand-purple-light). However, consider the following:

  1. Consistency: The "Join Slack" button has a disabled state, but the "Submit Testimonial" button does not. Should it also be disabled while joining or have its own loading state?

  2. Accessibility: Consider adding aria-label attributes for screen readers, especially since the buttons are in a flex container.

As per coding guidelines, the use of Tailwind design token classes is correct.

Optional enhancement:

             <div className="flex flex-wrap gap-4 justify-center">
               <button
                 onClick={handleJoinSlack}
                 disabled={isJoining}
+                aria-label="Join our Slack community"
                 className="px-4 py-2 bg-brand-purple hover:bg-brand-purple-light text-text-primary font-medium rounded-lg transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed text-sm"
               >
                 {isJoining ? "Joining..." : "Join Slack"}
               </button>
               <button
                 onClick={() => router.push("/testimonials/submit")}
+                aria-label="Submit your testimonial"
                 className="px-4 py-2 bg-brand-purple hover:bg-brand-purple-light text-text-primary font-medium rounded-lg transition-colors duration-200 text-sm"
               >
                 Submit Testimonial
               </button>
             </div>
apps/web/src/data/testimonials.ts (2)

1-1: Remove unnecessary comment.

Per coding guidelines, code should be self-documenting. The type and constant names clearly indicate this is image testimonials data.

-// Image testimonials data
 export type ImageTestimonial = {

9-9: Use UPPER_SNAKE_CASE for constants.

Per coding guidelines, constants should use UPPER_SNAKE_CASE.

-export const imageTestimonials: ImageTestimonial[] = [
+export const IMAGE_TESTIMONIALS: ImageTestimonial[] = [

Note: Update the import in apps/web/src/app/(main)/testimonials/page.tsx accordingly.

apps/api/prisma/schema.prisma (1)

48-57: Consider adding an index on createdAt for query performance.

The getAll endpoint orders testimonials by createdAt desc. As the testimonials table grows, an index would optimize this query.

   user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
+
+  @@index([createdAt(sort: Desc)])
 }
apps/web/src/app/(main)/testimonials/page.tsx (3)

37-37: Replace hardcoded hex values with design tokens.

Per coding guidelines, never use hardcoded hex values directly in components. Use semantic color classes from the design token system instead.

-      <div className="mb-4 break-inside-avoid rounded-xl border border-[#252525] bg-neutral-900/50 overflow-hidden hover:border-neutral-700 transition-colors">
+      <div className="mb-4 break-inside-avoid rounded-xl border border-neutral-800 bg-neutral-900/50 overflow-hidden hover:border-neutral-700 transition-colors">

Apply similar changes to other occurrences on lines 52, 124, 127, 146, 166.

Also applies to: 52-52


54-61: Add sizes attribute to Image with fill prop.

When using fill prop, providing a sizes attribute helps Next.js optimize image loading and prevents layout shift warnings.

         <div className="relative w-10 h-10 rounded-full overflow-hidden border border-white/10">
           <Image
             src={item.user.avatar}
             alt={item.user.name}
             fill
+            sizes="40px"
             className="object-cover"
           />
         </div>

34-34: Use const with arrow functions for component definitions.

Per coding guidelines, prefer const with arrow functions instead of function declarations, and define a type for const functions.

-const TestimonialCard = ({ item }: { item: Testimonial }) => {
+type TestimonialCardProps = {
+  item: Testimonial;
+};
+
+const TestimonialCard: React.FC<TestimonialCardProps> = ({ item }) => {
-const TestimonialsPage = () => {
+const TestimonialsPage: React.FC = () => {

Also applies to: 78-78

apps/api/src/routers/testimonial.ts (2)

17-17: Add context to log statements for debugging.

Per coding guidelines, log errors with context (userId, endpoint, timestamp) for debugging. Consider using structured logging.

-            console.log("Testimonials served from cache");
+            console.log("[testimonials:getAll] cache HIT");
-        console.log("Testimonials fetched from database and cached");
+        console.log("[testimonials:getAll] fetched from database and cached");
-        console.log("Testimonials cache invalidated after submission");
+        console.log("[testimonials:submit] cache invalidated", { userId });

Also applies to: 20-20, 31-31, 92-92


15-15: Use a specific type instead of any[] for cached data.

Define a proper type for the cached testimonials to maintain type safety throughout the codebase.

+type CachedTestimonial = {
+    id: string;
+    userId: string;
+    content: string;
+    name: string;
+    avatar: string;
+    createdAt: Date;
+    updatedAt: Date;
+};
+
 export const testimonialRouter = router({
     getAll: publicProcedure.query(async ({ ctx }: any) => {
         // try to get from cache first
-        const cached = await redisCache.get<any[]>(TESTIMONIALS_CACHE_KEY);
+        const cached = await redisCache.get<CachedTestimonial[]>(TESTIMONIALS_CACHE_KEY);

Alternatively, import the generated Prisma type for Testimonial if available.

packages/shared/types/redis.ts (4)

3-8: Variable shadowing is intentional but can be clarified.

The static analysis tool flags line 8 as a redeclaration of redis. This pattern is intentional—global.redis persists across HMR in development, while the module-level redis holds the reference for the current process. Consider renaming the module-level variable to redisInstance to make the distinction clearer and silence the linter.

-let redis: Redis | undefined;
+let redisInstance: Redis | undefined;

Then update references at lines 44, 47, 48, 56, 62 accordingly.


45-59: Production Redis config is inconsistent with development.

The development configuration includes lazyConnect: true, but the production configuration omits it. This inconsistency may cause different connection behaviors between environments.

             if (!redis) {
                 redis = new Redis(redisUrl, {
                     maxRetriesPerRequest: 3,
                     retryStrategy(times) {
                         const delay = Math.min(times * 50, 2000);
                         return delay;
                     },
+                    lazyConnect: true,
                 });

93-93: Avoid any type for the value parameter.

Per coding guidelines, avoid using any. Use a generic or unknown type instead to maintain type safety.

-    async set(key: string, value: any, ttl?: number): Promise<boolean> {
+    async set<T>(key: string, value: T, ttl?: number): Promise<boolean> {

137-141: KEYS command can block Redis in production.

The KEYS command scans the entire keyspace and blocks the server, which is problematic with large datasets. Consider using SCAN for production-safe iteration.

     async delPattern(pattern: string): Promise<boolean> {
         const client = getRedisClient();
         if (!client) return false;

         try {
-            const keys = await client.keys(pattern);
-            if (keys.length > 0) {
-                await client.del(...keys);
-            }
+            let cursor = "0";
+            do {
+                const [nextCursor, keys] = await client.scan(cursor, "MATCH", pattern, "COUNT", 100);
+                cursor = nextCursor;
+                if (keys.length > 0) {
+                    await client.del(...keys);
+                }
+            } while (cursor !== "0");
             return true;
         } catch (error) {
apps/web/src/app/(main)/testimonials/submit/page.tsx (4)

59-61: Replace alert() with a toast notification or inline error display.

Using alert() blocks the UI and provides poor UX. Consider using a toast component for non-blocking error feedback.

     onError: (error: any) => {
-      alert("Error submitting testimonial: " + error.message);
+      // use a toast notification library or inline error state
+      // e.g., toast.error("Error submitting testimonial: " + error.message);
     },

117-123: Use design tokens instead of hardcoded hex values.

Per coding guidelines, never use hardcoded hex values like #101010 and #252525 directly in components. Reference colors from the design token system using Tailwind classes.

Replace hardcoded colors with the appropriate semantic Tailwind classes from your design system. For example, if #101010 maps to a background token, use bg-background or the equivalent defined in design-tokens.ts.

Also applies to: 129-129, 166-166, 201-201


98-108: Replace alert() with consistent error handling.

Line 100 uses alert() for missing avatar validation. For consistency and better UX, use the same error notification pattern as other errors in this component.


238-241: Image onError handler bypasses Next.js optimization.

Directly setting src on the image element bypasses Next.js Image optimization. Consider using a state variable to handle fallback images properly.

// add state for avatar error
const [avatarError, setAvatarError] = React.useState(false);

// in the Image component:
<Image
  src={avatarError ? `https://i.pravatar.cc/150?u=${session?.user?.email}` : displayAvatar}
  alt="Profile Picture"
  fill
  className="object-cover"
  onError={() => setAvatarError(true)}
/>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 050e439 and f17d0d2.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (15)
  • apps/api/.env.example (1 hunks)
  • apps/api/package.json (1 hunks)
  • apps/api/prisma/schema.prisma (1 hunks)
  • apps/api/src/routers/_app.ts (2 hunks)
  • apps/api/src/routers/testimonial.ts (1 hunks)
  • apps/api/tsconfig.json (1 hunks)
  • apps/web/next.config.js (1 hunks)
  • apps/web/package.json (2 hunks)
  • apps/web/src/app/(main)/dashboard/pro/dashboard/page.tsx (1 hunks)
  • apps/web/src/app/(main)/testimonials/page.tsx (1 hunks)
  • apps/web/src/app/(main)/testimonials/submit/page.tsx (1 hunks)
  • apps/web/src/data/testimonials.ts (1 hunks)
  • packages/shared/package.json (1 hunks)
  • packages/shared/types/index.ts (1 hunks)
  • packages/shared/types/redis.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'
Remove unused imports
Use UPPER_SNAKE_CASE for constants
Use camelCase for functions and variables

Files:

  • apps/web/src/data/testimonials.ts
  • apps/api/src/routers/testimonial.ts
  • apps/api/src/routers/_app.ts
  • packages/shared/types/redis.ts
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
  • packages/shared/types/index.ts
  • apps/web/src/app/(main)/dashboard/pro/dashboard/page.tsx
  • apps/web/next.config.js
  • apps/web/src/app/(main)/testimonials/page.tsx
apps/web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

apps/web/src/**/*.{ts,tsx}: Always follow the design system defined in apps/web/src/lib/design-tokens.ts and apps/web/tailwind.config.ts
NEVER use hardcoded hex values (e.g., #5519f7) directly in components; ALWAYS reference colors from the design token system using Tailwind classes
Use semantic color names that describe purpose, not appearance
Use font-sans for standard UI text (Geist Sans) and font-mono for code, technical content, or monospace needs (DM Mono)
Follow Tailwind's spacing scale (0.25rem increments); for section padding use mobile p-4 (1rem) and desktop p-[60px]
Use appropriate border radius: small elements rounded-lg, medium rounded-xl, large rounded-2xl, buttons rounded-[16px]
Use animation durations: fast duration-100 (0.1s), normal duration-300 (0.3s), slow duration-600 (0.6s)

Files:

  • apps/web/src/data/testimonials.ts
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
  • apps/web/src/app/(main)/dashboard/pro/dashboard/page.tsx
  • apps/web/src/app/(main)/testimonials/page.tsx
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,ts}: Prefer functional components with TypeScript and use proper TypeScript types, avoid any
Extract reusable logic into custom hooks
Use descriptive prop names and define prop types using TypeScript interfaces or types
Prefer controlled components over uncontrolled
Use zustand for global state (located in src/store/)
Use absolute imports from @/ prefix when available
Include proper aria labels for accessibility
Ensure keyboard navigation works
Maintain proper heading hierarchy
Provide alt text for images
Avoid unnecessary re-renders

Files:

  • apps/web/src/data/testimonials.ts
  • apps/api/src/routers/testimonial.ts
  • apps/api/src/routers/_app.ts
  • packages/shared/types/redis.ts
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
  • packages/shared/types/index.ts
  • apps/web/src/app/(main)/dashboard/pro/dashboard/page.tsx
  • apps/web/src/app/(main)/testimonials/page.tsx
**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (.cursorrules)

Organize imports: react → third-party → local components → utils → types

Files:

  • apps/web/src/data/testimonials.ts
  • apps/api/src/routers/testimonial.ts
  • apps/api/src/routers/_app.ts
  • packages/shared/types/redis.ts
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
  • packages/shared/types/index.ts
  • apps/web/src/app/(main)/dashboard/pro/dashboard/page.tsx
  • apps/web/next.config.js
  • apps/web/src/app/(main)/testimonials/page.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Use PascalCase for types and interfaces with descriptive names
Use dynamic imports for code splitting when appropriate
Memoize expensive computations

Files:

  • apps/web/src/data/testimonials.ts
  • apps/api/src/routers/testimonial.ts
  • apps/api/src/routers/_app.ts
  • packages/shared/types/redis.ts
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
  • packages/shared/types/index.ts
  • apps/web/src/app/(main)/dashboard/pro/dashboard/page.tsx
  • apps/web/src/app/(main)/testimonials/page.tsx
apps/web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursorrules)

Optimize images using next/image

apps/web/src/**/*.{tsx,ts}: Use Zustand for global state, located in src/store/
Use PascalCase for types and interfaces with descriptive names
Use dynamic imports for code splitting when appropriate
Optimize images using next/image
Memoize expensive computations
Define a type when defining const functions

Files:

  • apps/web/src/data/testimonials.ts
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
  • apps/web/src/app/(main)/dashboard/pro/dashboard/page.tsx
  • apps/web/src/app/(main)/testimonials/page.tsx
**/*.{js,jsx,ts,tsx,py,java,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/general_rules.mdc)

**/*.{js,jsx,ts,tsx,py,java,go,rb,php}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'

Files:

  • apps/web/src/data/testimonials.ts
  • apps/api/src/routers/testimonial.ts
  • apps/api/src/routers/_app.ts
  • packages/shared/types/redis.ts
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
  • packages/shared/types/index.ts
  • apps/web/src/app/(main)/dashboard/pro/dashboard/page.tsx
  • apps/web/next.config.js
  • apps/web/src/app/(main)/testimonials/page.tsx
apps/web/src/**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (apps/web/.cursor/rules/frontend_rules.mdc)

apps/web/src/**/*.{tsx,ts,jsx,js}: Organize imports in order: React → third-party → local components → utils → types
Use absolute imports from @/ prefix when available
Remove unused imports
Use UPPER_SNAKE_CASE for constants
Use camelCase for functions and variables
Always use Tailwind classes for styling HTML elements; avoid using CSS or style tags
Use descriptive variable and function names; name event functions with a 'handle' prefix (e.g., handleClick, handleKeyDown)
Use const with arrow functions instead of function declarations (e.g., 'const toggle = () =>')

Files:

  • apps/web/src/data/testimonials.ts
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
  • apps/web/src/app/(main)/dashboard/pro/dashboard/page.tsx
  • apps/web/src/app/(main)/testimonials/page.tsx
apps/api/src/**/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

apps/api/src/**/*.{js,ts}: Log errors with context (userId, endpoint, timestamp) for debugging
Always await async operations; never forget to handle promise rejections
Never log sensitive information (passwords, tokens, credit card numbers)

Files:

  • apps/api/src/routers/testimonial.ts
  • apps/api/src/routers/_app.ts
apps/api/src/**/*.ts

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Avoid any type; use unknown for truly dynamic data and narrow with type guards

Files:

  • apps/api/src/routers/testimonial.ts
  • apps/api/src/routers/_app.ts
🧠 Learnings (15)
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{routes,controllers,handlers,middleware}/*.{js,ts} : Use zod or similar for runtime validation of incoming data

Applied to files:

  • apps/api/package.json
  • apps/api/src/routers/_app.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Document all required environment variables in `.env.example`

Applied to files:

  • apps/api/.env.example
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Provide alt text for images

Applied to files:

  • apps/web/src/data/testimonials.ts
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts,jsx,js} : Provide alt text for images

Applied to files:

  • apps/web/src/data/testimonials.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{routes,middleware}/*.{js,ts} : Use middleware for auth checks; never duplicate auth logic in route handlers

Applied to files:

  • apps/api/src/routers/_app.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Export types from shared package for consistency across apps

Applied to files:

  • packages/shared/types/index.ts
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/**/*.{tsx,ts,jsx,js} : Remove unused imports

Applied to files:

  • packages/shared/types/index.ts
  • apps/api/tsconfig.json
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to apps/web/src/**/*.{tsx,ts} : Optimize images using next/image

Applied to files:

  • apps/web/next.config.js
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{logging,middleware,utils}/*.{js,ts} : Use structured logging (JSON format) for easier parsing and analysis

Applied to files:

  • apps/api/tsconfig.json
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Avoid unnecessary comments; code should be self-documenting when possible

Applied to files:

  • apps/api/tsconfig.json
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{config,environment,setup}/*.{js,ts} : Use type-safe environment variable access with proper defaults

Applied to files:

  • apps/api/tsconfig.json
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts,jsx,js} : Organize imports: react → third-party → local components → utils → types

Applied to files:

  • apps/api/tsconfig.json
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Use descriptive prop names and define prop types using TypeScript interfaces or types

Applied to files:

  • apps/api/tsconfig.json
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/**/*.{tsx,ts,jsx,js} : Organize imports in order: React → third-party → local components → utils → types

Applied to files:

  • apps/api/tsconfig.json
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use UPPER_SNAKE_CASE for constants

Applied to files:

  • apps/api/tsconfig.json
🧬 Code graph analysis (2)
apps/api/src/routers/testimonial.ts (3)
apps/api/src/trpc.ts (3)
  • router (38-38)
  • publicProcedure (39-39)
  • protectedProcedure (40-40)
packages/shared/types/redis.ts (1)
  • redisCache (72-148)
apps/api/src/services/user.service.ts (1)
  • userService (5-89)
apps/api/src/routers/_app.ts (3)
apps/api/src/routers/testimonial.ts (1)
  • testimonialRouter (12-96)
apps/web/src/app/(main)/(landing)/pricing/page.tsx (4)
  • testimonial (623-635)
  • testimonials (496-640)
  • testimonial (610-619)
  • testimonial (594-606)
apps/web/src/components/landing-sections/testimonials.tsx (1)
  • div (45-82)
🪛 Biome (2.1.2)
packages/shared/types/redis.ts

[error] 8-8: Shouldn't redeclare 'redis'. Consider to delete it or rename it.

'redis' is defined here:

(lint/suspicious/noRedeclare)

🪛 dotenv-linter (4.0.0)
apps/api/.env.example

[warning] 34-34: [ExtraBlankLine] Extra blank line detected

(ExtraBlankLine)


[warning] 35-35: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)

🔇 Additional comments (9)
apps/api/package.json (1)

42-42: No functional changes detected.

This is a formatting-only change with no impact on dependencies or behavior.

apps/api/tsconfig.json (1)

38-38: No functional changes detected.

This is a formatting-only change with no impact on TypeScript configuration.

packages/shared/package.json (1)

11-15: LGTM!

The added dependencies are appropriate for Redis support:

  • ioredis is the standard Redis client for Node.js
  • @types/node provides necessary type definitions for Node.js APIs

These align with the new Redis utilities introduced in packages/shared/types/redis.ts.

packages/shared/types/index.ts (1)

2-3: LGTM!

The addition of the Redis export is well-organized and follows the established pattern. The semicolon addition ensures consistency across export statements.

Based on learnings, this properly exports types from the shared package for consistency across apps.

apps/web/package.json (1)

13-47: Dependencies are compatible with the project.

@hookform/resolvers 5.2.2, react-hook-form 7.68.0, and zod 4.1.9 are fully compatible with each other and with Next.js 15.5.3. These versions are appropriate for form validation in the testimonials feature.

apps/api/src/routers/_app.ts (1)

7-7: LGTM!

The testimonial router is correctly imported and registered following the existing pattern for other routers.

Also applies to: 25-25

apps/web/src/app/(main)/testimonials/submit/page.tsx (3)

1-19: Import organization looks good.

Imports are correctly organized: React → third-party libraries → local components → utils/hooks. This follows the coding guidelines.


23-34: Form validation with zod is well implemented.

The schema validation with character limits and the integration with react-hook-form using zodResolver is clean and follows best practices.

Also applies to: 64-76


84-96: Effect handles form population correctly.

The useEffect properly resets form values when existing testimonial data or session changes, with correct dependencies in the array.

Copy link
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: 2

🧹 Nitpick comments (1)
apps/api/src/utils/avatar-validator.ts (1)

164-171: Consider adding error logging for debugging.

The function silently returns false on all errors, which is appropriate for Zod refinement. However, logging failed validations would aid debugging without exposing details to the client.

As per coding guidelines for apps/api/src/**/*.{js,ts}, consider adding structured logging:

 export async function avatarUrlRefinement(url: string): Promise<boolean> {
     try {
         await validateAvatarUrl(url);
         return true;
     } catch (error) {
+        // log error with context for debugging (don't expose to client)
+        console.error('Avatar URL validation failed in refinement', { 
+            url: url.substring(0, 50), // truncate for privacy
+            error: error instanceof TRPCError ? error.message : 'Unknown error'
+        });
         return false;
     }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f17d0d2 and c25bf86.

📒 Files selected for processing (4)
  • apps/api/src/routers/testimonial.ts (1 hunks)
  • apps/api/src/trpc.ts (2 hunks)
  • apps/api/src/utils/avatar-validator.ts (1 hunks)
  • apps/web/src/app/(main)/testimonials/page.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/src/app/(main)/testimonials/page.tsx
  • apps/api/src/routers/testimonial.ts
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'
Remove unused imports
Use UPPER_SNAKE_CASE for constants
Use camelCase for functions and variables

Files:

  • apps/api/src/utils/avatar-validator.ts
  • apps/api/src/trpc.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,ts}: Prefer functional components with TypeScript and use proper TypeScript types, avoid any
Extract reusable logic into custom hooks
Use descriptive prop names and define prop types using TypeScript interfaces or types
Prefer controlled components over uncontrolled
Use zustand for global state (located in src/store/)
Use absolute imports from @/ prefix when available
Include proper aria labels for accessibility
Ensure keyboard navigation works
Maintain proper heading hierarchy
Provide alt text for images
Avoid unnecessary re-renders

Files:

  • apps/api/src/utils/avatar-validator.ts
  • apps/api/src/trpc.ts
**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (.cursorrules)

Organize imports: react → third-party → local components → utils → types

Files:

  • apps/api/src/utils/avatar-validator.ts
  • apps/api/src/trpc.ts
**/src/{lib,utils}/**

📄 CodeRabbit inference engine (.cursorrules)

Use kebab-case or camelCase for utility file names

Files:

  • apps/api/src/utils/avatar-validator.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Use PascalCase for types and interfaces with descriptive names
Use dynamic imports for code splitting when appropriate
Memoize expensive computations

Files:

  • apps/api/src/utils/avatar-validator.ts
  • apps/api/src/trpc.ts
**/*.{js,jsx,ts,tsx,py,java,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/general_rules.mdc)

**/*.{js,jsx,ts,tsx,py,java,go,rb,php}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'

Files:

  • apps/api/src/utils/avatar-validator.ts
  • apps/api/src/trpc.ts
apps/api/src/**/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

apps/api/src/**/*.{js,ts}: Log errors with context (userId, endpoint, timestamp) for debugging
Always await async operations; never forget to handle promise rejections
Never log sensitive information (passwords, tokens, credit card numbers)

Files:

  • apps/api/src/utils/avatar-validator.ts
  • apps/api/src/trpc.ts
apps/api/src/**/*.ts

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Avoid any type; use unknown for truly dynamic data and narrow with type guards

Files:

  • apps/api/src/utils/avatar-validator.ts
  • apps/api/src/trpc.ts
apps/api/src/**/{middleware,logging,utils}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Include correlation IDs in logs for request tracing across services

Files:

  • apps/api/src/utils/avatar-validator.ts
apps/api/src/**/{logging,middleware,utils}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Use structured logging (JSON format) for easier parsing and analysis

Files:

  • apps/api/src/utils/avatar-validator.ts
🧠 Learnings (3)
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{routes,controllers,handlers,middleware}/*.{js,ts} : Always validate user authentication before processing protected routes

Applied to files:

  • apps/api/src/trpc.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{routes,middleware}/*.{js,ts} : Use middleware for auth checks; never duplicate auth logic in route handlers

Applied to files:

  • apps/api/src/trpc.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/*.ts : Avoid `any` type; use `unknown` for truly dynamic data and narrow with type guards

Applied to files:

  • apps/api/src/trpc.ts
🔇 Additional comments (3)
apps/api/src/utils/avatar-validator.ts (2)

1-16: LGTM!

The imports and allowlist configuration are appropriate. The trusted hosts cover major OAuth providers and CDN services.


18-32: LGTM!

The validation limits and private IP ranges are comprehensive, covering IPv4/IPv6 private networks, localhost, and link-local addresses.

apps/api/src/trpc.ts (1)

5-8: LGTM!

The ProtectedContext type accurately represents the context shape after authentication, improving type clarity for protected procedures.

Copy link
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: 5

♻️ Duplicate comments (2)
apps/api/src/utils/avatar-validator.ts (1)

102-109: SSRF protection applied correctly.

The addition of redirect: "error" at line 105 properly addresses the SSRF vulnerability flagged in the previous review. This prevents attackers from bypassing private IP checks via HTTP redirects.

apps/api/src/trpc.ts (1)

5-44: Type safety improvements look good.

The removal of as any from the protectedProcedure export (line 44) properly addresses the previous review concern. The new approach using ProtectedContext type and typed middleware is a significant improvement. The remaining cast at line 32 is acceptable since it's narrowing the context type after runtime validation confirms the user exists.

Based on learnings and coding guidelines, this avoids the any type and uses proper TypeScript types as required.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c25bf86 and a5722c4.

📒 Files selected for processing (2)
  • apps/api/src/trpc.ts (3 hunks)
  • apps/api/src/utils/avatar-validator.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'
Remove unused imports
Use UPPER_SNAKE_CASE for constants
Use camelCase for functions and variables

Files:

  • apps/api/src/utils/avatar-validator.ts
  • apps/api/src/trpc.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,ts}: Prefer functional components with TypeScript and use proper TypeScript types, avoid any
Extract reusable logic into custom hooks
Use descriptive prop names and define prop types using TypeScript interfaces or types
Prefer controlled components over uncontrolled
Use zustand for global state (located in src/store/)
Use absolute imports from @/ prefix when available
Include proper aria labels for accessibility
Ensure keyboard navigation works
Maintain proper heading hierarchy
Provide alt text for images
Avoid unnecessary re-renders

Files:

  • apps/api/src/utils/avatar-validator.ts
  • apps/api/src/trpc.ts
**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (.cursorrules)

Organize imports: react → third-party → local components → utils → types

Files:

  • apps/api/src/utils/avatar-validator.ts
  • apps/api/src/trpc.ts
**/src/{lib,utils}/**

📄 CodeRabbit inference engine (.cursorrules)

Use kebab-case or camelCase for utility file names

Files:

  • apps/api/src/utils/avatar-validator.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Use PascalCase for types and interfaces with descriptive names
Use dynamic imports for code splitting when appropriate
Memoize expensive computations

Files:

  • apps/api/src/utils/avatar-validator.ts
  • apps/api/src/trpc.ts
**/*.{js,jsx,ts,tsx,py,java,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/general_rules.mdc)

**/*.{js,jsx,ts,tsx,py,java,go,rb,php}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'

Files:

  • apps/api/src/utils/avatar-validator.ts
  • apps/api/src/trpc.ts
apps/api/src/**/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

apps/api/src/**/*.{js,ts}: Log errors with context (userId, endpoint, timestamp) for debugging
Always await async operations; never forget to handle promise rejections
Never log sensitive information (passwords, tokens, credit card numbers)

Files:

  • apps/api/src/utils/avatar-validator.ts
  • apps/api/src/trpc.ts
apps/api/src/**/*.ts

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Avoid any type; use unknown for truly dynamic data and narrow with type guards

Files:

  • apps/api/src/utils/avatar-validator.ts
  • apps/api/src/trpc.ts
apps/api/src/**/{middleware,logging,utils}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Include correlation IDs in logs for request tracing across services

Files:

  • apps/api/src/utils/avatar-validator.ts
apps/api/src/**/{logging,middleware,utils}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Use structured logging (JSON format) for easier parsing and analysis

Files:

  • apps/api/src/utils/avatar-validator.ts
🧠 Learnings (8)
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{routes,middleware}/*.{js,ts} : Use middleware for auth checks; never duplicate auth logic in route handlers

Applied to files:

  • apps/api/src/trpc.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{routes,controllers,handlers,middleware}/*.{js,ts} : Always validate user authentication before processing protected routes

Applied to files:

  • apps/api/src/trpc.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/*.ts : Avoid `any` type; use `unknown` for truly dynamic data and narrow with type guards

Applied to files:

  • apps/api/src/trpc.ts
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts} : Use proper TypeScript types and avoid using `any` type

Applied to files:

  • apps/api/src/trpc.ts
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Prefer functional components with TypeScript and use proper TypeScript types, avoid `any`

Applied to files:

  • apps/api/src/trpc.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{routes,controllers,handlers,middleware}/*.{js,ts} : Use zod or similar for runtime validation of incoming data

Applied to files:

  • apps/api/src/trpc.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{routes,controllers,handlers,types}/*.ts : Define strict TypeScript interfaces for request/response bodies

Applied to files:

  • apps/api/src/trpc.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{database,models,services}/*.{js,ts} : Always use parameterized queries or ORM methods to prevent SQL injection

Applied to files:

  • apps/api/src/trpc.ts
🧬 Code graph analysis (2)
apps/api/src/utils/avatar-validator.ts (1)
apps/api/test-ddos.js (1)
  • response (28-28)
apps/api/src/trpc.ts (1)
apps/api/src/context.ts (1)
  • Context (26-26)

@apsinghdev
Copy link
Owner

i've tested this branch and found some suggestions:

pls take a look

  1. rename "testimonial" to "what you think about me?" in the form
  2. testimonials shouldn't be updated once submitted.
  3. also provide an option to add the social
    • just one link - either linkedin, x or whatever)
    • validation is required (only support - twitter, linkedin, insta and yt)
    • it should be optional tho
    • we can show it like this
Screenshot 2025-12-17 at 12 51 01 PM
  1. i tried making a payment but after the successful payment, it didn't redirected me to /checkout page. pls check.

@huamanraj
Copy link
Collaborator Author

huamanraj commented Dec 17, 2025

@apsinghdev fixed it!

optional social link and icon based on link input

image

button invisible after testimonial get submitted

image

also payment issue fixed

Copy link
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

♻️ Duplicate comments (3)
apps/api/src/routers/testimonial.ts (2)

14-14: Remove any type annotations; let tRPC infer types.

Per coding guidelines, avoid any type. tRPC should correctly infer context types from procedure definitions.

-    getAll: publicProcedure.query(async ({ ctx }: any) => {
+    getAll: publicProcedure.query(async ({ ctx }) => {
-    getMyTestimonial: protectedProcedure.query(async ({ ctx }: any) => {
+    getMyTestimonial: protectedProcedure.query(async ({ ctx }) => {
-        .mutation(async ({ ctx, input }: any) => {
+        .mutation(async ({ ctx, input }) => {

Also applies to: 37-37, 81-81


43-48: Misleading error message for read operation.

The error message says "submit testimonials" but this is getMyTestimonial (a read operation).

         if (!isPaidUser) {
             throw new TRPCError({
                 code: "FORBIDDEN",
-                message: "Only premium users can submit testimonials",
+                message: "Only premium users can view their testimonials",
             });
         }
apps/web/src/app/(main)/testimonials/submit/page.tsx (1)

72-74: Remove unnecessary any type casts on trpc.

The tRPC client is properly typed with AppRouter. These casts bypass type checking and violate coding guidelines.

-  const { data, isLoading: isDataLoading } = (
-    trpc as any
-  ).testimonial.getMyTestimonial.useQuery(undefined, {
+  const { data, isLoading: isDataLoading } = trpc.testimonial.getMyTestimonial.useQuery(undefined, {
-  const submitMutation = (trpc as any).testimonial.submit.useMutation({
+  const submitMutation = trpc.testimonial.submit.useMutation({

Also applies to: 89-89

🧹 Nitpick comments (8)
apps/web/src/app/(main)/testimonials/page.tsx (3)

6-6: Consider using project's custom icon components.

The project has custom Twitter and Youtube icons in apps/web/src/components/icons/icons.tsx. Consider using these for consistency across the codebase, unless lucide-react icons are preferred project-wide.


28-32: Remove duplicate type definition.

ImageTestimonial is already defined and exported from apps/web/src/data/testimonials.ts. Import it instead of redefining.

+import { imageTestimonials, ImageTestimonial } from "@/data/testimonials";
-import { imageTestimonials } from "@/data/testimonials";

-type ImageTestimonial = TestimonialBase & {
-  type: "image";
-  imageUrl: string;
-  alt: string;
-};

61-61: Replace hardcoded hex values with design tokens.

Per coding guidelines, avoid hardcoded hex values like #252525 and #101010. Use semantic color classes from the design token system (e.g., bg-surface-primary, border-border-primary).

Example locations to update:

  • Line 61: border-[#252525] → use border token
  • Line 80: border-[#252525] → use border token
  • Line 170: bg-[#101010] → use background token

Also applies to: 80-80, 170-170

apps/api/src/routers/testimonial.ts (2)

18-21: Consider using structured logging instead of console.log.

For production observability, consider using a structured logger with context (e.g., request ID, timestamp) instead of raw console.log. Per coding guidelines: "Log errors with context (userId, endpoint, timestamp) for debugging."

Also applies to: 32-32


91-93: Remove extra blank lines.

             if (!isPaidUser) {
                 throw new TRPCError({
                     code: "FORBIDDEN",
                     message: "Only premium users can submit testimonials",
                 });
             }
-
-

             // Check if testimonial already exists - prevent updates
apps/web/src/app/(main)/testimonials/submit/page.tsx (3)

96-98: Type the error parameter properly.

Avoid any for the error parameter. Use tRPC's error type for proper type safety.

+import { TRPCClientError } from "@trpc/client";

-    onError: (error: any) => {
+    onError: (error: TRPCClientError<any>) => {
       setError(error.message || "Error submitting testimonial");
     },

289-292: Consider using a local fallback image.

The fallback uses an external service (pravatar.cc). Consider using a local placeholder image for better reliability and privacy.


155-155: Replace hardcoded hex values with design tokens.

Similar to the testimonials list page, replace hardcoded colors like bg-[#101010] and border-[#252525] with semantic design token classes.

Also applies to: 167-167, 253-253

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 37b6cbd and baf013a.

📒 Files selected for processing (7)
  • apps/api/prisma/schema.prisma (1 hunks)
  • apps/api/src/routers/testimonial.ts (1 hunks)
  • apps/web/src/app/(main)/dashboard/pro/dashboard/page.tsx (3 hunks)
  • apps/web/src/app/(main)/testimonials/page.tsx (1 hunks)
  • apps/web/src/app/(main)/testimonials/submit/page.tsx (1 hunks)
  • apps/web/src/components/checkout/CheckoutWrapper.tsx (1 hunks)
  • apps/web/src/hooks/useSubscription.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/api/prisma/schema.prisma
  • apps/web/src/app/(main)/dashboard/pro/dashboard/page.tsx
🧰 Additional context used
📓 Path-based instructions (13)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'
Remove unused imports
Use UPPER_SNAKE_CASE for constants
Use camelCase for functions and variables

Files:

  • apps/web/src/hooks/useSubscription.ts
  • apps/api/src/routers/testimonial.ts
  • apps/web/src/app/(main)/testimonials/page.tsx
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
  • apps/web/src/components/checkout/CheckoutWrapper.tsx
apps/web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

apps/web/src/**/*.{ts,tsx}: Always follow the design system defined in apps/web/src/lib/design-tokens.ts and apps/web/tailwind.config.ts
NEVER use hardcoded hex values (e.g., #5519f7) directly in components; ALWAYS reference colors from the design token system using Tailwind classes
Use semantic color names that describe purpose, not appearance
Use font-sans for standard UI text (Geist Sans) and font-mono for code, technical content, or monospace needs (DM Mono)
Follow Tailwind's spacing scale (0.25rem increments); for section padding use mobile p-4 (1rem) and desktop p-[60px]
Use appropriate border radius: small elements rounded-lg, medium rounded-xl, large rounded-2xl, buttons rounded-[16px]
Use animation durations: fast duration-100 (0.1s), normal duration-300 (0.3s), slow duration-600 (0.6s)

Files:

  • apps/web/src/hooks/useSubscription.ts
  • apps/web/src/app/(main)/testimonials/page.tsx
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
  • apps/web/src/components/checkout/CheckoutWrapper.tsx
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,ts}: Prefer functional components with TypeScript and use proper TypeScript types, avoid any
Extract reusable logic into custom hooks
Use descriptive prop names and define prop types using TypeScript interfaces or types
Prefer controlled components over uncontrolled
Use zustand for global state (located in src/store/)
Use absolute imports from @/ prefix when available
Include proper aria labels for accessibility
Ensure keyboard navigation works
Maintain proper heading hierarchy
Provide alt text for images
Avoid unnecessary re-renders

Files:

  • apps/web/src/hooks/useSubscription.ts
  • apps/api/src/routers/testimonial.ts
  • apps/web/src/app/(main)/testimonials/page.tsx
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
  • apps/web/src/components/checkout/CheckoutWrapper.tsx
**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (.cursorrules)

Organize imports: react → third-party → local components → utils → types

Files:

  • apps/web/src/hooks/useSubscription.ts
  • apps/api/src/routers/testimonial.ts
  • apps/web/src/app/(main)/testimonials/page.tsx
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
  • apps/web/src/components/checkout/CheckoutWrapper.tsx
**/*[A-Z]*.{tsx,ts}

📄 CodeRabbit inference engine (.cursorrules)

Use PascalCase for component file names (e.g., UserProfile.tsx)

Files:

  • apps/web/src/hooks/useSubscription.ts
  • apps/web/src/components/checkout/CheckoutWrapper.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Use PascalCase for types and interfaces with descriptive names
Use dynamic imports for code splitting when appropriate
Memoize expensive computations

Files:

  • apps/web/src/hooks/useSubscription.ts
  • apps/api/src/routers/testimonial.ts
  • apps/web/src/app/(main)/testimonials/page.tsx
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
  • apps/web/src/components/checkout/CheckoutWrapper.tsx
apps/web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursorrules)

Optimize images using next/image

apps/web/src/**/*.{tsx,ts}: Use Zustand for global state, located in src/store/
Use PascalCase for types and interfaces with descriptive names
Use dynamic imports for code splitting when appropriate
Optimize images using next/image
Memoize expensive computations
Define a type when defining const functions

Files:

  • apps/web/src/hooks/useSubscription.ts
  • apps/web/src/app/(main)/testimonials/page.tsx
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
  • apps/web/src/components/checkout/CheckoutWrapper.tsx
**/*.{js,jsx,ts,tsx,py,java,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/general_rules.mdc)

**/*.{js,jsx,ts,tsx,py,java,go,rb,php}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'

Files:

  • apps/web/src/hooks/useSubscription.ts
  • apps/api/src/routers/testimonial.ts
  • apps/web/src/app/(main)/testimonials/page.tsx
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
  • apps/web/src/components/checkout/CheckoutWrapper.tsx
apps/web/src/**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (apps/web/.cursor/rules/frontend_rules.mdc)

apps/web/src/**/*.{tsx,ts,jsx,js}: Organize imports in order: React → third-party → local components → utils → types
Use absolute imports from @/ prefix when available
Remove unused imports
Use UPPER_SNAKE_CASE for constants
Use camelCase for functions and variables
Always use Tailwind classes for styling HTML elements; avoid using CSS or style tags
Use descriptive variable and function names; name event functions with a 'handle' prefix (e.g., handleClick, handleKeyDown)
Use const with arrow functions instead of function declarations (e.g., 'const toggle = () =>')

Files:

  • apps/web/src/hooks/useSubscription.ts
  • apps/web/src/app/(main)/testimonials/page.tsx
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
  • apps/web/src/components/checkout/CheckoutWrapper.tsx
apps/api/src/**/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

apps/api/src/**/*.{js,ts}: Log errors with context (userId, endpoint, timestamp) for debugging
Always await async operations; never forget to handle promise rejections
Never log sensitive information (passwords, tokens, credit card numbers)

Files:

  • apps/api/src/routers/testimonial.ts
apps/api/src/**/*.ts

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Avoid any type; use unknown for truly dynamic data and narrow with type guards

Files:

  • apps/api/src/routers/testimonial.ts
apps/web/src/components/**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (apps/web/.cursor/rules/frontend_rules.mdc)

apps/web/src/components/**/*.{tsx,ts,jsx,js}: Never use hardcoded hex values directly in components; always reference colors from the design token system using Tailwind classes
Use semantic color names from the design token system that describe purpose, not appearance (e.g., bg-brand-purple, bg-surface-primary, text-text-primary)
Use font-sans for standard UI text (Geist Sans) and font-mono for code, technical content, or monospace needs (DM Mono)
Follow Tailwind's spacing scale for section padding: p-4 (1rem) on mobile, p-[60px] on desktop
Use rounded-lg (0.5rem) for small elements, rounded-xl (1rem) for medium elements, rounded-2xl (1.5rem) for large elements, and rounded-[16px] for buttons
Use duration-100 (0.1s) for fast transitions, duration-300 (0.3s) for normal transitions, and duration-600 (0.6s) for slow transitions
Use available custom animations: animate-accordion-down, animate-accordion-up, animate-scrollRight, animate-scrollLeft, animate-customspin, animate-spin-slow, animate-spin-slow-reverse, animate-marquee, animate-marquee-vertical, animate-shine
Prefer functional components with TypeScript
Extract reusable logic into custom hooks
Prefer controlled components over uncontrolled
Include proper aria labels for accessibility
Ensure keyboard navigation works in interactive components
Maintain proper heading hierarchy in page components
Provide alt text for images
Use 'class:' instead of the ternary operator in class tags whenever possible
Implement accessibility features on interactive elements (e.g., tabindex='0', aria-label, onClick, onKeyDown)
Always follow the design system defined in apps/web/src/lib/design-tokens.ts and apps/web/tailwind.config.ts

Files:

  • apps/web/src/components/checkout/CheckoutWrapper.tsx
apps/web/src/components/**/*.{tsx,ts}

📄 CodeRabbit inference engine (apps/web/.cursor/rules/frontend_rules.mdc)

apps/web/src/components/**/*.{tsx,ts}: Use proper TypeScript types and avoid using any type
Use descriptive prop names and define prop types using TypeScript interfaces or types
Name components using PascalCase (e.g., UserProfile.tsx)

Files:

  • apps/web/src/components/checkout/CheckoutWrapper.tsx
🧠 Learnings (11)
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts} : Use proper TypeScript types and avoid using `any` type

Applied to files:

  • apps/api/src/routers/testimonial.ts
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/*.ts : Avoid `any` type; use `unknown` for truly dynamic data and narrow with type guards

Applied to files:

  • apps/api/src/routers/testimonial.ts
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Prefer functional components with TypeScript and use proper TypeScript types, avoid `any`

Applied to files:

  • apps/api/src/routers/testimonial.ts
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{routes,controllers,handlers,types}/*.ts : Define strict TypeScript interfaces for request/response bodies

Applied to files:

  • apps/api/src/routers/testimonial.ts
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{routes,controllers,handlers,middleware}/*.{js,ts} : Use zod or similar for runtime validation of incoming data

Applied to files:

  • apps/api/src/routers/testimonial.ts
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/**/*.{tsx,ts} : Use PascalCase for types and interfaces with descriptive names

Applied to files:

  • apps/api/src/routers/testimonial.ts
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{services,repositories}/*.{js,ts} : Avoid N+1 queries; use eager loading or batch queries when fetching related data

Applied to files:

  • apps/api/src/routers/testimonial.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/*.{js,ts} : Always await async operations; never forget to handle promise rejections

Applied to files:

  • apps/api/src/routers/testimonial.ts
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Use descriptive prop names and define prop types using TypeScript interfaces or types

Applied to files:

  • apps/api/src/routers/testimonial.ts
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts} : Use descriptive prop names and define prop types using TypeScript interfaces or types

Applied to files:

  • apps/api/src/routers/testimonial.ts
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Avoid unnecessary comments; code should be self-documenting when possible

Applied to files:

  • apps/web/src/app/(main)/testimonials/submit/page.tsx
🧬 Code graph analysis (4)
apps/api/src/routers/testimonial.ts (4)
apps/api/src/trpc.ts (3)
  • router (42-42)
  • publicProcedure (43-43)
  • protectedProcedure (44-44)
packages/shared/types/redis.ts (1)
  • redisCache (72-148)
apps/api/src/services/user.service.ts (1)
  • userService (5-89)
apps/api/src/utils/avatar-validator.ts (1)
  • validateAvatarUrl (46-159)
apps/web/src/app/(main)/testimonials/page.tsx (4)
apps/web/src/data/testimonials.ts (2)
  • ImageTestimonial (2-7)
  • imageTestimonials (9-40)
apps/web/src/components/icons/icons.tsx (2)
  • Twitter (175-177)
  • Youtube (200-202)
apps/web/src/lib/trpc.ts (1)
  • trpc (4-4)
apps/web/src/components/ui/skeleton.tsx (1)
  • Skeleton (15-15)
apps/web/src/app/(main)/testimonials/submit/page.tsx (7)
apps/api/src/trpc.ts (1)
  • router (42-42)
apps/web/src/lib/auth/config.ts (1)
  • session (43-49)
apps/web/src/hooks/useSubscription.ts (1)
  • useSubscription (11-79)
apps/web/src/lib/trpc.ts (1)
  • trpc (4-4)
apps/web/src/components/ui/label.tsx (1)
  • Label (26-26)
apps/web/src/components/ui/input.tsx (1)
  • Input (22-22)
apps/web/src/components/ui/textarea.tsx (1)
  • Textarea (22-22)
apps/web/src/components/checkout/CheckoutWrapper.tsx (1)
apps/web/src/hooks/useSubscription.ts (1)
  • useSubscription (11-79)
🔇 Additional comments (12)
apps/web/src/app/(main)/testimonials/page.tsx (4)

37-56: LGTM!

The getSocialIcon helper correctly parses URLs and handles errors gracefully with a null fallback.


58-115: Well-structured component with proper type narrowing.

Good use of discriminated union for type narrowing and proper accessibility attributes on the social link.


123-167: LGTM!

The interleaving logic is correct and useMemo appropriately memoizes the computation with the correct dependency.


220-236: LGTM! Empty state logic is now correct.

The conditions are properly mutually exclusive - the grid renders only when allTestimonials.length > 0, and the empty state only when length === 0.

apps/api/src/routers/testimonial.ts (2)

59-80: LGTM! Comprehensive input validation.

Good use of zod with detailed constraints. The avatar URL is properly validated via validateAvatarUrl which implements SSRF protections.


94-104: LGTM! Testimonial immutability properly enforced.

The check for existing testimonial with clear error message correctly implements the PR requirement that testimonials cannot be edited once submitted.

apps/web/src/hooks/useSubscription.ts (1)

28-28: LGTM! Exposing refetch is appropriate.

The addition of refetch to the hook's return value is a clean change that enables consumers (like CheckoutWrapper) to manually trigger subscription status refresh.

Also applies to: 77-77

apps/web/src/components/checkout/CheckoutWrapper.tsx (2)

15-29: LGTM! Retry logic is well-implemented.

Good use of cleanup to clear timeout, defensive optional chaining on refetch, and reasonable retry parameters (3 attempts, 2s intervals).


32-40: LGTM! Good loading state UX.

The dynamic message ("Loading..." vs "Verifying payment...") provides appropriate feedback during different phases.

apps/web/src/app/(main)/testimonials/submit/page.tsx (3)

45-62: LGTM! Client-side validation mirrors server schema.

Good practice to have consistent validation on both client and server. The schema correctly validates name length, content bounds, and social link platforms.


133-146: LGTM! Submit handler with proper validation.

Good check for avatar existence before submission with clear error message.


164-199: Well-structured access control states.

Good UX with clear messaging for unauthenticated, non-premium, and already-submitted states. Each includes appropriate CTAs.

Also applies to: 201-236, 254-273

Copy link
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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
apps/api/src/services/payment.service.ts (5)

58-68: Add context to error logging.

The error log on line 65 lacks context such as planId and timestamp. This makes debugging production issues difficult.

As per coding guidelines, errors should be logged with context (userId, endpoint, timestamp) for debugging.

Apply this diff to improve error logging:

     } catch (error) {
-      console.error("Error fetching plan:", error);
+      console.error("error fetching plan:", {
+        planId,
+        error: error instanceof Error ? error.message : error,
+        timestamp: new Date().toISOString(),
+      });
       throw new Error("Failed to fetch plan");

73-105: Add timeout for external API call and avoid any type.

This method has several issues:

  1. The Razorpay API call on line 77 has no timeout, which can cause hanging requests if Razorpay is slow or unresponsive.
  2. Line 85 uses any type instead of unknown.
  3. Missing logging for successful order creation, which is a critical operation.

As per coding guidelines, implement timeouts for external API calls and avoid any type.

Consider:

  1. Wrapping the Razorpay call with a timeout mechanism or configuring timeout at the client level.
  2. Changing error: any to error: unknown and using type guards.
  3. Adding structured logging for successful order creation.
-    } catch (error: any) {
+    } catch (error: unknown) {
-      if (error.error) {
+      if (error && typeof error === 'object' && 'error' in error) {
         return {
-          error: error.error,
+          error: (error as any).error,
         } as RazorpayError;
       }
 
       // Handle unexpected errors
       return {
         error: {
           code: "INTERNAL_ERROR",
-          description: error.message || "An unexpected error occurred",
+          description: error instanceof Error ? error.message : "an unexpected error occurred",

110-138: Add input validation for security-critical signature verification.

This security-critical method lacks input validation. Empty or invalid inputs could lead to incorrect verification results. Additionally, successful signature verifications should be logged for audit trails.

As per coding guidelines, log all critical operations (auth attempts, payment processing, data mutations).

Apply this diff:

   verifyPaymentSignature(
     orderId: string,
     paymentId: string,
     signature: string
   ): boolean {
     try {
+      // validate inputs
+      if (!orderId || !paymentId || !signature) {
+        console.error("signature verification failed: missing required parameters");
+        return false;
+      }
+
       const keySecret = process.env.RAZORPAY_KEY_SECRET;
       if (!keySecret) {
         throw new Error("RAZORPAY_KEY_SECRET not configured");
       }
 
       // Create the expected signature
       const generatedSignatureHex = crypto
         .createHmac("sha256", keySecret)
         .update(`${orderId}|${paymentId}`)
         .digest("hex");
 
       const a = Buffer.from(signature, "hex");
       const b = Buffer.from(generatedSignatureHex, "hex");
 
       if (a.length !== b.length) return false;
 
       // Compare signatures securely
-      return crypto.timingSafeEqual(a, b);
+      const isValid = crypto.timingSafeEqual(a, b);
+      if (isValid) {
+        console.log("payment signature verified successfully", {
+          orderId,
+          timestamp: new Date().toISOString(),
+        });
+      }
+      return isValid;
     } catch (error) {
-      console.error("Signature verification error:", error);
+      console.error("signature verification error:", {
+        error: error instanceof Error ? error.message : error,
+        timestamp: new Date().toISOString(),
+      });
       return false;

143-174: Replace any return type and add logging for critical operation.

This method has several issues:

  1. Return type is any on line 146 instead of a specific Prisma type.
  2. Error logging lacks context (userId, paymentId).
  3. Missing logging for successful payment creation, which is a critical operation.

As per coding guidelines, avoid any type and log all critical operations.

Apply this diff:

   async createPaymentRecord(
     userId: string,
     paymentData: PaymentData
-  ): Promise<any> {
+  ): Promise<import('@prisma/client').Payment> {
     try {
       // Check if payment already exists (idempotency)
       const existingPayment = await prisma.payment.findUnique({
         where: { razorpayPaymentId: paymentData.razorpayPaymentId },
       });
 
       if (existingPayment) {
+        console.log("payment record already exists (idempotent)", {
+          userId,
+          paymentId: existingPayment.id,
+          timestamp: new Date().toISOString(),
+        });
         return existingPayment;
       }
 
       // Create new payment record
       const payment = await prisma.payment.create({
         data: {
           userId,
           razorpayPaymentId: paymentData.razorpayPaymentId,
           razorpayOrderId: paymentData.razorpayOrderId,
           amount: paymentData.amount, // Amount in paise (smallest currency unit)
           currency: paymentData.currency,
           status: PAYMENT_STATUS.CAPTURED,
         },
       });
 
+      console.log("payment record created successfully", {
+        userId,
+        paymentId: payment.id,
+        amount: payment.amount,
+        timestamp: new Date().toISOString(),
+      });
+
       return payment;
     } catch (error) {
-      console.error("Error creating payment record:", error);
+      console.error("error creating payment record:", {
+        userId,
+        razorpayPaymentId: paymentData.razorpayPaymentId,
+        error: error instanceof Error ? error.message : error,
+        timestamp: new Date().toISOString(),
+      });
       throw new Error("Failed to create payment record");

179-256: Wrap subscription creation and payment update in a transaction.

This method has a critical data consistency issue: the subscription creation (line 234) and payment update (line 246) are two separate database operations without a transaction wrapper. If the payment update fails after subscription creation, the database will be in an inconsistent state with an orphaned subscription.

Additional issues:

  1. Return type is any (line 183) instead of a specific Prisma type.
  2. Error logging lacks context (userId, planId, paymentId).
  3. Missing logging for successful subscription creation (critical operation).

As per coding guidelines, wrap database transactions in try-catch blocks with proper rollback on failure.

Apply this diff:

   async createSubscription(
     userId: string,
     planId: string,
     paymentId: string
-  ): Promise<any> {
+  ): Promise<import('@prisma/client').Subscription> {
     try {
       // Get plan details
       const plan = await prisma.plan.findUnique({
         where: { id: planId },
       });
 
       if (!plan) {
         throw new Error("Plan not found");
       }
 
       // Calculate end date - Currently only yearly plan is supported
       const startDate = new Date();
       const endDate = new Date(startDate);
 
       // Set subscription for 1 year (yearly plan)
       endDate.setFullYear(endDate.getFullYear() + 1);
 
       // Future plan intervals (commented out for now):
       // switch (plan.interval.toLowerCase()) {
       //   case "monthly":
       //     endDate.setMonth(endDate.getMonth() + 1);
       //     break;
       //   case "quarterly":
       //     endDate.setMonth(endDate.getMonth() + 3);
       //     break;
       //   case "yearly":
       //   case "annual":
       //     endDate.setFullYear(endDate.getFullYear() + 1);
       //     break;
       //   default:
       //     endDate.setFullYear(endDate.getFullYear() + 1);
       // }
 
       // Check if user already has an active subscription for this payment
       const existingSubscription = await prisma.subscription.findFirst({
         where: {
           userId,
           payments: {
             some: {
               id: paymentId,
             },
           },
         },
       });
 
       if (existingSubscription) {
+        console.log("subscription already exists for payment (idempotent)", {
+          userId,
+          subscriptionId: existingSubscription.id,
+          timestamp: new Date().toISOString(),
+        });
         return existingSubscription;
       }
 
-      // Create subscription
-      const subscription = await prisma.subscription.create({
-        data: {
-          userId,
-          planId,
-          status: SUBSCRIPTION_STATUS.ACTIVE,
-          startDate,
-          endDate,
-          autoRenew: true,
-        },
-      });
-
-      // Link payment to subscription
-      await prisma.payment.update({
-        where: { id: paymentId },
-        data: { subscriptionId: subscription.id },
+      // use transaction to ensure data consistency
+      const subscription = await prisma.$transaction(async (tx) => {
+        // create subscription
+        const newSubscription = await tx.subscription.create({
+          data: {
+            userId,
+            planId,
+            status: SUBSCRIPTION_STATUS.ACTIVE,
+            startDate,
+            endDate,
+            autoRenew: true,
+          },
+        });
+
+        // link payment to subscription
+        await tx.payment.update({
+          where: { id: paymentId },
+          data: { subscriptionId: newSubscription.id },
+        });
+
+        return newSubscription;
       });
 
+      console.log("subscription created successfully", {
+        userId,
+        subscriptionId: subscription.id,
+        planId,
+        timestamp: new Date().toISOString(),
+      });
+
       return subscription;
     } catch (error) {
-      console.error("Error creating subscription:", error);
+      console.error("error creating subscription:", {
+        userId,
+        planId,
+        paymentId,
+        error: error instanceof Error ? error.message : error,
+        timestamp: new Date().toISOString(),
+      });
       throw new Error("Failed to create subscription");
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between baf013a and 0aea504.

📒 Files selected for processing (2)
  • apps/api/src/services/payment.service.ts (1 hunks)
  • apps/web/src/app/(main)/(landing)/pitch/page.tsx (19 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/web/src/app/(main)/(landing)/pitch/page.tsx
🧰 Additional context used
📓 Path-based instructions (16)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'
Remove unused imports
Use UPPER_SNAKE_CASE for constants
Use camelCase for functions and variables

Files:

  • apps/api/src/services/payment.service.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,ts}: Prefer functional components with TypeScript and use proper TypeScript types, avoid any
Extract reusable logic into custom hooks
Use descriptive prop names and define prop types using TypeScript interfaces or types
Prefer controlled components over uncontrolled
Use zustand for global state (located in src/store/)
Use absolute imports from @/ prefix when available
Include proper aria labels for accessibility
Ensure keyboard navigation works
Maintain proper heading hierarchy
Provide alt text for images
Avoid unnecessary re-renders

Files:

  • apps/api/src/services/payment.service.ts
**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (.cursorrules)

Organize imports: react → third-party → local components → utils → types

Files:

  • apps/api/src/services/payment.service.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Use PascalCase for types and interfaces with descriptive names
Use dynamic imports for code splitting when appropriate
Memoize expensive computations

Files:

  • apps/api/src/services/payment.service.ts
**/*.{js,jsx,ts,tsx,py,java,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/general_rules.mdc)

**/*.{js,jsx,ts,tsx,py,java,go,rb,php}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'

Files:

  • apps/api/src/services/payment.service.ts
apps/api/src/**/{services,models,database}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Encrypt sensitive data (passwords, tokens, API keys) before storing in database

Files:

  • apps/api/src/services/payment.service.ts
apps/api/src/**/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

apps/api/src/**/*.{js,ts}: Log errors with context (userId, endpoint, timestamp) for debugging
Always await async operations; never forget to handle promise rejections
Never log sensitive information (passwords, tokens, credit card numbers)

Files:

  • apps/api/src/services/payment.service.ts
apps/api/src/**/{database,services}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Handle database connection failures gracefully with retry logic

Files:

  • apps/api/src/services/payment.service.ts
apps/api/src/**/{database,models,services}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Always use parameterized queries or ORM methods to prevent SQL injection

Files:

  • apps/api/src/services/payment.service.ts
apps/api/src/**/{services,database}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Wrap database transactions in try-catch blocks with proper rollback on failure

Files:

  • apps/api/src/services/payment.service.ts
apps/api/src/**/{services,repositories}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Avoid N+1 queries; use eager loading or batch queries when fetching related data

Files:

  • apps/api/src/services/payment.service.ts
apps/api/src/**/{services,models}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Validate data against schema before database operations

Files:

  • apps/api/src/services/payment.service.ts
apps/api/src/**/*.ts

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Avoid any type; use unknown for truly dynamic data and narrow with type guards

Files:

  • apps/api/src/services/payment.service.ts
apps/api/src/**/{database,clients,services}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Use connection pooling for database and external service clients

Files:

  • apps/api/src/services/payment.service.ts
apps/api/src/**/{services,clients,handlers}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Implement timeouts for external API calls to prevent hanging requests

Files:

  • apps/api/src/services/payment.service.ts
apps/api/src/**/{middleware,services,routes,controllers}/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Log all critical operations (auth attempts, payment processing, data mutations)

Files:

  • apps/api/src/services/payment.service.ts
🔇 Additional comments (1)
apps/api/src/services/payment.service.ts (1)

18-31: LGTM!

The interface export enables external modules to import and use this type, which aligns with the broader API surface expansion in this PR.

@apsinghdev
Copy link
Owner

apsinghdev commented Dec 18, 2025

@huamanraj everything looks fine but i don't know why am not getting to the testimonials page on clicking the button. also, getting some errors in the console.

ref

Screen.Recording.2025-12-18.at.11.05.39.PM.mov
  • also, please a add a home button on /testimonial page to go to /dashboard/home
  • one more suggestion: pls don't cache the testimonials as i think we won't need caching for it as it's only 1 review per pro user. keep the redis functionality so that we don't have to rewrite it in project search.

Copy link
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

♻️ Duplicate comments (4)
apps/api/src/routers/testimonial.ts (4)

8-8: Avoid any type; use proper typing for context.

This issue was already flagged in a previous review. Per coding guidelines, avoid any type. TRPC should infer the context type correctly from the procedure definition.

As per coding guidelines and learnings retrieved from long-term memory.


19-19: Avoid any type; use proper typing for context.

This issue was already flagged in a previous review. Per coding guidelines, avoid any type. TRPC should infer the context type correctly from the procedure definition.

As per coding guidelines and learnings retrieved from long-term memory.


25-30: Misleading error message in read operation.

This issue was already flagged in a previous review. The error message says "submit testimonials" but this is the getMyTestimonial endpoint (read operation). The message should reflect access/viewing restrictions instead.


63-63: Avoid any type; use proper typing for context.

This issue was already flagged in a previous review. Per coding guidelines, avoid any type. TRPC should infer the context type correctly from the procedure and input definitions.

As per coding guidelines and learnings retrieved from long-term memory.

🧹 Nitpick comments (1)
apps/api/src/routers/testimonial.ts (1)

74-75: Remove unnecessary blank lines.

Two consecutive empty lines here serve no purpose and reduce code readability.

🔎 Proposed fix
             }
-

-
             // Check if testimonial already exists - prevent updates
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0aea504 and cd72787.

📒 Files selected for processing (4)
  • apps/api/src/routers/testimonial.ts
  • apps/api/src/routers/user.ts
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
  • apps/web/src/components/checkout/checkout-confirmation.tsx
✅ Files skipped from review due to trivial changes (2)
  • apps/web/src/components/checkout/checkout-confirmation.tsx
  • apps/api/src/routers/user.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/src/app/(main)/testimonials/submit/page.tsx
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,js,jsx}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'
Remove unused imports
Use UPPER_SNAKE_CASE for constants
Use camelCase for functions and variables

Files:

  • apps/api/src/routers/testimonial.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{tsx,ts}: Prefer functional components with TypeScript and use proper TypeScript types, avoid any
Extract reusable logic into custom hooks
Use descriptive prop names and define prop types using TypeScript interfaces or types
Prefer controlled components over uncontrolled
Use zustand for global state (located in src/store/)
Use absolute imports from @/ prefix when available
Include proper aria labels for accessibility
Ensure keyboard navigation works
Maintain proper heading hierarchy
Provide alt text for images
Avoid unnecessary re-renders

Files:

  • apps/api/src/routers/testimonial.ts
**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (.cursorrules)

Organize imports: react → third-party → local components → utils → types

Files:

  • apps/api/src/routers/testimonial.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Use PascalCase for types and interfaces with descriptive names
Use dynamic imports for code splitting when appropriate
Memoize expensive computations

Files:

  • apps/api/src/routers/testimonial.ts
**/*.{js,jsx,ts,tsx,py,java,go,rb,php}

📄 CodeRabbit inference engine (.cursor/rules/general_rules.mdc)

**/*.{js,jsx,ts,tsx,py,java,go,rb,php}: Always use lowercase when writing comments
Avoid unnecessary comments; code should be self-documenting when possible
Use comments to explain 'why', not 'what'

Files:

  • apps/api/src/routers/testimonial.ts
apps/api/src/**/*.{js,ts}

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

apps/api/src/**/*.{js,ts}: Log errors with context (userId, endpoint, timestamp) for debugging
Always await async operations; never forget to handle promise rejections
Never log sensitive information (passwords, tokens, credit card numbers)

Files:

  • apps/api/src/routers/testimonial.ts
apps/api/src/**/*.ts

📄 CodeRabbit inference engine (apps/api/.cursor/rules/backend_rules.mdc)

Avoid any type; use unknown for truly dynamic data and narrow with type guards

Files:

  • apps/api/src/routers/testimonial.ts
🧠 Learnings (10)
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts} : Use proper TypeScript types and avoid using `any` type

Applied to files:

  • apps/api/src/routers/testimonial.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/*.ts : Avoid `any` type; use `unknown` for truly dynamic data and narrow with type guards

Applied to files:

  • apps/api/src/routers/testimonial.ts
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Prefer functional components with TypeScript and use proper TypeScript types, avoid `any`

Applied to files:

  • apps/api/src/routers/testimonial.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{routes,controllers,handlers,types}/*.ts : Define strict TypeScript interfaces for request/response bodies

Applied to files:

  • apps/api/src/routers/testimonial.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{routes,controllers,handlers,middleware}/*.{js,ts} : Use zod or similar for runtime validation of incoming data

Applied to files:

  • apps/api/src/routers/testimonial.ts
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/**/*.{tsx,ts} : Use PascalCase for types and interfaces with descriptive names

Applied to files:

  • apps/api/src/routers/testimonial.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/{services,repositories}/*.{js,ts} : Avoid N+1 queries; use eager loading or batch queries when fetching related data

Applied to files:

  • apps/api/src/routers/testimonial.ts
📚 Learning: 2025-11-25T07:34:58.984Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/api/.cursor/rules/backend_rules.mdc:0-0
Timestamp: 2025-11-25T07:34:58.984Z
Learning: Applies to apps/api/src/**/*.{js,ts} : Always await async operations; never forget to handle promise rejections

Applied to files:

  • apps/api/src/routers/testimonial.ts
📚 Learning: 2025-11-25T07:34:30.473Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T07:34:30.473Z
Learning: Applies to **/*.{tsx,ts} : Use descriptive prop names and define prop types using TypeScript interfaces or types

Applied to files:

  • apps/api/src/routers/testimonial.ts
📚 Learning: 2025-11-25T07:35:19.071Z
Learnt from: CR
Repo: apsinghdev/opensox PR: 0
File: apps/web/.cursor/rules/frontend_rules.mdc:0-0
Timestamp: 2025-11-25T07:35:19.071Z
Learning: Applies to apps/web/src/components/**/*.{tsx,ts} : Use descriptive prop names and define prop types using TypeScript interfaces or types

Applied to files:

  • apps/api/src/routers/testimonial.ts
🧬 Code graph analysis (1)
apps/api/src/routers/testimonial.ts (3)
apps/api/src/trpc.ts (3)
  • router (42-42)
  • publicProcedure (43-43)
  • protectedProcedure (44-44)
apps/api/src/services/user.service.ts (1)
  • userService (5-89)
apps/api/src/utils/avatar-validator.ts (1)
  • validateAvatarUrl (46-159)
🔇 Additional comments (1)
apps/api/src/routers/testimonial.ts (1)

88-89: Good implementation of avatar URL validation.

The addition of validateAvatarUrl properly addresses the server-side security validation concerns raised in previous reviews. The validation function (shown in relevant code snippets) implements HTTPS-only, IP blocking, domain allowlist, HEAD request validation, and content-type/size checks.

Comment on lines +46 to +61
socialLink: z.string().url("Invalid social link URL").refine((url) => {
const supportedPlatforms = [
'twitter.com',
'x.com',
'linkedin.com',
'instagram.com',
'youtube.com',
'youtu.be',
];
try {
const parsedUrl = new URL(url);
return supportedPlatforms.some(platform => parsedUrl.hostname.includes(platform));
} catch {
return false;
}
}, "Only Twitter/X, LinkedIn, Instagram, and YouTube links are supported").optional().or(z.literal('')),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix insecure hostname validation that allows malicious domains.

The hostname.includes(platform) check is vulnerable to subdomain attacks. For example, malicious-twitter.com or evil-linkedin.com.attacker.net would pass validation because their hostnames contain the platform strings.

🔎 Proposed fix using exact hostname matching
-            socialLink: z.string().url("Invalid social link URL").refine((url) => {
-                const supportedPlatforms = [
-                    'twitter.com',
-                    'x.com',
-                    'linkedin.com',
-                    'instagram.com',
-                    'youtube.com',
-                    'youtu.be',
-                ];
-                try {
-                    const parsedUrl = new URL(url);
-                    return supportedPlatforms.some(platform => parsedUrl.hostname.includes(platform));
-                } catch {
-                    return false;
-                }
-            }, "Only Twitter/X, LinkedIn, Instagram, and YouTube links are supported").optional().or(z.literal('')),
+            socialLink: z.string().url("Invalid social link URL").refine((url) => {
+                const supportedPlatforms = [
+                    'twitter.com',
+                    'x.com',
+                    'linkedin.com',
+                    'instagram.com',
+                    'youtube.com',
+                    'youtu.be',
+                ];
+                try {
+                    const parsedUrl = new URL(url);
+                    const hostname = parsedUrl.hostname;
+                    // exact match or subdomain of allowed platform
+                    return supportedPlatforms.some(platform => 
+                        hostname === platform || hostname.endsWith(`.${platform}`)
+                    );
+                } catch {
+                    return false;
+                }
+            }, "Only Twitter/X, LinkedIn, Instagram, and YouTube links are supported").optional().or(z.literal('')),
🤖 Prompt for AI Agents
In apps/api/src/routers/testimonial.ts around lines 46 to 61, the hostname
validation uses hostname.includes(platform) which allows malicious hosts
containing the platform string; change it to exact-match or proper subdomain
matching: parse the URL and compare parsedUrl.hostname === platform OR
parsedUrl.hostname.endsWith('.' + platform) (this covers valid subdomains like
"www.twitter.com" or "mobile.twitter.com"), apply the same logic for each
supportedPlatforms entry (including handling short hosts like "youtu.be"), keep
the try/catch, and return false on parse errors so only exact platform hostnames
or their subdomains are allowed.

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.

6 participants