HEX
Server: Apache/2.4.52 (Ubuntu)
System: Linux spn-python 5.15.0-89-generic #99-Ubuntu SMP Mon Oct 30 20:42:41 UTC 2023 x86_64
User: arjun (1000)
PHP: 8.1.2-1ubuntu2.20
Disabled: NONE
Upload Files
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 };
};