Skip to content

Commit f45811c

Browse files
committed
fixed broken file upload in project creation
1 parent 1039bd6 commit f45811c

File tree

11 files changed

+268
-284
lines changed

11 files changed

+268
-284
lines changed

app/api/contractor/projects/[projectId]/files/route.ts

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import { NextResponse } from "next/server";
22
import { getServerSession } from "next-auth";
33
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
44
import prisma from "@/utils/db";
5+
import { v4 as uuidv4 } from "uuid";
6+
import { promises as fs } from "fs";
7+
import path from "path";
58

69
export async function GET(
710
request: Request,
@@ -14,7 +17,7 @@ export async function GET(
1417
}
1518

1619
// Get project files from database
17-
const files = await prisma.projectfile.findMany({
20+
const files = await prisma.ProjectFile.findMany({
1821
where: {
1922
projectId: params.projectId
2023
},
@@ -40,22 +43,49 @@ export async function POST(
4043
return new NextResponse("Unauthorized", { status: 401 });
4144
}
4245

43-
const body = await request.json();
44-
const { filename, originalname, path, mimetype, size } = body;
46+
const formData = await request.formData();
47+
const file = formData.get("files") as File;
48+
49+
if (!file) {
50+
return new NextResponse("No file uploaded", { status: 400 });
51+
}
52+
53+
if (file.type !== "application/pdf") {
54+
return new NextResponse("Only PDF files are allowed", { status: 400 });
55+
}
56+
57+
// Create unique filename
58+
const uniqueFileName = `${Date.now()}-${file.name}`;
59+
60+
// Create project-specific directory
61+
const projectUploadPath = path.join(
62+
process.cwd(),
63+
"server",
64+
"uploads",
65+
params.projectId
66+
);
67+
68+
await fs.mkdir(projectUploadPath, { recursive: true });
69+
70+
// Save file to disk
71+
const filePath = path.join('uploads', params.projectId, uniqueFileName);
72+
const fullPath = path.join(process.cwd(), 'server', filePath);
73+
const buffer = await file.arrayBuffer();
74+
await fs.writeFile(fullPath, new Uint8Array(buffer));
4575

4676
// Create file record in database
47-
const file = await prisma.projectfile.create({
77+
const projectFile = await prisma.ProjectFile.create({
4878
data: {
4979
projectId: params.projectId,
50-
filename,
51-
originalname,
52-
path,
53-
mimetype,
54-
size,
80+
filename: uniqueFileName,
81+
originalname: file.name,
82+
mimetype: file.type,
83+
size: buffer.byteLength,
84+
path: filePath,
5585
}
5686
});
5787

58-
return NextResponse.json(file);
88+
return NextResponse.json(projectFile);
5989
} catch (error) {
6090
console.error("Error creating project file:", error);
6191
return new NextResponse("Internal Server Error", { status: 500 });

app/api/contractor/projects/route.ts

Lines changed: 27 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import { getServerSession } from "next-auth";
22
import { NextResponse } from "next/server";
33
import prisma from "@/server/utills/db";
4-
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
5-
import { promises as fs } from "fs";
6-
import path from "path";
74

85
export async function GET() {
96
try {
@@ -35,69 +32,44 @@ export async function GET() {
3532

3633
export async function POST(req: Request) {
3734
try {
38-
const session = await getServerSession(authOptions);
39-
if (!session) {
35+
const session = await getServerSession();
36+
console.log("[PROJECTS_POST] Session:", session);
37+
38+
if (!session?.user?.email) {
4039
return new NextResponse("Unauthorized", { status: 401 });
4140
}
4241

43-
const { name, filePaths } = await req.json();
44-
45-
// Create project using backend API
46-
const projectResponse = await fetch("http://localhost:3001/api/projects", {
47-
method: "POST",
48-
headers: {
49-
"Content-Type": "application/json",
50-
},
51-
body: JSON.stringify({
52-
name,
53-
contractorId: session.user.id,
54-
}),
42+
const user = await prisma.user.findUnique({
43+
where: { email: session.user.email },
5544
});
45+
console.log("[PROJECTS_POST] User:", user);
5646

57-
if (!projectResponse.ok) {
58-
throw new Error("Failed to create project");
47+
if (!user) {
48+
return new NextResponse("Unauthorized", { status: 401 });
5949
}
6050

61-
const project = await projectResponse.json();
62-
63-
// If there are files, move them from temp to project directory
64-
if (filePaths && filePaths.length > 0) {
65-
// Create project directory
66-
const projectDir = path.join(process.cwd(), "server", "uploads", project.id);
67-
await fs.mkdir(projectDir, { recursive: true });
68-
69-
// Move each file and create file records
70-
for (const tempPath of filePaths) {
71-
const fileName = path.basename(tempPath);
72-
const sourcePath = path.join(process.cwd(), "server", tempPath);
73-
const targetPath = path.join(projectDir, fileName);
74-
75-
// Move file from temp to project directory
76-
await fs.rename(sourcePath, targetPath);
51+
const body = await req.json();
52+
console.log("[PROJECTS_POST] Request body:", body);
53+
const { name } = body;
7754

78-
// Create file record in database
79-
await fetch(`http://localhost:3001/api/projects/${project.id}/files`, {
80-
method: "POST",
81-
headers: {
82-
"Content-Type": "application/json",
83-
},
84-
body: JSON.stringify({
85-
filename: fileName,
86-
originalname: fileName.substring(37), // Remove UUID prefix
87-
path: `uploads/${project.id}/${fileName}`,
88-
mimetype: "application/pdf",
89-
size: (await fs.stat(targetPath)).size,
90-
}),
91-
});
92-
}
55+
if (!name) {
56+
return new NextResponse("Name is required", { status: 400 });
9357
}
9458

59+
console.log("[PROJECTS_POST] Creating project with:", { name, contractorId: user.id });
60+
const project = await prisma.Project.create({
61+
data: {
62+
name,
63+
contractorId: user.id,
64+
},
65+
});
66+
console.log("[PROJECTS_POST] Created project:", project);
67+
9568
return NextResponse.json(project);
9669
} catch (error) {
97-
console.error("Error creating project:", error);
98-
return new NextResponse(
99-
error instanceof Error ? error.message : "Internal Server Error",
100-
{ status: 500 }
101-
);
70+
console.error("[PROJECTS_POST] Error details:", error);
71+
// Return more specific error message if available
72+
const errorMessage = error instanceof Error ? error.message : "Internal error";
73+
return new NextResponse(errorMessage, { status: 500 });
10274
}
10375
}

app/api/upload/route.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,34 +13,38 @@ export async function POST(req: Request) {
1313

1414
const formData = await req.formData();
1515
const file = formData.get("uploadedFile") as File;
16+
const projectId = formData.get("projectId") as string;
1617

1718
if (!file) {
1819
return new NextResponse("No file uploaded", { status: 400 });
1920
}
2021

22+
if (!projectId) {
23+
return new NextResponse("Project ID is required", { status: 400 });
24+
}
25+
2126
if (file.type !== "application/pdf") {
2227
return new NextResponse("Only PDF files are allowed", { status: 400 });
2328
}
2429

2530
const uniqueFileName = `${uuidv4()}-${file.name}`;
2631

27-
// Create temp uploads directory
28-
const uploadPath = path.join(
32+
// Create project-specific directory
33+
const projectUploadPath = path.join(
2934
process.cwd(),
3035
"server",
3136
"uploads",
32-
"temp",
33-
uniqueFileName
37+
projectId
3438
);
3539

36-
// Ensure temp directory exists
37-
await fs.mkdir(path.dirname(uploadPath), { recursive: true });
40+
await fs.mkdir(projectUploadPath, { recursive: true });
3841

42+
const uploadPath = path.join(projectUploadPath, uniqueFileName);
3943
const buffer = Buffer.from(await file.arrayBuffer());
4044
await fs.writeFile(uploadPath, Uint8Array.from(buffer));
4145

4246
return NextResponse.json({
43-
filePath: `uploads/temp/${uniqueFileName}`,
47+
filePath: `uploads/${projectId}/${uniqueFileName}`,
4448
fileName: uniqueFileName,
4549
originalName: file.name,
4650
size: buffer.length,

app/projects/[projectId]/materials/page.tsx

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export default function MaterialsPage() {
9393

9494
// Fetch uploaded files
9595
const filesResponse = await fetch(
96-
`/api/projects/uploaded-files`
96+
`/api/contractor/projects/${params.projectId}/files`
9797
);
9898
if (!filesResponse.ok) {
9999
throw new Error("Failed to fetch uploaded files");
@@ -102,7 +102,7 @@ export default function MaterialsPage() {
102102
setUploadedFiles(filesData);
103103
} catch (error) {
104104
console.error("Error fetching data:", error);
105-
toast.error("Failed to load materials");
105+
toast.error("Failed to load data");
106106
} finally {
107107
setLoading(false);
108108
}
@@ -219,38 +219,47 @@ export default function MaterialsPage() {
219219
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
220220
if (!e.target.files || e.target.files.length === 0) return;
221221

222-
const file = e.target.files[0];
223-
if (file.type !== 'application/pdf') {
224-
toast.error('Only PDF files are allowed');
225-
return;
222+
const files = e.target.files;
223+
for (let i = 0; i < files.length; i++) {
224+
const file = files[i];
225+
if (file.size > 20 * 1024 * 1024) {
226+
toast.error('File size exceeds 20MB');
227+
return;
228+
}
229+
if (file.type !== 'application/pdf') {
230+
toast.error('Only PDF files are allowed');
231+
return;
232+
}
226233
}
227234

228235
const formData = new FormData();
229-
formData.append('files', file);
236+
for (let i = 0; i < files.length; i++) {
237+
formData.append('files', files[i]);
238+
}
230239

231240
try {
232-
const response = await fetch(`http://localhost:3001/api/projects/${params.projectId}/files`, {
241+
const response = await fetch(`/api/contractor/projects/${params.projectId}/files`, {
233242
method: 'POST',
234243
body: formData,
235244
});
236245

237246
if (!response.ok) {
238-
throw new Error('Failed to upload file');
247+
throw new Error('Failed to upload files');
239248
}
240249

241250
const result = await response.json();
242251

243-
// Refresh the file list from backend
244-
const filesResponse = await fetch(`http://localhost:3001/api/projects/${params.projectId}/files`);
252+
// Refresh the file list
253+
const filesResponse = await fetch(`/api/contractor/projects/${params.projectId}/files`);
245254
if (!filesResponse.ok) {
246255
throw new Error('Failed to fetch uploaded files');
247256
}
248257
const filesData = await filesResponse.json();
249258
setUploadedFiles(filesData);
250-
toast.success('File uploaded successfully');
259+
toast.success('Files uploaded successfully');
251260
} catch (error) {
252-
console.error('Error uploading file:', error);
253-
toast.error('Failed to upload file');
261+
console.error('Error uploading files:', error);
262+
toast.error('Failed to upload files');
254263
}
255264
};
256265

@@ -358,27 +367,24 @@ export default function MaterialsPage() {
358367
{uploadedFiles.map((file) => (
359368
<div
360369
key={file.id}
361-
className="flex items-center p-2 rounded-md border border-gray-200"
370+
className="flex items-center p-2 rounded-md hover:bg-gray-50 border border-gray-200"
362371
>
363372
<input
364373
type="checkbox"
365374
id={`file-${file.id}`}
366375
checked={selectedFilesForImport.includes(file.path)}
367-
onChange={(e) => {
368-
if (e.target.checked) {
369-
setSelectedFilesForImport([...selectedFilesForImport, file.path]);
370-
} else {
371-
setSelectedFilesForImport(selectedFilesForImport.filter(path => path !== file.path));
372-
}
373-
}}
376+
onChange={() => toggleFileSelection(file.path)}
374377
className="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded"
375378
/>
376379
<label
377380
htmlFor={`file-${file.id}`}
378-
className="ml-3 block text-sm font-medium text-gray-700 truncate"
381+
className="ml-3 block text-sm font-medium text-gray-700 truncate cursor-pointer flex-1"
379382
>
380383
{file.originalname}
381384
</label>
385+
<span className="text-xs text-gray-500 ml-2">
386+
{new Date(file.uploadedAt).toLocaleDateString()}
387+
</span>
382388
</div>
383389
))}
384390
</div>
@@ -423,20 +429,29 @@ export default function MaterialsPage() {
423429

424430
<div className="bg-white shadow rounded-lg p-6">
425431
<h2 className="text-xl font-semibold mb-4">Upload Material from PDF</h2>
426-
<div className="flex items-center">
432+
<div className="flex flex-col items-start space-y-4">
427433
<input
428434
type="file"
429435
accept=".pdf"
430436
onChange={handleFileChange}
431437
className="hidden"
432438
id="fileInput"
439+
multiple
433440
/>
434-
<label
435-
htmlFor="fileInput"
436-
className="inline-block px-4 py-2 bg-indigo-600 text-white rounded-md cursor-pointer hover:bg-indigo-700"
437-
>
438-
Upload PDF
439-
</label>
441+
<div className="flex items-center space-x-4">
442+
<label
443+
htmlFor="fileInput"
444+
className="inline-flex items-center px-4 py-2 bg-indigo-600 text-white rounded-md cursor-pointer hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
445+
>
446+
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
447+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
448+
</svg>
449+
Upload PDF Files
450+
</label>
451+
<span className="text-sm text-gray-500">
452+
Only PDF files up to 20MB are allowed
453+
</span>
454+
</div>
440455
</div>
441456
</div>
442457

0 commit comments

Comments
 (0)