File: //home/arjun/projects/good-life-be/helper/questionsCron.js
import moment from 'moment';
import { spawn } from 'child_process';
import { dirname, join } from 'path';
import Event from '../models/Event.js';
import __dirname from '../path.js';
import User from '../models/User.js';
import EventCounts from '../models/eventsCount.js';
import { preprocessEvents } from './eventsUtils.js';
import BadRequest from './exception/badRequest.js';
import FormData from '../models/formDataModel.js';
import sendEmails from './sendEmail.js';
import Login from '../models/Login.js';
// Function to get the last inserted date for a user
async function isUserActive(userId) {
const user = await User.findOne({
where: { id: userId },
attributes: ['eventStatus'],
});
return user?.eventStatus === 'pending'; // Return true if active, false otherwise
}
async function getLastProcessedDate(userId) {
const lastEvent = await Event.findOne({
where: { user_id: userId, status: 'active' },
order: [['date', 'DESC']],
attributes: ['date'],
raw: true,
});
return lastEvent ? lastEvent?.date : null;
}
async function updateUserEventStatus(userId, status) {
await User.update({ eventStatus: status }, { where: { id: userId } });
}
async function sendDataToPython(data, events, test) {
return new Promise((resolve, reject) => {
const pythonScriptPath = join(
__dirname,
'helper/gpt_schedule_optimizer.py'
); // Ensure correct path
let argsArray = [pythonScriptPath, data, events, test];
// Convert the data to JSON string
// const jsonData = JSON.stringify({ date, events });
// Spawn the Python process
const pythonExecutable = 'python3';
const pythonProcess = spawn(pythonExecutable, argsArray); // Use 'python3' if needed
let stdoutData = '';
let stderrData = '';
// // Send JSON data to Python via stdin
// pythonProcess.stdin.write(jsonData);
// pythonProcess.stdin.end();
// Capture stdout data
pythonProcess.stdout.on('data', (data) => {
stdoutData += data;
});
// Capture stderr data
pythonProcess.stderr.on('data', (data) => {
stderrData += data;
});
// Handle process close
pythonProcess.on('close', (code) => {
if (code === 0) {
resolve(stdoutData.trim());
} else {
reject(
new Error(`Python script exited with code ${code}: ${stderrData}`)
);
}
});
// Handle process errors
pythonProcess.on('error', (err) => {
reject(new Error(`Failed to start Python script: ${err.message}`));
});
});
}
const checkAndUpdateUserStatus = async (userId) => {
try {
// Get the last date from the Event table for the user
const lastEvent = await Event.findOne({
where: { user_id: userId },
order: [['date', 'DESC']], // Get the latest event
attributes: ['date'], // Only fetch the date field
raw: true,
});
if (!lastEvent || !lastEvent.date) {
console.log(`No events found for user ${userId}`);
return;
}
const lastEventDate = lastEvent.date;
console.log(`Last event date for user ${userId}:`, lastEventDate);
// Check if this last date exists in the database
const existingEvent = await Event.findOne({
where: { user_id: userId, date: lastEventDate },
raw: true,
});
if (!existingEvent) {
console.log(
`Last event date ${lastEventDate} is missing for user ${userId}. Updating status to processing...`
);
// Update FormData status to 'processing' for this user
await FormData.update(
{ status: 'processing' },
{ where: { user_id: userId } }
);
} else {
console.log(
`All events entered correctly for user ${userId}. No update needed.`
);
}
} catch (error) {
console.error('Error checking and updating user status:', error.message);
}
};
// Function to execute the cron job
export const runCronJob = async (response, user) => {
try {
// let dayData;
const userId = user; // Replace with dynamic user ID
// Check if user eventStatus is active
const isActive = await isUserActive(userId);
if (!isActive) {
console.log(`User ${userId} eventStatus is inactive. Skipping cron job.`);
throw new BadRequest('User Already has events');
return;
}
console.log('Starting cron job...');
if (!response.Data?.Calendar) {
return;
}
const dayData = Object.entries(response.Data.Calendar).map(
([date, { events }]) => ({
date,
events,
priorityLevel: response.Data.Priority_Level,
distributionData: response.Data.distribution_data,
})
);
// console.log('dayData', dayData);
const lastProcessedDate = await getLastProcessedDate(userId);
console.log('lastProcessedDate', lastProcessedDate);
// const today = moment().startOf('day');
// const startDate = lastProcessedDate ? lastProcessedDate : today;
const lastProcessedMoment = moment(lastProcessedDate); // Convert once
const filteredDates = Object.entries(response.Data.Calendar)
.filter(([date]) =>
lastProcessedDate ? moment(date).isAfter(lastProcessedMoment) : true
) // Only future dates
.sort(([dateA], [dateB]) => moment(dateA).diff(moment(dateB))); // Sort by date in ascending order
if (filteredDates.length === 0) {
console.log('No new events to process.');
return;
}
let totalProcessedDays = 0;
await FormData.update(
{ status: 'processing' },
{ where: { user_id: userId } }
);
// Process up to 365 days
// const datesToProcess = dayEvents.slice(0, 365);
for (const [date, details] of filteredDates) {
const dayDataPython = {
[date]: details,
};
await FormData.update({ dataStatus: 0 }, { where: { user_id: user } });
const [lastDate] = filteredDates.slice(-1); // Gets the last element using slice
const currentDate = date; // Current processing date
// const { startTime, endTime, eventTitle } = details; // Extract required values
// const uniqueKey = `${date}_${startTime}-${endTime}_${eventTitle.replace(/\s+/g, '')}`;
// console.log('Generated Unique Key:', uniqueKey);
const diffInDays = Math.floor(
(new Date(lastDate[0]) - new Date(currentDate)) / (1000 * 60 * 60 * 24)
);
console.log('Last Date:', lastDate[0]);
const remainingDays = 365 - diffInDays;
console.log('Remaining Days:', remainingDays);
console.log('Processing date:', date); // ✅ This will print each date
// console.log('filteredDates', filteredDates);
let priority_levels = response.Data.Priority_Level;
let distribution_data = response.Data.distribution_data;
const formData = await FormData.findOne({ where: { user_id: user } });
// ✅ Only proceed if dataStatus is 1 (previous event was inserted successfully)
// if (formData?.dataStatus === 1) {
// console.log(
// `Skipping API call for ${date}, waiting for previous event insertion.`
// );
// continue; // Skip API call until the previous event is inserted
// }
// await FormData.update({ dataStatus: 1 }, { where: { user_id: user } });
let result;
try {
result = await sendDataToPython(
JSON.stringify(dayDataPython),
JSON.stringify(priority_levels),
JSON.stringify(distribution_data)
); // Await the result
// ✅ Find first '{' to extract only JSON part
// Find the start of the JSON object
if (typeof result === 'string') {
// Remove any surrounding quotes if they exist
result = result.replace(/^"|"$/g, '');
}
// **Check if result contains "fail" and skip iteration**
if (result.toLowerCase().includes('fali')) {
console.log(`Skipping processing for date: ${date} due to failure.`);
continue; // Skip current iteration and move to the next date
}
let parsedResult;
try {
parsedResult = JSON.parse(result);
} catch (error) {
console.log('error In parsing', error);
throw new BadRequest(error);
}
// ✅ Extract only the Calendar object
let extractedCalendar =
parsedResult?.Calendar?.Calendar || parsedResult?.Calendar;
const eventData = await preprocessEvents(extractedCalendar, user);
for (const event of eventData) {
const createdEvent = await Event.create(event); // Insert event into DB
// if (createdEvent) {
// // Update dataStatus to 1 after successful event insertion
// // await FormData.update(
// // { dataStatus: 0 },
// // { where: { user_id: user } }
// // );
// }
}
const existingCount = await EventCounts.findOne({
where: { user_id: user },
});
if (!existingCount) {
// If no entry exists, create a new one
await EventCounts.create({
user_id: user,
count: remainingDays,
});
} else {
// If entry exists, update the count
await EventCounts.update(
{ count: remainingDays },
{ where: { user_id: user } }
);
}
} catch (error) {
console.log('error', error);
throw new BadRequest(error.message);
}
await FormData.update({ dataStatus: 0 }, { where: { user_id: user } });
}
console.log(
`Cron job execution completed. Total events processed: ${totalProcessedDays}`
);
// After processing all 365 days, update eventStatus to inactive
await EventCounts.update(
{ count: 0, eventStatus: 'active' },
{ where: { user_id: user } }
);
await updateUserEventStatus(userId, 'active');
await FormData.update(
{ status: 'completed' },
{ where: { user_id: user } }
);
console.log(`User ${userId} eventStatus updated to inactive.`);
const { MAILER_NAME, MAILER_AUTH_USER, WEB_DOMAIN } = process.env;
const users = await User.findOne({
where: {
id: user,
},
raw: true,
});
const login = await Login.findOne({
where: { user_id: user },
attributes: ['email'],
raw: true,
});
const mailOptions = {
from: 'midhun@spericorn.com',
to: login.email,
subject: 'Your Calendar Optimization is Complete! 🎉',
};
const contentVarialbles = {
name: `${users.first_name} ${users.second_name}`,
feUrl: 'https://calendar-dev.spericorn.com/calendar',
};
const fileName = 'calenderGenerated.ejs';
sendEmails({ mailOptions, fileName, contentVarialbles });
return 'Completed';
} catch (error) {
console.log('error Final', error);
throw new BadRequest(error.message);
}
};
console.log('Cron job scheduled to run daily at midnight.');