Skip to content

05: upload anything on cloudinary#4

Merged
CodingWithTushar merged 1 commit into
mainfrom
05--upload-anything-on-cloudinary
Jul 17, 2026
Merged

05: upload anything on cloudinary#4
CodingWithTushar merged 1 commit into
mainfrom
05--upload-anything-on-cloudinary

Conversation

@CodingWithTushar

@CodingWithTushar CodingWithTushar commented Jul 17, 2026

Copy link
Copy Markdown
Owner
  • Added Cloudinary upload integration (service/module and API route).

  • Updated UI (or API client) to allow submitting files for upload.

  • Added minimal validation and success/error handling for uploads.

  • Updated tests/examples demonstrating an upload (if present).

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Cloudinary dependencies and configuration were added for authenticated image and video uploads. The homepage now uses a Cloudinary upload widget, while the video route stores uploaded asset metadata in Prisma. Video.duration changed from String to Float with a corresponding database migration.

Changes

Cloudinary image upload flow

Layer / File(s) Summary
Cloudinary setup and image upload UI
package.json, lib/cloudinary.ts, app/page.tsx
Cloudinary packages and SDK configuration were added, and the homepage now uploads images through CldUploadButton and displays returned asset details.
Authenticated image upload endpoint
app/api/upload-image/route.ts
The POST route authenticates requests, validates configuration and multipart files, streams images to Cloudinary, and returns the uploaded public ID.

Video upload persistence

Layer / File(s) Summary
Video duration contract and migration
prisma/schema.prisma, prisma/migrations/.../migration.sql
Video.duration changed from String to Float, with the database column recreated as required DOUBLE PRECISION.
Video upload and database record creation
app/api/upload-video/route.ts
The route authenticates users, uploads videos to Cloudinary, creates Prisma video records from form and upload metadata, and returns the record.

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

Sequence Diagram(s)

Image upload flow

sequenceDiagram
  participant Browser
  participant CldUploadButton
  participant Cloudinary
  Browser->>CldUploadButton: Select image
  CldUploadButton->>Cloudinary: Upload with learningSaas preset
  Cloudinary-->>CldUploadButton: Return upload result
  CldUploadButton-->>Browser: Display asset details
Loading

Video upload flow

sequenceDiagram
  participant Browser
  participant VideoUploadRoute
  participant Cloudinary
  participant Prisma
  Browser->>VideoUploadRoute: Submit multipart video
  VideoUploadRoute->>Cloudinary: Upload video stream
  Cloudinary-->>VideoUploadRoute: Return asset metadata
  VideoUploadRoute->>Prisma: Create video record
  Prisma-->>VideoUploadRoute: Return persisted record
  VideoUploadRoute-->>Browser: Return JSON response
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive No pull request description was provided, so there is no meaningful description to assess. Add a short description summarizing the Cloudinary upload routes, UI changes, and schema updates.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is clearly related to the PR’s main change: adding Cloudinary-based upload support.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (1)
app/page.tsx (1)

15-40: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace inline styles with Tailwind utility classes.

Since this project uses Tailwind CSS, consider using utility classes instead of inline styles for consistency and maintainability. You can remove the wrapper div and apply the classes directly to CldUploadButton.

♻️ Proposed refactor
-      <div style={{
-        backgroundColor: '`#0070f3`',
-        color: 'white',
-        padding: '12px 24px',
-        border: 'none',
-        borderRadius: '6px',
-        fontSize: '16px',
-        fontWeight: '500',
-        cursor: 'pointer',
-        display: 'inline-block'
-      }}>
-        <CldUploadButton
-          uploadPreset="learningSaas"
-          onSuccess={(result) => {
-            if (result.info && typeof result.info !== 'string') {
-              setUploadedImage(result.info);
-              console.log('Upload successful:', result.info);
-            }
-          }}
-          onQueuesEnd={(result, { widget }) => {
-            widget.close();
-          }}
-        >
-          Upload Image
-        </CldUploadButton>
-      </div>
+      <CldUploadButton
+        className="bg-[`#0070f3`] text-white px-6 py-3 rounded-md text-base font-medium cursor-pointer inline-block"
+        uploadPreset="learningSaas"
+        onSuccess={(result) => {
+          if (result.info && typeof result.info !== 'string') {
+            setUploadedImage(result.info);
+            console.log('Upload successful:', result.info);
+          }
+        }}
+        onQueuesEnd={(result, { widget }) => {
+          widget.close();
+        }}
+      >
+        Upload Image
+      </CldUploadButton>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/page.tsx` around lines 15 - 40, Replace the inline style wrapper around
CldUploadButton with equivalent Tailwind utility classes applied directly to
CldUploadButton, then remove the unnecessary wrapper div while preserving the
existing upload callbacks and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/api/upload-image/route.ts`:
- Line 42: Correct the folder value in the upload configuration to
`learningSaas`, matching the `uploadPreset` used by the page and replacing the
misspelled `leanringSaas`.
- Around line 1-27: Update POST to import and use the shared configured
Cloudinary instance from lib/cloudinary.ts. Remove the local cloudinary.config
call and the duplicated credential environment checks, while preserving the
existing upload and authorization behavior.
- Line 19: Update the auth call in the upload-image route to await the Promise
returned by auth() before destructuring userId, preserving the existing
authentication flow so valid requests are not treated as unauthorized.

