diff --git a/package.json b/package.json index ce284728..c6562990 100644 --- a/package.json +++ b/package.json @@ -54,10 +54,12 @@ "@types/lodash": "^4.17.7", "@types/uuid": "^10.0.0", "@uidotdev/usehooks": "^2.4.1", + "100xdevs-job-board": "file:", "bcryptjs": "^2.4.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.0", + "date-fns": "^2.30.0", "dayjs": "^1.11.13", "framer-motion": "^11.9.0", "jiti": "^1.21.6", @@ -71,6 +73,7 @@ "node-cron": "^3.0.3", "nodemailer": "^6.9.15", "react": "^18", + "react-day-picker": "^8.10.1", "react-dom": "^18", "react-hook-form": "^7.52.2", "react-icons": "^5.2.1", @@ -86,6 +89,7 @@ "devDependencies": { "@types/bcryptjs": "^2.4.6", "@types/node": "^20.16.10", + "@types/node-cron": "^3.0.11", "@types/nodemailer": "^6.4.16", "@types/react": "^18", "@types/react-dom": "^18", @@ -102,4 +106,4 @@ "ts-node": "^10.9.2", "typescript": "^5.6.2" } -} \ No newline at end of file +} diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8aa98268..49ac8b9b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -63,6 +63,9 @@ model Job { application String companyLogo String skills String[] + expired Boolean @default(false) + hasExpiryDate Boolean @default(false) @map("has_expiry_date") + expiryDate DateTime? hasSalaryRange Boolean @default(false) @map("has_salary_range") minSalary Int? maxSalary Int? diff --git a/prisma/seed.ts b/prisma/seed.ts index 7a16f7f6..795cd564 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -36,6 +36,8 @@ let jobs = [ maxExperience: 2, companyLogo: '', hasSalaryRange: true, + hasExpiryDate: true, + expiryDate: new Date(new Date().setDate(new Date().getDate() + 49)), minSalary: 60000, maxSalary: 80000, isVerifiedJob: true, @@ -53,7 +55,8 @@ let jobs = [ type: EmployementType.Full_time, workMode: WorkMode.office, currency: Currency.USD, - + hasExpiryDate: true, + expiryDate: new Date(new Date().setDate(new Date().getDate() + 49)), hasExperiencerange: false, companyLogo: '', hasSalaryRange: false, @@ -74,6 +77,7 @@ let jobs = [ type: EmployementType.Full_time, workMode: WorkMode.hybrid, currency: Currency.USD, + hasExpiryDate: false, hasExperiencerange: true, minExperience: 3, maxExperience: 4, @@ -100,6 +104,8 @@ let jobs = [ hasExperiencerange: true, minExperience: 1, maxExperience: 2, + hasExpiryDate: true, + expiryDate: new Date(new Date().setDate(new Date().getDate() + 49)), companyLogo: '', hasSalaryRange: true, minSalary: 50000, @@ -120,6 +126,8 @@ let jobs = [ type: EmployementType.Full_time, workMode: WorkMode.hybrid, currency: Currency.USD, + hasExpiryDate: true, + expiryDate: new Date(new Date().setDate(new Date().getDate() + 49)), hasExperiencerange: false, companyLogo: '', hasSalaryRange: true, @@ -145,6 +153,8 @@ let jobs = [ minExperience: 1, maxExperience: 2, companyLogo: '', + hasExpiryDate: true, + expiryDate: new Date(new Date().setDate(new Date().getDate() + 49)), hasSalaryRange: true, minSalary: 80000, maxSalary: 100000, @@ -165,6 +175,7 @@ let jobs = [ workMode: WorkMode.remote, currency: Currency.USD, hasExperiencerange: true, + hasExpiryDate: false, minExperience: 1, maxExperience: 2, companyLogo: '', @@ -187,6 +198,8 @@ let jobs = [ workMode: WorkMode.hybrid, currency: Currency.USD, hasExperiencerange: true, + hasExpiryDate: true, + expiryDate: new Date(new Date().setDate(new Date().getDate() + 49)), minExperience: 1, maxExperience: 2, companyLogo: '', @@ -208,6 +221,7 @@ let jobs = [ workMode: WorkMode.office, currency: Currency.USD, hasExperiencerange: true, + hasExpiryDate: false, minExperience: 1, maxExperience: 2, companyLogo: '', @@ -233,6 +247,8 @@ let jobs = [ minExperience: 1, maxExperience: 2, companyLogo: '', + hasExpiryDate: true, + expiryDate: new Date(new Date().setDate(new Date().getDate() + 49)), hasSalaryRange: true, minSalary: 75000, maxSalary: 95000, @@ -253,6 +269,7 @@ let jobs = [ currency: Currency.USD, companyLogo: '', hasSalaryRange: true, + hasExpiryDate: false, hasExperiencerange: false, minSalary: 25000, maxSalary: 50000, @@ -271,6 +288,8 @@ let jobs = [ type: EmployementType.Contract, workMode: WorkMode.remote, currency: Currency.USD, + hasExpiryDate: true, + expiryDate: new Date(new Date().setDate(new Date().getDate() + 49)), hasExperiencerange: true, minExperience: 1, maxExperience: 2, @@ -339,6 +358,8 @@ async function seedJobs() { city: faker.location.city(), address: faker.location.city(), hasExperiencerange: j.hasExperiencerange, + hasExpiryDate: j.hasExpiryDate, + expiryDate: j.expiryDate, minExperience: j.minExperience, maxExperience: j.maxExperience, companyLogo: '/main.svg', diff --git a/src/actions/corn.ts b/src/actions/corn.ts new file mode 100644 index 00000000..15b7b2d5 --- /dev/null +++ b/src/actions/corn.ts @@ -0,0 +1,20 @@ +// lib/cron.ts +import cron from 'node-cron'; +import { updateExpiredJobs } from './job.action'; + +let cronJobInitialized = false; + +export const startCronJob = () => { + if (!cronJobInitialized) { + cronJobInitialized = true; + + // Schedule the job to run at midnight (12:00 AM) every day + cron.schedule('0 0 * * *', async () => { + try { + await updateExpiredJobs(); + } catch (error) { + console.error('Error updating expired jobs:', error); + } + }); + } +}; diff --git a/src/actions/job.action.ts b/src/actions/job.action.ts index 8b835601..2f946989 100644 --- a/src/actions/job.action.ts +++ b/src/actions/job.action.ts @@ -53,6 +53,8 @@ export const createJob = withServerActionAsyncCatcher< description, hasSalaryRange, hasExperiencerange, + hasExpiryDate, + expiryDate, maxSalary, minExperience, maxExperience, @@ -65,6 +67,8 @@ export const createJob = withServerActionAsyncCatcher< description, hasExperiencerange, minExperience, + expiryDate, + hasExpiryDate, maxExperience, skills, companyName, @@ -111,6 +115,7 @@ export const getAllJobs = withServerActionAsyncCatcher< orderBy: [orderBy], where: { isVerifiedJob: true, + expired: false, ...filterQueries, }, select: { @@ -124,6 +129,8 @@ export const getAllJobs = withServerActionAsyncCatcher< hasExperiencerange: true, minExperience: true, maxExperience: true, + hasExpiryDate: true, + expiryDate: true, skills: true, address: true, workMode: true, @@ -164,6 +171,7 @@ export const getRecommendedJobs = withServerActionAsyncCatcher< category: category, id: { not: id }, isVerifiedJob: true, + expired: false, }, orderBy: { postedAt: 'desc', @@ -194,6 +202,7 @@ export const getRecommendedJobs = withServerActionAsyncCatcher< const fallbackJobs = await prisma.job.findMany({ where: { id: { not: id }, + expired: false, }, orderBy: { postedAt: 'desc', @@ -239,7 +248,7 @@ export const getJobById = withServerActionAsyncCatcher< const result = JobByIdSchema.parse(data); const { id } = result; const job = await prisma.job.findFirst({ - where: { id }, + where: { id, expired: false }, select: { id: true, title: true, @@ -252,6 +261,8 @@ export const getJobById = withServerActionAsyncCatcher< category: true, city: true, hasExperiencerange: true, + expiryDate: true, + hasExpiryDate: true, minExperience: true, maxExperience: true, skills: true, @@ -272,6 +283,7 @@ export const getJobById = withServerActionAsyncCatcher< export const getCityFilters = async () => { const response = await prisma.job.findMany({ select: { + expired: false, city: true, }, }); @@ -284,8 +296,9 @@ export const getCityFilters = async () => { export const getRecentJobs = async () => { try { const recentJobs = await prisma.job.findMany({ - where: { + where: { isVerifiedJob: true, + expired: false, }, orderBy: { postedAt: 'desc', @@ -354,3 +367,19 @@ export const updateJob = withServerActionAsyncCatcher< additonal ).serialize(); }); + +export async function updateExpiredJobs() { + const currentDate = new Date(); + + await prisma.job.updateMany({ + where: { + hasExpiryDate: true, + expiryDate: { + lt: currentDate, + }, + }, + data: { + expired: true, + }, + }); +} diff --git a/src/app/page.tsx b/src/app/page.tsx index bd78e16e..c0a3335e 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,9 +1,11 @@ +import { startCronJob } from '@/actions/corn'; import Faqs from '@/components/Faqs'; import HeroSection from '@/components/hero-section'; import { JobLanding } from '@/components/job-landing'; import Testimonials from '@/components/Testimonials'; const HomePage = async () => { + startCronJob(); return (
Posted for
30 days