From d171d23471e7fb6919ef2cab44f6b1348292a625 Mon Sep 17 00:00:00 2001 From: Om Lanke Date: Tue, 23 Sep 2025 01:31:13 +0530 Subject: [PATCH] job description file upload --- apps/admin/app/(main)/jobs/new/actions.ts | 50 +- .../app/(main)/jobs/new/new-job-form.tsx | 126 ++- apps/admin/app/(main)/jobs/new/schema.ts | 22 +- .../job-descriptions/[filename]/route.ts | 45 + apps/student/app/(main)/jobs/JobClient.tsx | 71 +- .../job-descriptions/[filename]/route.ts | 46 + .../components/job-application-modal.tsx | 2 +- .../db/migrations/0010_nappy_avengers.sql | 3 + packages/db/migrations/0011_gifted_cammi.sql | 1 + .../db/migrations/meta/0010_snapshot.json | 818 ++++++++++++++++++ .../db/migrations/meta/0011_snapshot.json | 818 ++++++++++++++++++ packages/db/migrations/meta/_journal.json | 14 + packages/db/schema.ts | 6 +- 13 files changed, 1992 insertions(+), 30 deletions(-) create mode 100644 apps/admin/app/api/files/job-descriptions/[filename]/route.ts create mode 100644 apps/student/app/api/files/job-descriptions/[filename]/route.ts create mode 100644 packages/db/migrations/0010_nappy_avengers.sql create mode 100644 packages/db/migrations/0011_gifted_cammi.sql create mode 100644 packages/db/migrations/meta/0010_snapshot.json create mode 100644 packages/db/migrations/meta/0011_snapshot.json diff --git a/apps/admin/app/(main)/jobs/new/actions.ts b/apps/admin/app/(main)/jobs/new/actions.ts index e721645..87f6684 100644 --- a/apps/admin/app/(main)/jobs/new/actions.ts +++ b/apps/admin/app/(main)/jobs/new/actions.ts @@ -1,12 +1,14 @@ 'use server'; import { db, companies, jobs } from '@workspace/db'; +import { writeFile, mkdir } from 'fs/promises'; +import { join } from 'path'; export async function createJob(formData: FormData) { const companyIdRaw = formData.get('companyId'); const companyId = companyIdRaw ? Number(companyIdRaw) : undefined; const title = String(formData.get('title') ?? '').trim(); const link = String(formData.get('link') ?? '').trim(); - const description = String(formData.get('description') ?? '').trim() || 'N/A'; + const description = String(formData.get('description') ?? '').trim() || ''; const location = String(formData.get('location') ?? '').trim() || 'N/A'; const imageURL = String(formData.get('imageURL') ?? '').trim() || 'https://via.placeholder.com/100x100?text=Job'; @@ -19,13 +21,49 @@ export async function createJob(formData: FormData) { const allowDeadKT = formData.get('allowDeadKT') === 'on' || formData.get('allowDeadKT') === 'true'; const allowLiveKT = formData.get('allowLiveKT') === 'on' || formData.get('allowLiveKT') === 'true'; + // Handle file upload + const descriptionFile = formData.get('descriptionFile') as File | null; + const fileType = formData.get('fileType') as string | null; + + let fileUrl: string | null = null; + let fileName: string | null = null; + + if (descriptionFile && descriptionFile.size > 0) { + try { + // Create uploads directory if it doesn't exist + const uploadsDir = join(process.cwd(), 'public', 'uploads', 'job-descriptions'); + await mkdir(uploadsDir, { recursive: true }); + + // Generate unique filename + const timestamp = Date.now(); + const originalName = descriptionFile.name.replace(/[^a-zA-Z0-9.-]/g, '_'); + fileName = `${timestamp}_${originalName}`; + const filePath = join(uploadsDir, fileName); + + // Write file to disk + const bytes = await descriptionFile.arrayBuffer(); + await writeFile(filePath, Buffer.from(bytes)); + + // Set file URL for database + fileUrl = `/uploads/job-descriptions/${fileName}`; + } catch (error) { + console.error('Error uploading file:', error); + return { error: 'Failed to upload description file.' }; + } + } + if (!companyId || !title) return { error: 'Company and title are required.' }; + + // Either description text OR file is required + if (!description && !descriptionFile) { + return { error: 'Either description text or description file is required.' }; + } await db.insert(jobs).values({ companyId, title, link, - description, + description: description || null, location, imageURL, salary, @@ -36,18 +74,20 @@ export async function createJob(formData: FormData) { minHSC, allowDeadKT, allowLiveKT, + fileType: fileType || null, + fileUrl: fileUrl || null, + fileName: fileName || null, }); return { success: true }; } export async function createCompany(formData: FormData) { const name = String(formData.get('name') ?? '').trim(); - const email = String(formData.get('email') ?? '').trim(); const link = String(formData.get('link') ?? '').trim(); const description = String(formData.get('description') ?? '').trim(); const imageURL = String(formData.get('imageURL') ?? '').trim() || 'https://via.placeholder.com/100x100?text=Company'; - if (!name || !email || !link || !description) return { error: 'All fields are required.' }; - const [inserted] = await db.insert(companies).values({ name, email, link, description, imageURL }).returning(); + if (!name || !link || !description) return { error: 'Name, link, and description are required.' }; + const [inserted] = await db.insert(companies).values({ name, link, description, imageURL }).returning(); if (!inserted) return { error: 'Failed to add company.' }; return { success: true, company: { id: inserted.id, name: inserted.name } }; } diff --git a/apps/admin/app/(main)/jobs/new/new-job-form.tsx b/apps/admin/app/(main)/jobs/new/new-job-form.tsx index 4246f05..bca5468 100644 --- a/apps/admin/app/(main)/jobs/new/new-job-form.tsx +++ b/apps/admin/app/(main)/jobs/new/new-job-form.tsx @@ -29,6 +29,9 @@ import { CheckCircle, AlertCircle, LinkIcon, + Upload, + FileText, + X, } from "lucide-react" import { cn } from "@workspace/ui/lib/utils" import { Alert, AlertDescription } from "@workspace/ui/components/alert" @@ -62,6 +65,8 @@ function NewJobForm({ companies }: { companies: { id: number; name: string }[] } minHSC: 0, allowDeadKT: true, allowLiveKT: true, + fileType: undefined, + descriptionFile: undefined, }, }) @@ -74,7 +79,7 @@ function NewJobForm({ companies }: { companies: { id: number; name: string }[] } formData.append('companyId', String(data.companyId)) formData.append('title', data.title) formData.append('link', data.link) - formData.append('description', data.description) + formData.append('description', data.description || '') formData.append('location', data.location) formData.append('imageURL', data.imageURL || '') formData.append('salary', data.salary) @@ -89,6 +94,12 @@ function NewJobForm({ companies }: { companies: { id: number; name: string }[] } formData.append('allowDeadKT', String(data.allowDeadKT)) formData.append('allowLiveKT', String(data.allowLiveKT)) + // Handle file upload + if (data.descriptionFile) { + formData.append('descriptionFile', data.descriptionFile) + formData.append('fileType', data.fileType || (data.descriptionFile.type === 'application/pdf' ? 'pdf' : 'text')) + } + const result = await createJob(formData) if (result?.success) { setSuccess(true) @@ -412,23 +423,102 @@ function NewJobForm({ companies }: { companies: { id: number; name: string }[] } /> - ( - - Job Description * - -