In `@app/api/upload-video/route.ts`:
- Around line 39-50: Update the upload handling around the formData parsing and
buffering: validate that title is a non-empty string, require the file to have
the allowed video MIME type, and reject files exceeding the configured size
limit before calling file.arrayBuffer() or creating the Buffer. Stop reading
originalSize from form data and derive it from file.size for downstream
processing.
- Around line 91-93: Remove the finally block containing prisma.$disconnect()
from the upload request handler, while preserving the existing request success
and error flows. Keep the shared PrismaClient exported by lib/prisma.ts alive
across requests.
- Around line 76-90: Update the catch path surrounding prisma.video.create in
the upload handler to destroy the uploaded Cloudinary asset identified by
result.public_id before returning the 500 response. Ensure cleanup is attempted
when database insertion fails while preserving the existing error response
behavior.
- Around line 20-26: Update the POST function’s authentication flow to await the
asynchronous auth() call before destructuring userId. Preserve the existing
unauthorized response for missing user IDs and the remainder of the route
behavior.

In `@lib/cloudinary.ts`:
- Around line 3-8: Update the required Cloudinary environment-variable check in
lib/cloudinary.ts to throw an informative error when any of
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, or CLOUDINARY_API_SECRET
is missing. Keep the validation before Cloudinary setup so invalid configuration
fails fast.

In `@prisma/migrations/20260716145847_float_fix/migration.sql`:
- Around line 8-9: Update the migration’s Video.duration alteration to preserve
existing values instead of dropping the column; convert the existing text values
to DOUBLE PRECISION in place when they are numeric, or backfill non-numeric/NULL
values before enforcing NOT NULL. Keep the resulting duration column non-null
and retain all existing data.

---

Nitpick comments:
In `@app/page.tsx`:
- Around line 15-40: Replace the inline style wrapper around CldUploadButton
with equivalent Tailwind utility classes applied directly to CldUploadButton,
then remove the unnecessary wrapper div while preserving the existing upload
callbacks and behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b683ac84-44bf-46e3-92dd-d7c9e7a6b2d0

📥 Commits

Reviewing files that changed from the base of the PR and between c721a64 and e068a04.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (7)
  • app/api/upload-image/route.ts
  • app/api/upload-video/route.ts
  • app/page.tsx
  • lib/cloudinary.ts
  • package.json
  • prisma/migrations/20260716145847_float_fix/migration.sql
  • prisma/schema.prisma

Comment on lines +1 to +27
import { v2 as cloudinary } from "cloudinary";
import { auth } from "@clerk/nextjs/server";
import { NextRequest, NextResponse } from "next/server";

interface CloudinaryUploadResult {
public_id : string;
[key: string] : any
}

cloudinary.config({
cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET
})

