File: //proc/1233/root/home/arjun/projects/good-life-be/api/Event/service.js
/* eslint-disable no-unneeded-ternary */
import fs from 'fs';
import { dirname, join } from 'path';
import { col, fn, Op, Sequelize } from 'sequelize';
import { fileURLToPath } from 'url';
import BadRequest from '../../helper/exception/badRequest.js';
import { generatePDF } from '../../helper/pdfGeneration.js';
import EventModel from '../../models/Event.js';
import User from '../../models/User.js';
import FormData from '../../models/formDataModel.js';
import TempFormData from '../../models/TempFormData.js';
import {
assignColorToEvents,
eventUpdate,
findAndEditOverlapEvents,
groupEventsByDay,
MatchingEvents,
processEventsByday,
} from './utils.js';
export const eventById = async (id) => {
const event = await EventModel.findOne({ where: { id }, raw: true });
return event;
};
// ----------------->>>> Event CRUD <<<<-----------------
export const getAllEvents = async (userId) => {
const events = await EventModel.findAll({
where: { user_id: userId, status: 'active' },
attributes: [
'id',
'date',
'event_title',
'event_description',
'event_name',
'start_time',
'end_time',
'category',
'subCategory',
'is_visible',
'ai_suggested_activity',
'ai_activity',
'is_vacation_event',
],
raw: true,
});
const coloredEvents = await assignColorToEvents(events);
const updatedEvents = coloredEvents.map((event) => {
const startDate = new Date(`${event.date}T${event.start_time}`);
const endDate = new Date(`${event.date}T${event.end_time}`);
return {
...event,
start: startDate,
end: endDate,
};
});
const { timeZone } = Intl.DateTimeFormat().resolvedOptions();
return { updatedEvents, timeZone };
};
export const createEvent = async (body, userId) => {
const existingEvents = await EventModel.findAll({
where: {
user_id: userId,
[Op.and]: [
{ start_time: body.start_time }, // Direct comparison with start_time
{ end_time: body.end_time }, // Direct comparison with end_time
],
},
raw: true,
});
if (existingEvents.length > 0) {
throw new BadRequest(
'A conflict occurred: This time is already scheduled.'
);
}
await EventModel.create({
user_id: userId,
...body,
});
};
export const updateEvent = async (eventId, type, body, user) => {
const event = await eventById(eventId);
if (!event) {
throw new BadRequest('Event not found');
}
// -------->>> Conflict checking <<<---------
const normalizedStartTime = `${body.start_time}`; // Convert HH:mm to HH:mm:ss
const normalizedEndTime = `${body.end_time}`; // Co
const conflictingEvents = await EventModel.findAll({
where: {
status: 'active',
user_id: user.id,
date: body.date,
[Op.and]: [
Sequelize.where(
fn('TO_CHAR', col('start_time'), 'HH24:MI'),
Op.lt,
normalizedStartTime
),
Sequelize.where(
fn('TO_CHAR', col('end_time'), 'HH24:MI'),
Op.gt,
normalizedEndTime
),
],
},
attributes: [
'start_time',
'end_time',
'category',
'subCategory',
'recurring',
],
raw: true,
});
if (conflictingEvents.length > 0) {
throw new BadRequest(
'A conflict occurred:\nThis time is already scheduled.'
);
}
const data = {
status: 'active',
user_id: user.id,
category: body?.category,
subCategory: body?.subCategory,
event_title: body?.event_title,
start_time: body?.start_time,
end_time: body?.end_time,
event_description: body?.event_description,
conflict: body?.conflict || (conflictingEvents.length > 0 && false),
};
// -------->>> If conflict is set to false, update all overlapping events <<<---------
if (body.overlapping_events && body.overlapping_events.length > 0) {
findAndEditOverlapEvents(body, type, user);
}
// ------------->>> Update the event with the new data <<<-------------
if (type === 'selected') {
await eventUpdate(eventId, data);
}
if (type === 'all') {
const matchingEvents = await MatchingEvents(event, user.id);
if (matchingEvents.length === 0) {
throw new BadRequest('No matching events found to update');
}
const nonRecurringEventIds = matchingEvents
.filter((e) => !e.recurring) // Only non-recurring events
.map((e) => e.id);
const recurringEventIds = matchingEvents
.filter((e) => e.recurring) // Only recurring events
.map((e) => e.id);
// Update non-recurring events
if (nonRecurringEventIds.length > 0) {
await eventUpdate(nonRecurringEventIds, data);
}
// Update recurring events
if (recurringEventIds.length > 0) {
await eventUpdate(recurringEventIds, data);
}
}
};
export const removeEvent = async (id, { type }, userId) => {
const event = await eventById(id);
if (!event) {
throw new BadRequest('Event not found');
}
const data = { status: 'deleted', conflict: true };
if (type === 'selected') {
await eventUpdate(id, data);
}
if (type === 'all') {
const matchingEvents = await MatchingEvents(event, userId);
if (matchingEvents.length === 0) {
throw new BadRequest('No matching events found to delete');
}
const nonRecurringEventIds = matchingEvents
.filter((e) => !e.recurring) // Only non-recurring events
.map((e) => e.id);
const recurringEventIds = matchingEvents
.filter((e) => e.recurring) // Only recurring events
.map((e) => e.id);
// Update non-recurring events
if (nonRecurringEventIds.length > 0) {
await eventUpdate(nonRecurringEventIds, data);
}
// Update recurring events
if (recurringEventIds.length > 0) {
await eventUpdate(recurringEventIds, data);
}
}
};
// ---------------------->>> For vaccation events <<<----------------------
export const showAndHideEvent = async (eventId, body) => {
const event = await EventModel.findByPk(eventId);
if (!event) throw new BadRequest('Event not found');
await EventModel.update(
{
is_visible: body.is_visible === 'true' ? true : false,
// is_vacation_event: !event.is_vacation_event,
},
{
where: {
id: eventId,
},
}
);
};
export const deleteEventsAPI = async (user) => {
const eventsByuserId = await EventModel.findOne({
wheredeleteEventsAPI: { user_id: user },
raw: true,
});
if (!eventsByuserId) {
throw new BadRequest('Event not found for this user');
}
const event = await EventModel.update(
{ status: 'deleted' }, // Set status to 'deleted'
{
where: { user_id: user }, // Filter by user_id
raw: true,
}
);
await User.update(
{ eventStatus: 'pending' }, // Fields to update
{ where: { id: user } } // Condition
);
const deletedCount = await FormData.destroy({
where: { user_id: user },
});
const deletedTempData = await TempFormData.destroy({
where: { user_id: user },
});
return event;
};
// ---------------->>> Calendar PDF Generation <<<------------------
export const generateCalendarPdf = async (userId) => {
const { SERVER_DOMAIN } = process.env;
const events = await EventModel.findAll({
where: { user_id: userId, status: 'active' },
attributes: [
'id',
'date',
'event_title',
'start_time',
'end_time',
'category',
'subCategory',
],
raw: true,
sort: [['date', 'ASC']],
});
const { events: processEvents } = processEventsByday(events);
const newEvents = await assignColorToEvents(processEvents);
// const result = await groupEventsByWeek(newEvents);
let results = await groupEventsByDay(newEvents);
// Sort the results by the start date of each week
// let results = result.sort((a, b) => {
// // Find the earliest event.date for week a
// const earliestEventA = a.events.reduce((minDate, event) => {
// const eventDate = new Date(event.date);
// return eventDate < minDate ? eventDate : minDate;
// }, new Date('9999-12-31')); // Set a very far future date as the initial value
// // Find the earliest event.date for week b
// const earliestEventB = b.events.reduce((minDate, event) => {
// const eventDate = new Date(event.date);
// return eventDate < minDate ? eventDate : minDate;
// }, new Date('9999-12-31')); // Set a very far future date as the initial value
// // Compare the earliest event dates for sorting
// return earliestEventA - earliestEventB; // Ascending order
// });
const imageUrls = [`${SERVER_DOMAIN}/images/calendar-logo.png`];
const currentModuleDirectory = dirname(fileURLToPath(import.meta.url));
const publicPath = join(currentModuleDirectory, '..', '..', 'public');
const pdfPath = join(publicPath, 'calendarPDF');
if (!fs.existsSync(pdfPath)) {
fs.mkdirSync(pdfPath);
}
const htmlFilePath = `${publicPath}/templates/calendar-pdf.ejs`;
// results = results.slice(0, 6);
// console.log('RESULT_____________________', result[0].events);
const data = {
results,
imageUrls,
};
const now = new Date();
const fileName = `Calendar_PDF_${now
.toISOString()
.replace(/[-:T.]/g, '')
.slice(0, 15)}`;
const pdfBuffer = await generatePDF(data, htmlFilePath);
const savePathRoot = join(pdfPath, `${fileName}.pdf`);
const writeStream = fs.createWriteStream(savePathRoot);
writeStream.write(pdfBuffer);
writeStream.end();
await new Promise((resolve) => {
writeStream.on('finish', resolve);
});
const pdfUrlLink = `${SERVER_DOMAIN}/calendarPDF/${fileName}.pdf`;
return pdfUrlLink;
// return results;
};
// ----------------->>>> Conflcit data <<<<--------------------
export const conflictCount = async (userId) => {
const events = await EventModel.findAll({
where: {
user_id: userId,
conflict: true,
status: 'active',
},
attributes: [
'event_title',
'start_time',
'end_time',
'category',
'subCategory',
'date',
'event_description',
'id',
],
raw: true,
order: [
['event_title', 'ASC'],
['date', 'ASC'],
],
});
// Group events by title
const groupedEvents = {};
// Find overlapping conflicts
events.forEach((event) => {
const key = event.event_title;
if (!groupedEvents[key]) {
groupedEvents[key] = {
events: [],
totalConflicts: 0,
};
}
// Find overlapping events
const overlappingEventsMap = new Map();
events.forEach((otherEvent) => {
if (event.id !== otherEvent.id && event.date === otherEvent.date) {
// Convert times to comparable values
const eventStart = new Date(`2000-01-01T${event.start_time}`);
const eventEnd = new Date(`2000-01-01T${event.end_time}`);
const otherStart = new Date(`2000-01-01T${otherEvent.start_time}`);
const otherEnd = new Date(`2000-01-01T${otherEvent.end_time}`);
// Check if times overlap
if (eventStart < otherEnd && eventEnd > otherStart) {
const existing = overlappingEventsMap.get(otherEvent.event_title);
if (existing) {
existing.count += 1;
} else {
overlappingEventsMap.set(otherEvent.event_title, {
id: otherEvent.id,
title: otherEvent.event_title,
count: 1,
start_time: otherEvent.start_time,
end_time: otherEvent.end_time,
category: otherEvent.category,
});
}
}
}
});
event.overlapping_events = Array.from(overlappingEventsMap.values()); // Convert Map to array of objects
groupedEvents[key].events.push(event);
groupedEvents[key].totalConflicts++;
});
// Unique event count based on title, start, end, category, subCategory
const uniqueEvents = new Set();
events.forEach((event) => {
const eventKey = `${event.event_title}-${event.start_time}-${event.end_time}-${event.category}-${event.subCategory}`;
uniqueEvents.add(eventKey);
});
return { total: uniqueEvents.size, groupedEvents };
};