-
Notifications
You must be signed in to change notification settings - Fork 42
Added complete dashboard backend and frontend for dynamic data #117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
89ac256
ffa3f54
5980dd1
4975cda
96e0d5d
cf3ed91
adcbac2
ccca3cd
dc34de4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,134 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { NextRequest, NextResponse } from "next/server"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { getServerSession } from "next-auth"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { authOptions } from "@/app/api/auth/[...nextauth]/options"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import dbConnect from "@/lib/dbConnect"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import DocumentModel from "@/model/Document"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { deleteFromCloudinary, isCloudinaryConfigured, getCloudinaryFileBuffer } from "@/lib/cloudinary"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // GET /api/cases/[id]/documents/[docId] - Download a document | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export async function GET( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request: NextRequest, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { params }: { params: { id: string; docId: string } } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const session = await getServerSession(authOptions); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!session?.user?._id) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await dbConnect(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { id, docId } = await params; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Find document and verify ownership | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const document = await DocumentModel.findOne({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _id: docId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| caseId: id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| userId: session.user._id | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!document) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json({ error: "Document not found" }, { status: 404 }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check if Cloudinary is configured | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isCloudinaryConfigured()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { error: "Cloudinary is not configured. Please set up CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, and CLOUDINARY_API_SECRET environment variables." }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { status: 500 } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Update download count and last accessed | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await DocumentModel.findByIdAndUpdate(docId, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $inc: { downloadCount: 1 }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lastAccessed: new Date() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+41
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Count downloads only on successful fetch; harden Content-Disposition with ASCII fallback + RFC 5987.
- // Update download count and last accessed
- await DocumentModel.findByIdAndUpdate(docId, {
- $inc: { downloadCount: 1 },
- lastAccessed: new Date()
- });- // Return file with appropriate headers
- // Properly encode filename for HTTP headers to handle Unicode characters
- const encodedFilename = encodeURIComponent(document.originalName);
- const contentDisposition = `attachment; filename*=UTF-8''${encodedFilename}`;
-
- return new NextResponse(fileBuffer, {
+ // Update metrics after successful fetch
+ await DocumentModel.findByIdAndUpdate(docId, {
+ $inc: { downloadCount: 1 },
+ lastAccessed: new Date()
+ });
+
+ // Return file with safe, standards-compliant headers
+ const safeName = (document.originalName || "download").replace(/[\r\n"]/g, "_");
+ const encoded = encodeURIComponent(safeName);
+ const contentDisposition = `attachment; filename="${safeName}"; filename*=UTF-8''${encoded}`;
+
+ return new NextResponse(fileBuffer, {
headers: {
- "Content-Type": document.mimeType,
+ "Content-Type": document.mimeType || "application/octet-stream",
"Content-Disposition": contentDisposition,
"Content-Length": fileBuffer.length.toString(),
"Cache-Control": "no-cache",
},
});Also applies to: 62-74 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Get file buffer directly from Cloudinary using API | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log("Fetching file from Cloudinary using API:", document.cloudinaryPublicId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Determine resource type based on file type | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // const resourceType = document.mimeType === 'application/pdf' ? 'raw' : 'image'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const resourceType = document.mimeType?.startsWith('image/') ? 'image' : 'raw'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const fileBuffer = await getCloudinaryFileBuffer(document.cloudinaryPublicId, resourceType); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log("Downloaded file:", { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fileName: document.originalName, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mimeType: document.mimeType, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fileSize: document.fileSize, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bufferSize: fileBuffer.length | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Return file with appropriate headers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Properly encode filename for HTTP headers to handle Unicode characters | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const encodedFilename = encodeURIComponent(document.originalName); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const contentDisposition = `attachment; filename*=UTF-8''${encodedFilename}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new NextResponse(fileBuffer, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Content-Type": document.mimeType, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Content-Disposition": contentDisposition, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Content-Length": fileBuffer.length.toString(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Cache-Control": "no-cache", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error("Failed to fetch file from Cloudinary:", error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| error: "Failed to fetch file from Cloudinary. Please check file permissions." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, { status: 500 }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error("Error downloading document:", error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { error: "Failed to download document" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { status: 500 } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // DELETE /api/cases/[id]/documents/[docId] - Delete a document | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export async function DELETE( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| request: NextRequest, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { params }: { params: { id: string; docId: string } } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const session = await getServerSession(authOptions); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!session?.user?._id) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await dbConnect(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { id, docId } = await params; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Find document and verify ownership | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const document = await DocumentModel.findOne({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _id: docId, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| caseId: id, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| userId: session.user._id | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!document) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json({ error: "Document not found" }, { status: 404 }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Delete file from Cloudinary | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await deleteFromCloudinary(document.cloudinaryPublicId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error("Error deleting file from Cloudinary:", error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Continue with database deletion even if Cloudinary deletion fails | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+115
to
+121
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Ensure Cloudinary deletion works for non-images by passing resource_type. Raw assets (e.g., PDFs, DOCX) won’t delete with default image resource_type. Detect from mimeType and pass through. - try {
- await deleteFromCloudinary(document.cloudinaryPublicId);
+ try {
+ const resourceType = document.mimeType?.startsWith('image/') ? 'image' : 'raw';
+ await deleteFromCloudinary(document.cloudinaryPublicId, resourceType);Additionally update lib/cloudinary.ts: -export async function deleteFromCloudinary(publicId: string): Promise<void> {
+export async function deleteFromCloudinary(publicId: string, resourceType: 'image' | 'raw' = 'image'): Promise<void> {
return new Promise((resolve, reject) => {
- cloudinary.uploader.destroy(publicId, (error, result) => {
+ cloudinary.uploader.destroy(publicId, { resource_type: resourceType }, (error, result) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
}📝 Committable suggestion
Suggested change
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Delete document record | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await DocumentModel.findByIdAndDelete(docId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json({ message: "Document deleted successfully" }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error("Error deleting document:", error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { error: "Failed to delete document" }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { status: 500 } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| import { NextRequest, NextResponse } from "next/server"; | ||
| import { getServerSession } from "next-auth"; | ||
| import { authOptions } from "@/app/api/auth/[...nextauth]/options"; | ||
| import dbConnect from "@/lib/dbConnect"; | ||
| import CaseModel from "@/model/Case"; | ||
| import DocumentModel from "@/model/Document"; | ||
| import { uploadToCloudinary, isCloudinaryConfigured } from "@/lib/cloudinary"; | ||
|
|
||
| // GET /api/cases/[id]/documents - Get all documents for a case | ||
| export async function GET( | ||
| request: NextRequest, | ||
| { params }: { params: { id: string } } | ||
| ) { | ||
| try { | ||
| const session = await getServerSession(authOptions); | ||
| if (!session?.user?._id) { | ||
| return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | ||
| } | ||
|
|
||
| await dbConnect(); | ||
| const { id } = await params; | ||
|
|
||
| // Verify case exists and belongs to user | ||
| const caseData = await CaseModel.findOne({ | ||
| _id: id, | ||
| userId: session.user._id | ||
| }); | ||
|
Comment on lines
+21
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate ObjectId format to avoid CastError 500s on invalid IDs. Mirror the validation used in other routes and return 400 early. - const { id } = params;
+ const { id } = params;
+ if (!id.match(/^[0-9a-fA-F]{24}$/)) {
+ return NextResponse.json(
+ { error: "Invalid case ID format" },
+ { status: 400 }
+ );
+ }Also applies to: 60-67 🤖 Prompt for AI Agents |
||
|
|
||
| if (!caseData) { | ||
| return NextResponse.json({ error: "Case not found" }, { status: 404 }); | ||
| } | ||
|
|
||
| // Get documents for this case | ||
| const documents = await DocumentModel.find({ | ||
| caseId: id, | ||
| userId: session.user._id | ||
| }).sort({ uploadDate: -1 }); | ||
|
|
||
| return NextResponse.json({ documents }); | ||
| } catch (error) { | ||
| console.error("Error fetching documents:", error); | ||
| return NextResponse.json( | ||
| { error: "Failed to fetch documents" }, | ||
| { status: 500 } | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| // POST /api/cases/[id]/documents - Upload a new document | ||
| export async function POST( | ||
| request: NextRequest, | ||
| { params }: { params: { id: string } } | ||
| ) { | ||
| try { | ||
| const session = await getServerSession(authOptions); | ||
| if (!session?.user?._id) { | ||
| return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); | ||
| } | ||
|
|
||
| await dbConnect(); | ||
| const { id } = await params; | ||
|
|
||
| // Verify case exists and belongs to user | ||
| const caseData = await CaseModel.findOne({ | ||
| _id: id, | ||
| userId: session.user._id | ||
| }); | ||
|
|
||
| if (!caseData) { | ||
| return NextResponse.json({ error: "Case not found" }, { status: 404 }); | ||
| } | ||
|
|
||
| const formData = await request.formData(); | ||
| const file = formData.get("file") as File; | ||
| const documentType = formData.get("documentType") as string; | ||
| const description = formData.get("description") as string; | ||
| const tags = formData.get("tags") as string; | ||
|
|
||
| if (!file) { | ||
| return NextResponse.json({ error: "No file provided" }, { status: 400 }); | ||
| } | ||
|
|
||
| // Validate file size (max 10MB) | ||
| const maxSize = 10 * 1024 * 1024; // 10MB | ||
| if (file.size > maxSize) { | ||
| return NextResponse.json( | ||
| { error: "File size exceeds 10MB limit" }, | ||
| { status: 400 } | ||
| ); | ||
| } | ||
|
|
||
| // Validate file type | ||
| const allowedTypes = [ | ||
| "application/pdf", | ||
| "application/msword", | ||
| "application/vnd.openxmlformats-officedocument.wordprocessingml.document", | ||
| "text/plain", | ||
| "image/jpeg", | ||
| "image/png", | ||
| "image/gif" | ||
| ]; | ||
|
|
||
| if (!allowedTypes.includes(file.type)) { | ||
| return NextResponse.json( | ||
| { error: "File type not allowed" }, | ||
| { status: 400 } | ||
| ); | ||
| } | ||
|
|
||
| // Generate unique filename | ||
| const timestamp = Date.now(); | ||
| const fileExtension = file.name.split('.').pop(); | ||
| const fileName = `${id}_${timestamp}.${fileExtension}`; | ||
|
|
||
| // Check if Cloudinary is configured | ||
| if (!isCloudinaryConfigured()) { | ||
| return NextResponse.json( | ||
| { error: "Cloudinary is not configured. Please set up CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, and CLOUDINARY_API_SECRET environment variables." }, | ||
| { status: 500 } | ||
| ); | ||
| } | ||
|
|
||
| // Convert file to buffer | ||
| const bytes = await file.arrayBuffer(); | ||
| const buffer = Buffer.from(bytes); | ||
|
|
||
| // Upload to Cloudinary | ||
| const cloudinaryResult = await uploadToCloudinary( | ||
| buffer, | ||
| fileName, | ||
| `legal-ease/cases/${id}/documents` | ||
| ); | ||
|
|
||
| // Create document record | ||
| const document = new DocumentModel({ | ||
| caseId: id, | ||
| fileName: fileName, | ||
| originalName: file.name, | ||
| fileSize: file.size, | ||
| mimeType: file.type, | ||
| cloudinaryUrl: cloudinaryResult.url, | ||
| cloudinaryPublicId: cloudinaryResult.public_id, | ||
| uploadedBy: session.user._id, | ||
| documentType: documentType || "other", | ||
| description: description || "", | ||
| tags: tags ? tags.split(',').map(tag => tag.trim()) : [], | ||
| userId: session.user._id | ||
| }); | ||
|
|
||
| await document.save(); | ||
|
|
||
| return NextResponse.json({ document }, { status: 201 }); | ||
| } catch (error) { | ||
| console.error("Error uploading document:", error); | ||
| return NextResponse.json( | ||
| { error: "Failed to upload document" }, | ||
| { status: 500 } | ||
| ); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Validate ObjectId format for id and docId to avoid CastError 500s.
Return 400 early on invalid IDs.
🤖 Prompt for AI Agents