export async function POST(request: NextRequest) {

try {
const { userId } = auth();

if (!userId) {
return NextResponse.json({error: "UnAuthorized"}, {status: 401});
}

if (!process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME || !process.env.CLOUDINARY_API_KEY || !process.env.CLOUDINARY_API_SECRET) {
return NextResponse.json({error : "Cloudinary credentails not provided"}, {status: 500})
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use the shared Cloudinary instance.

You already have a configured Cloudinary instance exported from lib/cloudinary.ts. You should import and use it here instead of duplicating the configuration and environment variable checks.

♻️ Proposed refactor
-import { v2 as cloudinary } from "cloudinary";
+import cloudinary from "`@/lib/cloudinary`";
 import { auth } from "`@clerk/nextjs/server`";
 import { NextRequest, NextResponse } from "next/server";
 
 interface CloudinaryUploadResult {
     public_id : string;
     [key: string] : any
 }
 
-cloudinary.config({
-    cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
-    api_key: process.env.CLOUDINARY_API_KEY,
-    api_secret: process.env.CLOUDINARY_API_SECRET
-})
-
 export async function POST(request: NextRequest) {
     
     try {
         const { userId } = auth();  
     
         if (!userId) {
             return NextResponse.json({error: "UnAuthorized"}, {status: 401});
         }
 
-         if (!process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME || !process.env.CLOUDINARY_API_KEY || !process.env.CLOUDINARY_API_SECRET) {
-            return NextResponse.json({error : "Cloudinary credentails not provided"}, {status: 500})
-      }
-
         const formData = await request.formData();
📝 Committable suggestion

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

Suggested change
import { v2 as cloudinary } from "cloudinary";
import { auth } from "@clerk/nextjs/server";
import { NextRequest, NextResponse } from "next/server";
interface CloudinaryUploadResult {
public_id : string;
[key: string] : any
}
cloudinary.config({
cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET
})
export async function POST(request: NextRequest) {
try {
const { userId } = auth();
if (!userId) {
return NextResponse.json({error: "UnAuthorized"}, {status: 401});
}
if (!process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME || !process.env.CLOUDINARY_API_KEY || !process.env.CLOUDINARY_API_SECRET) {
return NextResponse.json({error : "Cloudinary credentails not provided"}, {status: 500})
}
import cloudinary from "`@/lib/cloudinary`";
import { auth } from "`@clerk/nextjs/server`";
import { NextRequest, NextResponse } from "next/server";
interface CloudinaryUploadResult {
public_id : string;
[key: string] : any
}
export async function POST(request: NextRequest) {
try {
const { userId } = auth();
if (!userId) {
return NextResponse.json({error: "UnAuthorized"}, {status: 401});
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/upload-image/route.ts` around lines 1 - 27, Update POST to import and
use the shared configured Cloudinary instance from lib/cloudinary.ts. Remove the
local cloudinary.config call and the duplicated credential environment checks,
while preserving the existing upload and authorization behavior.

export async function POST(request: NextRequest) {

try {
const { userId } = auth();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does auth() from @clerk/nextjs/server return a Promise in Clerk Next.js SDK version 7?

💡 Result:

Yes, in the Clerk Next.js SDK (starting from version 6 and continuing into version 7), the auth helper from @clerk/nextjs/server returns a Promise [1][2][3]. Because auth is asynchronous, you must use the await keyword when calling it [1][2][4]. This change was implemented to align with updates to Next.js's request-time APIs [2][3][4]. Example usage in an App Router context: import { auth } from '@clerk/nextjs/server'; export default async function Page { const { userId } = await auth; if (!userId) { return

Hello, guest!

; } return

Hello, {userId}!

; }

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== package.json refs ==\n'
git ls-files | rg '(^|/)package\.json$|pnpm-lock\.yaml$|package-lock\.json$|yarn\.lock$'

printf '\n== upload route file ==\n'
sed -n '1,120p' app/api/upload-image/route.ts

printf '\n== clerk usage search ==\n'
rg -n "`@clerk/nextjs`|auth\(\)|await auth|const \{ userId \} = auth\(" -S .

Repository: CodingWithTushar/learningsaas

Length of output: 3099


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the Clerk package version and any other auth call sites.
for f in package.json apps/*/package.json app/package.json; do
  [ -f "$f" ] && echo "### $f" && sed -n '1,220p' "$f"
done

echo
echo "### route file"
sed -n '1,120p' app/api/upload-image/route.ts

echo
echo "### auth call sites"
rg -n "auth\(\)|await auth|from '`@clerk/nextjs/server`'|from \"`@clerk/nextjs/server`\"" -S app .

Repository: CodingWithTushar/learningsaas

Length of output: 3922


Await auth() here.
@clerk/nextjs/server in this app returns a Promise, so destructuring it synchronously makes userId undefined and turns every request into a 401.

🐛 Proposed fix
-        const { userId } = auth();  
+        const { userId } = await auth();  
📝 Committable suggestion

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

Suggested change
const { userId } = auth();
const { userId } = await auth();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/upload-image/route.ts` at line 19, Update the auth call in the
upload-image route to await the Promise returned by auth() before destructuring
userId, preserving the existing authentication flow so valid requests are not
treated as unauthorized.

const result = await new Promise<CloudinaryUploadResult>((resolve, reject) => {
const uploadStream = cloudinary.uploader.upload_stream(
//Folder name for the Cloudinary
{folder: "leanringSaas"},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Fix typo in the folder name.

The folder name leanringSaas contains a typo and should likely be learningSaas to match the upload preset used in app/page.tsx (uploadPreset="learningSaas").

📝 Proposed fix
                 //Folder name for the Cloudinary
-                {folder: "leanringSaas"},
+                {folder: "learningSaas"},
📝 Committable suggestion

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

Suggested change
{folder: "leanringSaas"},
{folder: "learningSaas"},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/upload-image/route.ts` at line 42, Correct the folder value in the
upload configuration to `learningSaas`, matching the `uploadPreset` used by the
page and replacing the misspelled `leanringSaas`.

Comment on lines +20 to +26
export async function POST(request: NextRequest) {
try {
const { userId } = auth();

if (!userId) {
return NextResponse.json({ error: "UnAuthorized" }, { status: 401 });
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file context =="
sed -n '1,120p' app/api/upload-video/route.ts

echo
echo "== clerk package references =="
rg -n '"`@clerk/nextjs`"|`@clerk/nextjs`' package.json pnpm-lock.yaml package-lock.json yarn.lock 2>/dev/null || true

echo
echo "== auth import/usages =="
rg -n '\bauth\(' app -g '!**/node_modules/**' || true

Repository: CodingWithTushar/learningsaas

Length of output: 3269


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== package.json =="
cat package.json 2>/dev/null | sed -n '1,220p'

echo
echo "== route file =="
cat -n app/api/upload-video/route.ts 2>/dev/null | sed -n '1,120p'

Repository: CodingWithTushar/learningsaas

Length of output: 4586


🌐 Web query:

Clerk Next.js auth() asynchronous v6 await auth() server-side route handlers docs

💡 Result:

In Clerk Next.js v6, the auth helper has been updated to be asynchronous to align with Next.js's asynchronous dynamic APIs [1][2]. Consequently, you must await the auth call when using it in server-side contexts, including Route Handlers [1][3]. When working in a Route Handler, you should define your request handler as an async function and await auth to retrieve the authentication object [1][4]. Example implementation in a Route Handler: import { auth } from '@clerk/nextjs/server'; import { NextResponse } from 'next/server'; export async function GET { // Always await auth in v6 const { userId, isAuthenticated } = await auth; if (!isAuthenticated) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } return NextResponse.json({ userId }); } Key points for v6 migration and usage: - Asynchronous auth: All calls to auth in Server Components, Server Actions, and Route Handlers must be awaited [1][5][6]. - Protecting Routes: You can use the auth.protect method, which is also asynchronous, to secure routes [5][6]. This helper will return a 404 or 401 error (depending on the request type) for unauthenticated users, or you can use standard conditional logic with auth for custom handling [4][6]. - clerkClient: Similarly, the clerkClient helper has been updated to be asynchronous and must be awaited when initialized [1][2]. For detailed migration steps, including information on codemods provided by Clerk to automate these changes, refer to the official Clerk v6 upgrade guide [1].

Citations:


🌐 Web query:

Clerk nextjs 7 auth() await auth server route handler

💡 Result:

In Next.js App Router (including versions compatible with Clerk's latest SDK), the auth() helper is asynchronous and must be awaited when used within Route Handlers [1][2][3]. To use auth() in a Route Handler, import it from @clerk/nextjs/server [1]. You have two primary approaches for handling authentication: 1. Automatic Protection: Use await auth.protect() to automatically enforce authentication. If the user is not signed in, this will return a 404 or 401 error depending on the request type [1][4]. typescript import { auth } from '`@clerk/nextjs/server`' export async function GET() { // Automatically returns a 404/401 if unauthenticated await auth.protect() // Proceed with authorized logic return Response.json({ message: 'Success' }) } 2. Manual Control: Use await auth() to retrieve the authentication state and handle unauthenticated users manually [1][4]. typescript import { auth } from '`@clerk/nextjs/server`' import { NextResponse } from 'next/server' export async function GET() { const { isAuthenticated, userId } = await auth() if (!isAuthenticated) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } // Proceed with your logic using userId return NextResponse.json({ userId }) } Key Requirements: - Configuration: Ensure clerkMiddleware() is correctly configured in your middleware file [2][3]. - Asynchronous Usage: Always use await auth() [3]. The function returns an object containing the Auth session details, including isAuthenticated and userId [1][2][5]. - Context: These helpers are exclusively for server-side use in the App Router [2][6].

Citations:


Await Clerk auth() in this route. @clerk/nextjs/server v7 exposes auth() as async, so this destructuring leaves userId undefined and rejects every request.

Fix
-    const { userId } = auth();
+    const { userId } = await auth();
📝 Committable suggestion

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

Suggested change
export async function POST(request: NextRequest) {
try {
const { userId } = auth();
if (!userId) {
return NextResponse.json({ error: "UnAuthorized" }, { status: 401 });
}
export async function POST(request: NextRequest) {
try {
const { userId } = await auth();
if (!userId) {
return NextResponse.json({ error: "UnAuthorized" }, { status: 401 });
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/upload-video/route.ts` around lines 20 - 26, Update the POST
function’s authentication flow to await the asynchronous auth() call before
destructuring userId. Preserve the existing unauthorized response for missing
user IDs and the remainder of the route behavior.

Comment on lines +39 to +50
const formData = await request.formData();
const file = formData.get("file") as File | null;
const title = formData.get("title") as string;
const description = formData.get("description") as string;
const originalSize = formData.get("originalSize") as string;

if (!file) {
return NextResponse.json({ error: "File not found" }, { status: 400 });
}

const bytes = await file.arrayBuffer();
const buffer = Buffer.from(bytes);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Validate metadata and upload limits before buffering.

The casts do not validate title or originalSize, and an arbitrarily large file is copied into memory. Require a non-empty title, validate the video MIME type and configured size limit, and derive originalSize from file.size instead of trusting the client.

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

In `@app/api/upload-video/route.ts` around lines 39 - 50, Update the upload
handling around the formData parsing and buffering: validate that title is a
non-empty string, require the file to have the allowed video MIME type, and
reject files exceeding the configured size limit before calling
file.arrayBuffer() or creating the Buffer. Stop reading originalSize from form
data and derive it from file.size for downstream processing.

Comment on lines +76 to +90
const video = await prisma.video.create({
data: {
Title: title,
description: description,
publicId: result.public_id,
originalSize: originalSize,
compressedSize: String(result.bytes),
duration: result.duration || 0,
},
});

return NextResponse.json(video);
} catch (error) {
console.log("Upload Video failed", error);
return NextResponse.json({ error: "Upload Video failed" }, { status: 500 });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file and nearby context
file="app/api/upload-video/route.ts"
wc -l "$file"
sed -n '1,180p' "$file"

Repository: CodingWithTushar/learningsaas

Length of output: 2824


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="app/api/upload-video/route.ts"
wc -l "$file"
sed -n '1,180p' "$file"

Repository: CodingWithTushar/learningsaas

Length of output: 2824


Delete the uploaded Cloudinary asset if the DB insert fails.
If prisma.video.create throws, the video stays in Cloudinary without a matching record. Add a cleanup path that destroys result.public_id before returning the error.

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

In `@app/api/upload-video/route.ts` around lines 76 - 90, Update the catch path
surrounding prisma.video.create in the upload handler to destroy the uploaded
Cloudinary asset identified by result.public_id before returning the 500
response. Ensure cleanup is attempted when database insertion fails while
preserving the existing error response behavior.

Comment on lines +91 to +93
} finally {
await prisma.$disconnect();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the route and Prisma client setup
git ls-files app/api/upload-video/route.ts
git ls-files | rg 'prisma|db|database'

echo '--- app/api/upload-video/route.ts ---'
cat -n app/api/upload-video/route.ts

echo '--- Prisma-related files (paths only) ---'
git ls-files | rg '(^|/)(prisma|db|database)(/|\.|$)|schema\.prisma$|client\.ts$|prisma\.ts$'

# Show likely Prisma client initialization files, if present
for f in $(git ls-files | rg '(^|/)(prisma|db|database)(/|\.|$)|client\.ts$|prisma\.ts$'); do
  echo "--- $f ---"
  wc -l "$f"
done

Repository: CodingWithTushar/learningsaas

Length of output: 4686


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- lib/prisma.ts ---'
cat -n lib/prisma.ts

echo '--- search for prisma.$disconnect usage ---'
rg -n '\$disconnect\s*\(' app lib prisma

echo '--- search for PrismaClient initialization ---'
rg -n 'new PrismaClient|globalThis|singleton' lib app prisma

Repository: CodingWithTushar/learningsaas

Length of output: 842


Remove the per-request Prisma disconnect. lib/prisma.ts exports a shared PrismaClient, so finally { await prisma.$disconnect(); } tears down the pool after every upload and forces the next request to reconnect. Leave the shared client alive for the process.

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

In `@app/api/upload-video/route.ts` around lines 91 - 93, Remove the finally block
containing prisma.$disconnect() from the upload request handler, while
preserving the existing request success and error flows. Keep the shared
PrismaClient exported by lib/prisma.ts alive across requests.

Comment thread lib/cloudinary.ts
Comment on lines +3 to +8
if (
!process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME ||
!process.env.CLOUDINARY_API_KEY ||
!process.env.CLOUDINARY_API_SECRET
) {
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Throw an error for missing environment variables.

The if block checking for required Cloudinary environment variables is currently empty. It should throw an error to fail fast if the configuration is missing, rather than proceeding with an invalid setup.

🐛 Proposed fix
 if (
   !process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME ||
   !process.env.CLOUDINARY_API_KEY ||
   !process.env.CLOUDINARY_API_SECRET
 ) {
+  throw new Error("Missing Cloudinary environment variables");
 }
📝 Committable suggestion

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

Suggested change
if (
!process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME ||
!process.env.CLOUDINARY_API_KEY ||
!process.env.CLOUDINARY_API_SECRET
) {
}
if (
!process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME ||
!process.env.CLOUDINARY_API_KEY ||
!process.env.CLOUDINARY_API_SECRET
) {
throw new Error("Missing Cloudinary environment variables");
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/cloudinary.ts` around lines 3 - 8, Update the required Cloudinary
environment-variable check in lib/cloudinary.ts to throw an informative error
when any of NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, or
CLOUDINARY_API_SECRET is missing. Keep the validation before Cloudinary setup so
invalid configuration fails fast.

Comment on lines +8 to +9
ALTER TABLE "Video" DROP COLUMN "duration",
ADD COLUMN "duration" DOUBLE PRECISION NOT NULL;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== files ==\n'
git ls-files 'prisma/**' | sed -n '1,120p'

printf '\n== migration.sql ==\n'
cat -n prisma/migrations/20260716145847_float_fix/migration.sql

printf '\n== schema.prisma matches for Video.duration ==\n'
rg -n 'duration|model Video' prisma -n

printf '\n== nearby migration history for Video.duration ==\n'
rg -n 'ALTER TABLE "Video"|duration' prisma/migrations -g 'migration.sql'

Repository: CodingWithTushar/learningsaas

Length of output: 2082


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "hello"

Repository: CodingWithTushar/learningsaas

Length of output: 173


Convert Video.duration in place

ALTER TABLE ... DROP COLUMN discards the existing values, and the replacement NOT NULL column will fail on any non-empty table. If the current text values are numeric, cast the column in place; otherwise backfill first.

Proposed migration
-ALTER TABLE "Video" DROP COLUMN "duration",
-ADD COLUMN     "duration" DOUBLE PRECISION NOT NULL;
+ALTER TABLE "Video"
+ALTER COLUMN "duration" TYPE DOUBLE PRECISION
+USING "duration"::DOUBLE PRECISION;
📝 Committable suggestion

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

Suggested change
ALTER TABLE "Video" DROP COLUMN "duration",
ADD COLUMN "duration" DOUBLE PRECISION NOT NULL;
ALTER TABLE "Video"
ALTER COLUMN "duration" TYPE DOUBLE PRECISION
USING "duration"::DOUBLE PRECISION;
🧰 Tools
🪛 Squawk (2.59.0)

[warning] 8-8: Dropping a column may break existing clients.

(ban-drop-column)


[warning] 9-9: Adding a new column that is NOT NULL and has no default value to an existing table effectively makes it required. Make the field nullable or add a non-VOLATILE DEFAULT

(adding-required-field)

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

In `@prisma/migrations/20260716145847_float_fix/migration.sql` around lines 8 - 9,
Update the migration’s Video.duration alteration to preserve existing values
instead of dropping the column; convert the existing text values to DOUBLE
PRECISION in place when they are numeric, or backfill non-numeric/NULL values
before enforcing NOT NULL. Keep the resulting duration column non-null and
retain all existing data.

Source: Linters/SAST tools

@CodingWithTushar
CodingWithTushar merged commit 576e0a0 into main Jul 17, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant