initial commit - code moved to separate repo

This commit is contained in:
Dobromir Popov
2024-02-22 04:19:38 +02:00
commit 560d503219
240 changed files with 105125 additions and 0 deletions

480
src/helpers/calendar.js Normal file
View File

@ -0,0 +1,480 @@
const fs = require("fs").promises;
const path = require("path");
const process = require("process");
const { google } = require("googleapis");
const { OAuth2Client } = require("google-auth-library");
const http = require("http");
const url = require("url");
// const destroyer = require('server-destroy');
const CON = require("./const");
const TOKEN_PATH = path.join(process.cwd(), "content/token.json");
const CREDENTIALS_PATH = path.join(
process.cwd(),
"content/client_secret_926212607479-d3m8hm8f8esp3rf1639prskn445sa01v.apps.googleusercontent.com.json"
);
//generates iCalendar file for a single shift
GenerateICS = function (shifts) {
// https://stackoverflow.com/questions/3665115/create-a-file-in-memory-for-user-to-download-not-through-server
var content = "BEGIN:VCALENDAR\n";
content += "VERSION:2.0\n";
content += "PRODID:-//JW Cart//Shift Info//EN\n";
content += "CALSCALE:GREGORIAN\n";
content += "METHOD:PUBLISH\n";
content += "X-WR-CALNAME:Колички София-Изток\n";
content += "X-WR-TIMEZONE:Europe/Sofia\n";
content += "BEGIN:VTIMEZONE\n";
content += "TZID:Europe/Sofia\n";
content += "BEGIN:DAYLIGHT\n";
content += "TZOFFSETFROM:+0200\n";
content += "TZOFFSETTO:+0300\n";
content += "TZNAME:EEST\n";
content += "DTSTART:19700329T020000\n";
content += "RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\n";
content += "END:DAYLIGHT\n";
content += "BEGIN:STANDARD\n";
content += "TZOFFSETFROM:+0300\n";
content += "TZOFFSETTO:+0200\n";
content += "TZNAME:EET\n";
content += "DTSTART:19701025T030000\n";
content += "RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\n";
content += "END:STANDARD\n";
content += "END:VTIMEZONE\n";
content += shifts?.map((shift) => {
var content = "BEGIN:VEVENT\n";
content += "DTSTAMP:" + new Date().getTime() + "\n";
content += "UID:" + shift.id + "\n";
content +=
"DTSTART;TZID=Europe/Sofia:" +
CON.GetDateTimeShort(shift.startTime) +
"\n";
content +=
"DTEND;TZID=Europe/Sofia:" +
CON.GetDateTimeShort(shift.endTime.getTime()) +
"\n";
content += "LOCATION:" + shift.cartEvent.location.name + "\n";
// content += "SUMMARY:" + shift.cartEvent.location.name + "\n";
// Adding a VALARM component for a reminder 1 day before the event
content += "BEGIN:VALARM\n";
content += "TRIGGER:-P1D\n"; // Trigger alarm 1 day before the event
content += "ACTION:DISPLAY\n";
content += "DESCRIPTION:Reminder\n";
content += "END:VALARM\n";
content += "END:VEVENT";
return content;
})
.join("\n");
content += "END:VCALENDAR\n";
//encode content in base64 to be send as attachment
return Buffer.from(content).toString("base64");
};
// /**
// * Reads previously authorized credentials from the save file.
// *
// * @return {Promise<OAuth2Client|null>}
// */
// loadSavedCredentialsIfExist = async function loadSavedCredentialsIfExist() {
// try {
// const content = await fs.readFile(TOKEN_PATH);
// const credentials = JSON.parse(content);
// return google.auth.fromJSON(credentials);
// } catch (err) {
// return null;
// }
// }
// /**
// * Serializes credentials to a file compatible with GoogleAUth.fromJSON.
// *
// * @param {OAuth2Client} client
// * @return {Promise<void>}
// */
// saveCredentials = async function saveCredentials(client) {
// const content = await fs.readFile(CREDENTIALS_PATH);
// const keys = JSON.parse(content);
// const key = keys.installed || keys.web;
// const payload = JSON.stringify({
// type: 'authorized_user',
// client_id: key.client_id,
// client_secret: key.client_secret,
// refresh_token: client.credentials.refresh_token,
// });
// await fs.writeFile(TOKEN_PATH, payload);
// }
// /**
// * Load or request or authorization to call APIs.
// *
// */
// authorize = async function authorize() {
// let client = await exports.loadSavedCredentialsIfExist();
// if (client) {
// return client;
// }
// client = await exports.authenticate({
// scopes: SCOPES,
// keyfilePath: CREDENTIALS_PATH,
// });
// if (client.credentials) {
// await saveCredentials(client);
// }
// return client;
// }
// /**
// * Lists the next 10 events on the user's primary calendar.
// * @param {google.auth.OAuth2} auth An authorized OAuth2 client.
// */
// listEvents = async function listEvents(auth) {
// const calendar = google.calendar({ version: 'v3', auth });
// const res = await calendar.events.list({
// calendarId: 'primary',
// timeMin: new Date().toISOString(),
// maxResults: 10,
// singleEvents: true,
// orderBy: 'startTime',
// });
// const events = res.data.items;
// if (!events || events.length === 0) {
// console.log('No upcoming events found.');
// return;
// }
// console.log('Upcoming 10 events:');
// events.map((event, i) => {
// const start = event.start.dateTime || event.start.date;
// console.log(`${start} - ${event.summary}`);
// });
// }
// //usage
// GetEvents = async function GetEvents(dateTime = new Date()) {
// var events = exports.authorize().then(exports.listEvents).catch(console.error);
// }
// // const calendar = google.calendar({ version: 'v3', auth });
// const eventTest = {
// summary: 'Test Event',
// start: {
// dateTime: '2023-01-01T09:00:00-07:00',
// timeZone: 'Europe/Sofia',
// },
// end: {
// dateTime: '2023-01-01T17:00:00-07:00',
// timeZone: 'Europe/Sofia',
// },
// };
// const TOKEN_PATH = path.join(process.cwd(), 'content/token.json');
// const CREDENTIALS_PATH = path.join(process.cwd(), 'content/client_secret_926212607479-d3m8hm8f8esp3rf1639prskn445sa01v.apps.googleusercontent.com.json');
createEvent = async function createEvent(id) {
// var eventTest2 = {
// 'summary': 'Google I/O 2015',
// 'location': '800 Howard St., San Francisco, CA 94103',
// 'description': 'A chance to hear more about Google\'s developer products.',
// 'start': {
// 'dateTime': '2015-05-28T09:00:00-07:00',
// 'timeZone': 'America/Los_Angeles',
// },
// 'end': {
// 'dateTime': '2015-05-28T17:00:00-07:00',
// 'timeZone': 'America/Los_Angeles',
// },
// 'recurrence': [
// 'RRULE:FREQ=DAILY;COUNT=2'
// ],
// 'attendees': [
// { 'email': '' },
// ],
// 'reminders': {
// 'useDefault': false,
// 'overrides': [
// { 'method': 'email', 'minutes': 24 * 60 },
// { 'method': 'popup', 'minutes': 10 },
// ],
// },
// };
// var errors;
// var auth = await exports.authorize().then((auth) => {
// const calendar = google.calendar({ version: 'v3', auth });
// const res = calendar.events.insert({
// calendarId: 'primary',
// resource: eventTest,
// }, (err, event) => {
// if (err) {
// console.log(`There was an error creating the event: ${err}`);
// return;
// }
// console.log(`Event created: ${event.data.htmlLink}`);
// });
// }).catch(console.error).then((err) => {
// errors = err;
// });
// return errors;
// }
// authorizeNew = async function authorizeNew() {
// let client = await loadSavedCredentialsIfExist();
// if (client) {
// return client;
// }
// // Set up the Google Calendar API client
// const calendar = google.calendar({ version: 'v3', auth });
// // Load the client secrets from a JSON file
fs.readFile("./path/to/client_secret.json", (err, content) => {
if (err) return console.error("Error loading client secret file:", err);
// Authorize a client with the loaded credentials
authorizeOA(JSON.parse(content), calendar.calendarList.list);
});
if (client.credentials) {
await saveCredentials(client);
}
return client;
};
// var googletoken = axiosInstance.get("/content/google_token.json");
// console.log("googletoken: " + googletoken);
// if (!googletoken) {
// toast.error("Google token not found. redirecting to google login");
// const { data, error } = axiosInstance.get('/content/client_secret_926212607479-d3m8hm8f8esp3rf1639prskn445sa01v.apps.googleusercontent.com.json',
// { headers: { 'Content-Type': 'application/json' } });
// console.log("data: " + JSON.stringify(data));
// console.log("error: " + JSON.stringify(error));
// var secrets = data;
// const { google } = await import('googleapis');
// const { OAuth2 } = google.auth;
// const oAuth2Client = new OAuth2(
// secrets.web.client_id,
// secrets.web.client_secret,
// secrets.web.redirect_uris[0]
// );
// const authorizationUrl = oauth2Client.generateAuthUrl({
// // 'online' (default) or 'offline' (gets refresh_token)
// access_type: 'offline',
// /** Pass in the scopes array defined above.
// * Alternatively, if only one scope is needed, you can pass a scope URL as a string */
// scope: scopes,
// // Enable incremental authorization. Recommended as a best practice.
// include_granted_scopes: true
// });
// return { redirect: authorizationUrl };
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* @param {Object} credentials The authorization client credentials.
* @param {function} callback The callback to call with the authorized client.
*/
function authorizeOA(credentials, callback) {
const { client_secret, client_id, redirect_uris } =
credentials.installed || credentials.web;
const oAuth2Client = new OAuth2Client(
client_id,
client_secret,
redirect_uris[0]
);
// Check if we have previously stored a token
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) return getAccessToken(oAuth2Client, callback);
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* @param {getEventsCallback} callback The callback for the authorized client.
*/
function getAccessToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: "offline",
scope: ["https://www.googleapis.com/auth/calendar"],
});
console.log("Authorize this app by visiting this url:", authUrl);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question("Enter the code from that page here: ", (code) => {
rl.close();
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error("Error retrieving access token", err);
oAuth2Client.setCredentials(token);
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err);
console.log("Token stored to", TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}
// // /**
// // * Lists the next 10 events on the user's primary calendar.
// // * @param {google.auth.OAuth2} auth An authorized OAuth2 client.
// // * @param {getEventsCallback} callback The callback for the authorized client.
// // *
// // */
// // function listEvents(auth) {
// // const calendar = google.calendar({ version: 'v3', auth });
// // calendar.events.list({
// // calendarId: 'primary',
// // timeMin: (new Date()).toISOString(),
// // maxResults: 10,
// // singleEvents: true,
// // orderBy: 'startTime',
// // }, (err, res) => {
// // if (err) return console.error('The API returned an error: ' + err);
// // const events = res.data.items;
// // if (events.length) {
// // console.log('Upcoming 10 events:');
// // events.map((event, i) => {
// // const start = event.start.dateTime || event.start.date;
// // console.log(`${start} - ${event.summary}`);
// // });
// // } else {
// // console.log('No upcoming events found.');
// // }
// // });
// // }
// // [END calendar_quickstart]
// module.exports = {
// GenerateICS,
// createEvent,
// authorize,
// TOKEN_PATH,
// CREDENTIALS_PATH
// }
async function importModules() {
const openModule = await import('open');
const getPortModule = await import('get-port');
return {
open: openModule.default,
getPort: getPortModule.default
};
}
createEvent = async (event) => {
const { open, getPort } = await importModules();
event = event || {
summary: "Test Event",
location: "Online",
start: {
dateTime: "2022-12-19T09:00:00Z",
timeZone: "UTC",
},
end: {
dateTime: "2022-12-19T10:00:00Z",
timeZone: "UTC",
},
};
try {
const fc = await fs.readFile(CREDENTIALS_PATH);
// const auth = new google.auth.fromClientSecret(fc);
const secrets = JSON.parse(fc);
const { google } = await import("googleapis");
const { OAuth2 } = google.auth;
var callbackUrl = secrets.web.redirect_uris[0];
const freePort = await getPort();
const url = new URL(callbackUrl);
url.port = freePort;
callbackUrl = url.toString();
const oAuth2Client = new OAuth2(
secrets.web.client_id,
secrets.web.client_secret,
callbackUrl
);
// Generate the url that will be used for the consent dialog.
const authorizeUrl = oAuth2Client.generateAuthUrl({
access_type: "offline",
scope: "https://www.googleapis.com/auth/userinfo.profile",
});
let doAuth = await new Promise((resolve, reject) => {
const server = http
.createServer(async (req, res) => {
try {
if (req.url.indexOf("/oauth2callback") > -1) {
// acquire the code from the querystring, and close the web server.
const qs = new url.URL(req.url, "http://localhost:3003")
.searchParams;
const code = qs.get("code");
console.log(`Code is ${code}`);
res.end(
"Authentication successful! Please return to the console."
);
server.destroy();
// Now that we have the code, use that to acquire tokens.
const r = await oAuth2Client.getToken(code);
// Make sure to set the credentials on the OAuth2 client.
oAuth2Client.setCredentials(r.tokens);
auth = oAuth2Client;
console.info("Tokens acquired.");
resolve(oAuth2Client);
}
} catch (e) {
reject(e);
}
})
.listen(3003, () => {
// open the browser to the authorize url to start the workflow
open(authorizeUrl, { wait: false }).then((cp) => cp.unref());
console.log(
"Server started for auth flow to complete: " + authorizeUrl
);
});
// destroyer(server);
});
doAuth.then((auth) => {
const calendar = google.calendar({ version: "v3", auth: auth });
calendar.events.insert(
{
auth: auth,
calendarId: "primary",
resource: event,
},
function (err, event) {
if (err) {
console.log(
"There was an error contacting the Calendar service: " + err
);
return;
}
console.log("Event created: %s", event.htmlLink);
}
);
});
} catch (err) {
console.log(err);
}
};
exports.GenerateICS = GenerateICS;
exports.createEvent = createEvent;
createEvent();

669
src/helpers/common.js Normal file
View File

@ -0,0 +1,669 @@
// import { format, startOfMonth, endOfMonth, eachDayOfInterval, startOfWeek, endOfWeek, isSameMonth } from 'date-fns';
const levenshtein = require('fastest-levenshtein');
const path = require("path");
const { PrismaClient } = require('@prisma/client');
const DayOfWeek = require("@prisma/client").DayOfWeek;
const winston = require('winston');
const logger = winston.createLogger({
level: 'info', // Set the default log level
format: winston.format.json(), // Choose a log format
transports: [
// Define where logs should be output (e.g., to a file or the console)
new winston.transports.Console(),
],
});
exports.logger = logger;
// import { DayOfWeek } from "@prisma/client";
//const UserRole = require("@prisma/client").UserRole; - does not allow usage of UserRole in code
// import { UserRole } from '@prisma/client';
// const getConfig = require("next/config");
// exports.nextconfig = getConfig();
// //const { serverRuntimeConfig } = getConfig();
// const dotenv = require("dotenv");
// dotenv.config();
// // dotenv.config({ path: ".env.local" });
exports.isValidPhoneNumber = function (phone) {
if (typeof phone !== 'string') {
return false; // or handle as you see fit
}
// Remove spaces and dashes for validation
const cleanedPhone = phone.replace(/\s|-/g, '');
// Check if the phone starts with '08' and has 10 digits
if (cleanedPhone.startsWith('08') && cleanedPhone.length === 10) {
return true;
}
// Check if the phone starts with '+359' and has the correct length
if (cleanedPhone.startsWith('+359') && cleanedPhone.length === 12) {
return true;
}
// If neither condition is met, the phone number is invalid
return false;
}
exports.getBaseUrl = function (relative = "") {
const host = process.env.NEXT_PUBLIC_HOST || '127.0.0.1';
const port = process.env.NEXT_PUBLIC_PORT ? `:${process.env.NEXT_PUBLIC_PORT}` : '';
const protocol = process.env.NEXT_PUBLIC_PROTOCOL || "https"
//const url = `${protocol}://${host}${port}/${relative.replace(/^\/|\/$/g, '')}/`;
const isRelativeEmpty = !relative || relative.trim() === '';
const formattedRelative = !isRelativeEmpty ? '/' + relative.replace(/^\/|\/$/g, '') : '';
const url = `${protocol}://${host}${port}${formattedRelative}`;
logger.debug("NODE_ENV = ", process.env.NODE_ENV, "protocol:", protocol);
logger.debug("getBaseURL = ", url);
return url;
};
let prisma;
exports.getPrismaClient = function getPrismaClient() {
if (!prisma) {
logger.debug("getPrismaClient: process.env.DATABASE_URL = ", process.env.DATABASE_URL);
prisma = new PrismaClient({
// Optional: Enable logging
// log: ['query', 'info', 'warn', 'error'],
datasources: { db: { url: process.env.DATABASE_URL } },
});
}
return prisma;
}
exports.removeAccentsAndSpecialCharacters = function (str) {
return str.normalize("NFD") // split an accented letter in the base letter and the accent
.replace(/[\u0300-\u036f]/g, "") // remove all previously split accents
.replace(/[^a-zA-Z0-9]/g, ""); // remove all chars not letters and not digits
}
Date.prototype.getDayEuropean = function () {
const day = this.getDay();
return (day === 0) ? 6 : day - 1; // Convert 0 (Sunday) to 6, and decrement other days by 1
};
// Helper function to convert month name to 0-based index
exports.getMonthIndex = function (monthName) {
const monthNames = ["януари", "февруари", "март", "април", "май", "юни", "юли", "август", "септември", "октомври", "ноември", "декември"];
return monthNames.indexOf(monthName.toLowerCase());
};
exports.getMonthName = function (monthIndex) {
const monthNames = ["януари", "февруари", "март", "април", "май", "юни", "юли", "август", "септември", "октомври", "ноември", "декември"];
return monthNames[monthIndex];
};
exports.getMonthNameEn = function (monthIndex) {
const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
return monthNames[monthIndex];
};
exports.dayOfWeekNames = ["понеделник", "вторник", "сряда", "четвъртък", "петък", "събота", "неделя"];
exports.DaysOfWeekArray = [DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday, DayOfWeek.Saturday, DayOfWeek.Sunday];
// Helper function to convert day of week name to 0-based index (Sunday = 0)
exports.getDayOfWeekIndex = function (dayOfWeekName) {
dayOfWeekName = dayOfWeekName.toLowerCase().trim();
const abbreviatedDayOfWeekNames = ["пон", "вт", "ср", "четв", "пет", "съб", "нед"];
const enDayOfWeekNames = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"].map((d) => d.toLowerCase());
let index = exports.dayOfWeekNames.indexOf(dayOfWeekName);
// If not found in the full names, look in the abbreviated names
if (index === -1) {
index = abbreviatedDayOfWeekNames.indexOf(dayOfWeekName);
}
if (index === -1) {
index = enDayOfWeekNames.indexOf(dayOfWeekName);
}
logger.debug("getDayOfWeekIndex: dayOfWeekName = ", dayOfWeekName, "index = ", index);
return index;
};
exports.getDayOfWeekName = function (date) {
const dayOfWeekIndex = date.getDayEuropean();
return exports.dayOfWeekNames[dayOfWeekIndex];
};
exports.getDayOfWeekNameEnEnum = function (date) {
date = new Date(date);
const dayOfWeekIndex = date.getDayEuropean();
const dayOfWeekNames = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
//return enum instead of string
// const dayOfWeekNames: Record<number, DayOfWeek> = {
// 0: DayOfWeek.Monday,
// 1: DayOfWeek.Tuesday,
// 2: DayOfWeek.Wednesday,
// 3: DayOfWeek.Thursday,
// 4: DayOfWeek.Friday,
// 5: DayOfWeek.Saturday,
// 6: DayOfWeek.Sunday
// };
return dayOfWeekNames[dayOfWeekIndex];
}
/*
sets the date portion of the date object to the correct day of week, keeping the time portion
*/
exports.getDayOfWeekDate = function (dayOfWeekName, date = new Date()) {
const targetDay = exports.DaysOfWeekArray.indexOf(dayOfWeekName); // 0 = Sunday, 1 = Monday, etc.
if (targetDay === -1) {
throw new Error('Invalid day of week name provided');
}
// Adjust currentDay to match our custom mapping
let currentDay = date.getDayEuropean();
const daysDifference = targetDay - currentDay;
date.setDate(date.getDate() + daysDifference);
return date;
};
//common.getWeekOfMonth(date)
// exports.getWeekOfMonth = function (date) {
// // Copy date so don't modify original
// date = new Date(date);
// // Adjust to Monday of this week
// date.setDate(date.getDate() + 3 - (date.getDayEuropean() + 6) % 7);
// // Return week number
// const weekNumber = Math.floor((date.getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000 / 7);
// return weekNumber;
// }
exports.getMonthDatesInfo = function (date) {
// get first day of the month
var firstDay = new Date(date.getFullYear(), date.getMonth(), 1);
// get first day of next month
var lastDay = new Date(date.getFullYear(), date.getMonth() + 1, 1);
// lastDay = new Date(lastDay.getTime() - 1);
lastDay = new Date(lastDay.getTime() - 86400000); // 86400000 ms = 1 day
// // get the day of the week of the first day
var firstDayOfWeek = firstDay.getDay(); // 0 = Sunday, 1 = Monday, etc.
// calculate the date of the first Monday
var firstMonday = new Date(firstDay);
if (firstDayOfWeek !== 1) { // Check if the first day is not already a Monday
firstMonday.setDate(firstDay.getDate() + ((7 - firstDayOfWeek) % 7) + 1);
}
// get last Monday
var lastMonday = new Date(lastDay);
if (lastDay.getDay() !== 0) { // Check if the last day is not already a Sunday
lastMonday.setDate(lastDay.getDate() - ((lastDay.getDay() + 6) % 7));
} else {
lastMonday.setDate(lastDay.getDate() - 6);
}
// get Sunday of the last week
var lastSunday = new Date(lastMonday);
lastSunday.setDate(lastMonday.getDate() + 6);
var monthName = exports.getMonthName(date.getMonth());
// var firstDayNextMonth = new Date(date.getFullYear(), date.getMonth() + 1, 1);
// // Calculate the last Sunday of the current month
// // .getDay() returns 0 for Sunday, 1 for Monday, ..., 6 for Saturday
// var lastSunday = new Date(firstDayNextMonth);
// lastSunday.setDate(firstDayNextMonth.getDate() - firstDayNextMonth.getDay());
//logger.debug("Last Sunday: ", lastSunday);
return {
firstDay: firstDay,
lastDay: lastDay,
firstMonday: firstMonday,
lastMonday: lastMonday,
lastSunday: lastSunday,
date: date,
monthName: monthName,
year: date.getFullYear(),
nrOfWeeks: Math.ceil((lastMonday.getDate() - firstMonday.getDate()) / 7)
};
};
exports.getMonthlyScheduleRange = function (date) {
let info = exports.getMonthDatesInfo(date);
// get first day of the month
var firstDay = info.firstDay;
// get last sunday of the month
var lastSunday = info.lastSunday;
return {
firstDay: firstDay,
lastSunday: lastSunday
};
};
exports.getWeekNumber = function (date) {
let info = exports.getMonthDatesInfo(date);
// If the date is before the first full week, return 0
if (date < info.firstMonday) {
return 0;
}
// Calculate the week number based on the first full week
return Math.ceil((date.getDate() - info.firstMonday.getDate() + 1) / 7);
};
exports.getTimeRange = function (start, end) {
start = new Date(start);
end = new Date(end);
const startTime = start.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Sofia' });
const endTime = end.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Sofia' });
return `${startTime}-${endTime}`;
}
//get date in format сряда 1 юли 2020 г.
exports.getDateFormated = function (date) {
const dayOfWeekName = exports.getDayOfWeekName(date);
const day = date.getDate();
const monthName = exports.getMonthName(date.getMonth());
const year = date.getFullYear();
return `${dayOfWeekName} ${day} ${monthName} ${year} г.`;
}
exports.getTimeFomatted = function (date) {
return date.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Sofia' });//timeZone: 'local'
}
/*Todo: remove:
toISOString
slice(0, 10)
getISODateFromDateTime > getISODateOnly
*/
exports.toLocalISO = function (date) {
// Adjust for timezone and get ISO string without 'Z'
const offset = date.getTimezoneOffset() * 60000; // offset in milliseconds
return new Date(date.getTime() - offset).toISOString().slice(0, -1);
};
exports.getISODateOnly = function (date) {
// Use toLocalISO and get the date part only
return this.toLocalISO(date).split('T')[0];
};
exports.getDateFromDateTime = function (day) {
// Adjust for timezone and reset time to midnight
var date = new Date(day);
date.setHours(0, 0, 0, 0);
const offset = date.getTimezoneOffset() * 60000;
return new Date(date.getTime() - offset);
};
// ###########################################
// remove empty nodes from json
// Function to check if an object is empty or contains only empty nodes
exports.isEmpty = function (obj) {
for (const key in obj) {
if (typeof obj[key] === 'object') {
if (!exports.isEmpty(obj[key])) {
return false;
}
} else if (obj[key] !== '') {
return false;
}
}
return true;
}
// Function to remove empty nodes from a JSON object
exports.jsonRemoveEmptyNodes = function (obj) {
const newObj = {};
for (const key in obj) {
if (typeof obj[key] === 'object') {
const cleanedObj = exports.jsonRemoveEmptyNodes(obj[key]);
if (!exports.isEmpty(cleanedObj)) {
newObj[key] = cleanedObj;
}
} else if (obj[key] !== '') {
newObj[key] = obj[key];
}
}
return newObj;
}
// #region string functions
//input:Светослав и Сириел Георгиеви
//output: Светослав Георгиев, Сириел Георгиев
//input: Игор и Галина Москвини
//output: Игор Москвин, Галина Москвин
exports.separateFamilyMemberNames = function (namesArray) {
logger.debug("separateFamilyMemberNames: " + namesArray);
const result = [];
// itteraate over all names
for (let i = 0; i < namesArray.trim().split(' '); i++) {
const name = namesArray[i];
// if name contains " и " then split it into two names
// else just add it to the result
if (name.includes(" и ")) {
const namePairs = name.split(" и ");
const firstNames = namePairs.map((pair) => pair.split(" ")[0]);
// last name is only one. and it is shared for both firstnames
const lastName = namePairs[1].split(" ")[1];
for (let i = 0; i < namePairs.length; i++) {
let firstName = firstNames[i];
if (i === namePairs.length - 1 && namePairs.length % 2 !== 0) {
result.push(`${firstName} ${lastName}`);
} else {
result.push(`${firstName} ${lastName}`);
}
}
} else {
result.push(name);
}
}
//normalize names
for (var i = 0; i < result.length; i++) {
var name = result[i].trim();
//cut last letter of name if it is "a" or "и" (bulgarian feminine ending)
if (name.endsWith("a") || name.endsWith("и")) {
name = name.substring(0, name.length - 1);
}
result[i] = name;
}
return result;
}
exports.separateFamilyMemberNames2 = function (input) {
const nameParts = input.trim().split(' ');
let familyName = nameParts.pop();
const firstNames = nameParts.join(' ').split(' и ');
const result = firstNames.map(firstName => `${firstName} ${familyName}`);
return result;
}
exports.fuzzySearch = function (publishers, searchQuery, distanceThreshold = 0.9) {
const lowerCaseSearchQuery = searchQuery?.toLowerCase();
let results = publishers
.filter((p) => {
try {
const fullName = p.firstName.toLowerCase() + " " + p.lastName.toLowerCase();
const reversedFullName = p.lastName.toLowerCase() + " " + p.firstName.toLowerCase();
const distanceFullName = levenshtein.distance(fullName, lowerCaseSearchQuery);
const distanceReversedFullName = levenshtein.distance(reversedFullName, lowerCaseSearchQuery);
const similarityFullName = 1 - distanceFullName / Math.max(fullName.length, lowerCaseSearchQuery.length);
const similarityReversedFullName = 1 - distanceReversedFullName / Math.max(reversedFullName.length, lowerCaseSearchQuery.length);
// If total fullname length is less than 10 symbols, allow results that are 1 symbol difference
if ((fullName.length < 10 || reversedFullName.length < 10) && (distanceFullName <= 1 || distanceReversedFullName <= 1)) {
return true;
}
return similarityFullName >= distanceThreshold || similarityReversedFullName >= distanceThreshold
} catch (e) {
logger.error(e);
console.log(e);
return false;
}
})
.sort((a, b) => (a.similarity > b.similarity ? -1 : 1));
return (results && results.length > 0) ? results[0] : null;
}
exports.getCurrentNonthFormatted = function () {
const getCurrentYearMonth = () => {
const currentDate = new Date();
const year = currentDate.getFullYear();
const month = String(currentDate.getMonth() + 1).padStart(2, '0'); // Month is 0-indexed
return `${year}-${month}`;
}
return getCurrentYearMonth();
}
exports.getCurrentYearMonth = () => {
const currentDate = new Date();
const year = currentDate.getFullYear();
const month = String(currentDate.getMonth() + 1).padStart(2, '0'); // Month is 0-indexed
return `${year}-${month}`;
}
// format date to 'HH:mm' time string required by the time picker
exports.formatTimeHHmm = function (input) {
// Check if the input is a string or a Date object
const date = (typeof input === 'string') ? new Date(input) : input;
return date.toLocaleTimeString('en-US', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
timeZone: 'Europe/Sofia'
}).substring(0, 5);
}
//parse 'HH:mm' time string to date object
exports.parseTimeHHmm = (timeString) => {
// If timeString is already a date, return it as is
if (timeString instanceof Date) {
return timeString;
}
const [hours, minutes] = timeString.split(':');
const date = new Date();
date.setHours(hours);
date.setMinutes(minutes);
return date;
}
exports.setTimeHHmm = (date, timeStringOrHours) => {
const newDate = new Date(date);
if (typeof timeStringOrHours === 'string' && timeStringOrHours.includes(':')) {
// If hours is a string in "HH:mm" format
const [h, m] = timeStringOrHours.split(':');
newDate.setHours(parseInt(h, 10), parseInt(m, 10), 0, 0);
} else {
// If hours and minutes are provided separately
newDate.setHours(parseInt(timeStringOrHours, 10), 0, 0, 0);
}
return newDate;
};
exports.getTimeInMinutes = (dateOrTimestamp) => {
const date = new Date(dateOrTimestamp);
logger.debug("getTimeInMinutes: date = ", date);
return date.getHours() * 60 + date.getMinutes();
};
exports.parseBool = function (value) {
if (value === undefined) {
return false;
}
value = value || "false";
const truthyValues = ['1', 'y', 'yes', 'true', 't'];
return truthyValues.includes(String(value).toLowerCase());
}
exports.getStartOfWeek = function (date) {
const result = new Date(date); // create a copy of the input date
// If the day is Sunday (0), we set it to -6, otherwise, subtract the current day of the week from 1 (for Monday)
let daysToSubtract = result.getDay() === 0 ? 6 : result.getDay() - 1;
result.setDate(result.getDate() - daysToSubtract);
result.setHours(0, 0, 0, 0); // set time to midnight
return result;
}
exports.getEndOfWeek = function (date) {
const result = new Date(date);
// If the day is Sunday (0 in `getDay()`), no addition is needed. Otherwise, add 7 minus the current day of the week.
let daysToAdd = result.getDay() === 0 ? 0 : 7 - result.getDay();
result.setDate(result.getDate() + daysToAdd);
result.setHours(23, 59, 59, 999); // set time to the last millisecond of the day
return result;
}
exports.getStartOfMonth = function (date) {
date = new Date(date);
return new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0);
}
exports.getEndOfMonth = function (date) {
date = new Date(date);
const result = new Date(date.getFullYear(), date.getMonth() + 1, 0); // 0th day of the next month
result.setHours(23, 59, 59, 999); // set time to the last millisecond of the day
return result;
}
exports.excelSerialDateToDate = function (serial) {
// Set base date as 1899-12-31 (Excel's zero date, ignoring leap year bug)
let baseDate = new Date(1899, 11, 31);
// Extract date and time fractions
let datePart = Math.floor(serial);
let timePart = serial - datePart;
// Compute the complete date
let dateInMs = baseDate.getTime() + datePart * 24 * 60 * 60 * 1000;
let timeInMs = timePart * 24 * 60 * 60 * 1000;
// Construct and return the final date
return new Date(dateInMs + timeInMs);
}
// Function to get a value from the session
exports.getSessionValue = function (session, fieldName, defaultValue = null) {
return session[fieldName] || defaultValue;
};
// Function to update a value in the session
exports.updateSessionValue = function (session, fieldName, value) {
session[fieldName] = value;
session.save(); // Save the session after updating
};
exports.copyToClipboard = function (event, text) {
let contentToCopy;
if (event) {
const spanElement = event.currentTarget;
contentToCopy = spanElement.textContent;
// Animation logic
const originalText = spanElement.textContent;
const originalColor = spanElement.style.backgroundColor; // Store the original color
spanElement.textContent = "имейла копиран";
spanElement.style.backgroundColor = "blue"; // Change color to "info" (blue in this case)
setTimeout(() => {
spanElement.textContent = originalText;
spanElement.style.backgroundColor = originalColor; // Reset the color back to original
}, 3000); // Reset the text and color back to original after 3 seconds
} else {
// If event is null, use the provided text parameter
if (!text) {
console.error("No text provided to copy to clipboard");
return; // Exit the function if no text is provided
}
contentToCopy = text;
}
// Copy contentToCopy to clipboard
const tempTextarea = document.createElement('textarea');
tempTextarea.value = contentToCopy;
document.body.appendChild(tempTextarea);
tempTextarea.select();
document.execCommand('copy');
document.body.removeChild(tempTextarea);
}
// User and auth functions
// import { getSession } from "next-auth/react";
// import { UserRole } from "@prisma/client";
//convert to es6 import
const { getSession } = require("next-auth/react");
const { UserRole } = require("@prisma/client");
exports.getUser = async function (req) {
// Use req if provided (server-side), otherwise call getSession without args (client-side)
const session = req ? await getSession({ req }) : await getSession();
return session?.user;
}
exports.isUserInRole = async function (req, allowedRoles = []) {
const user = await exports.getUser(req);
// Check if the user is authenticated
if (!user) {
return false;
}
// If no specific roles are required, return true as the user is authenticated
if (allowedRoles.length === 0) {
return true;
}
// Check if the user's role is among the allowed roles
// Ensure role exists and is a valid UserRole
const userRole = user.role;
return allowedRoles.includes(userRole);
}
// Utility functions for localStorage operations
exports.setLocalStorage = function (key, value) {
if (typeof window !== 'undefined') {
window.localStorage.setItem(key, JSON.stringify(value));
}
};
exports.getLocalStorage = function (key, defaultValue) {
if (typeof window !== 'undefined') {
const stored = window.localStorage.getItem(key);
if (stored) {
try {
// Attempt to parse the stored JSON data
return JSON.parse(stored);
} catch (error) {
// Handle parsing error
console.error(`Error parsing JSON from localStorage for key '${key}':`, error);
// Return defaultValue or perform other error handling as needed
return defaultValue;
}
}
}
return defaultValue;
};

73
src/helpers/const.js Normal file
View File

@ -0,0 +1,73 @@
const path = require("path");
// const getConfig = require("next/config");
// exports.nextconfig = getConfig();
// const { serverRuntimeConfig } = getConfig();
// const { serverRuntimeConfig, publicRuntimeConfig } = require('next/config').default();
exports.contentPath = path.join(__dirname, "../../content") + path.sep;
// ------------------ constants exports ------------------
exports.monthNamesBG = [
"Януари",
"Февруари",
"Март",
"Април",
"Май",
"Юни",
"Юли",
"Август",
"Септември",
"Октомври",
"Ноември",
"Декември",
];
exports.weekdaysBG = [
"Неделя",
"Понеделник",
"Вторник",
"Сряда",
"Четвъртък",
"Петък",
"Събота",
];
exports.IsDateXMonthsAgo = function (date, months) {
var date = new Date(date);
var now = new Date();
var diff = now.getTime() - date.getTime();
var diffMonths = Math.floor(diff / (1000 * 60 * 60 * 24 * 30));
return diffMonths >= months;
};
exports.GetDateFormat = function (datetime) {
var datetime = new Date(datetime);
return `${datetime.getDate()}.${datetime.getMonth()}.${datetime.getFullYear()}`;
};
//Getdate in format 20221215T120000Z
exports.GetDateTimeShort = function (datetime) {
var datetime = new Date(datetime);
return `${datetime.getFullYear()}${String(datetime.getMonth() + 1).padStart(
2,
"0"
)}${String(datetime.getDate()).padStart(2, "0")}T${String(
datetime.getHours()
).padStart(2, "0")}${String(datetime.getMinutes()).padStart(2, "0")}${String(
datetime.getSeconds()
).padStart(2, "0")}`;
};
//Getdate in format HH:mm
exports.GetTimeFormat = function (datetime) {
//get date in format HH:mm
var datetime = new Date(datetime);
return `${String(datetime.getHours()).padStart(2, "0")}:${String(
datetime.getMinutes()
).padStart(2, "0")}`;
};

270
src/helpers/data.js Normal file
View File

@ -0,0 +1,270 @@
const common = require('./common');
async function findPublisher(names, email, select, getAll = false) {
// Normalize and split the name if provided
let nameParts = names ? names.normalize('NFC').trim().split(/\s+/) : [];
// Construct the select clause
let selectClause = select
? select.split(",").reduce((acc, curr) => ({ ...acc, [curr]: true }), {})
: { id: true, firstName: true, lastName: true };
// Construct the where clause based on the provided data
let whereClause = [];
if (nameParts.length > 0) {
if (nameParts.length === 1) {
// If only one name part is provided, check it against both firstName and lastName
whereClause.push({ OR: [{ firstName: nameParts[0] }, { lastName: nameParts[0] }] });
} else if (nameParts.length === 2) {
// If two name parts are provided, check combinations of firstName and lastName
whereClause.push({ firstName: nameParts[0], lastName: nameParts[1] });
whereClause.push({ firstName: nameParts[1], lastName: nameParts[0] });
} else if (nameParts.length === 3) {
// If three name parts are provided, consider the middle name part of first or last name
whereClause.push({ firstName: `${nameParts[0]} ${nameParts[1]}`, lastName: nameParts[2] });
whereClause.push({ firstName: nameParts[0], lastName: `${nameParts[1]} ${nameParts[2]}` });
} else if (nameParts.length > 3) {
// Join all parts except the last as the first name, and consider the last part as the last name
const firstName = nameParts.slice(0, -1).join(' ');
const lastName = nameParts.slice(-1).join(' ');
whereClause.push({ firstName: firstName, lastName: lastName });
// Alternative case: First part as first name, and join the rest as the last name
const altFirstName = nameParts.slice(0, 1).join(' ');
const altLastName = nameParts.slice(1).join(' ');
whereClause.push({ firstName: altFirstName, lastName: altLastName });
}
}
if (email) {
whereClause.push({ email: email });
}
if (whereClause.length === 0) {
return null; // No search criteria provided
}
const prisma = common.getPrismaClient();
// let publisher = await prisma.publisher.findFirst({
// select: selectClause,
// where: { OR: whereClause }
// });
// Retrieve all matching records
try {
let result = await prisma.publisher.findMany({
select: selectClause,
where: { OR: whereClause }
});
// If getAll is false, take only the first record
if (!getAll && result.length > 0) {
result = result.length > 0 ? [result[0]] : [];
}
if (result.length === 0 && names) {
console.log("No publisher found, trying fuzzy search for '" + names + "'- email: '" + email + "'");
const publishers = await prisma.publisher.findMany();
result = await common.fuzzySearch(publishers, names, 0.90);
console.log("Fuzzy search result: " + result?.firstName + " " + result?.lastName + " - " + result?.email);
}
//always return an array
result = result || [];
return result;
} catch (error) {
console.error("Error finding publisher: ", error);
throw error; // Rethrow the error or handle it as needed
}
}
async function findPublisherAvailability(publisherId, date) {
const prisma = common.getPrismaClient();
const dayOfWeek = common.getDayOfWeekNameEnEnum(date); // Assuming common.getDayOfWeekNameEnEnum returns the day of week
//const weekOfMonth = common.getWeekOfMonth(date); // Assuming common.getWeekOfMonth returns the week of month
date = new Date(date); // Convert to date object if not already
const hours = date.getHours();
const minutes = date.getMinutes();
const potentialAvailabilities = await prisma.availability.findMany({
where: {
publisherId: publisherId,
OR: [
{
// Exact date match
startTime: {
gte: new Date(date.setHours(0, 0, 0, 0)),
lt: new Date(date.setHours(23, 59, 59, 999))
}
},
{
// Correct day of week and before the date, with endDate consideration
dayofweek: dayOfWeek,
OR: [
{
endDate: null
},
{
endDate: {
gt: date
}
}
]
}
]
}
});
// Filter the results based on time and other criteria when not exact date match
const availability = potentialAvailabilities.find(avail => {
const availStartHours = avail.startTime.getHours();
const availStartMinutes = avail.startTime.getMinutes();
const availEndHours = avail.endTime.getHours();
const availEndMinutes = avail.endTime.getMinutes();
const isAfterStartTime = hours > availStartHours || (hours === availStartHours && minutes >= availStartMinutes);
const isBeforeEndTime = hours < availEndHours || (hours === availEndHours && minutes <= availEndMinutes);
// check day of week if not null
const isCorrectDayOfWeek = avail.repeatWeekly ? avail.startTime.getDay() === date.getDay() : true;
const isExactDateMatch = avail.dayOfMonth ? avail.startTime.toDateString() === date.toDateString() : true;
const isBeforeEndDate = avail.repeatWeekly ? true : avail.endTime > date;
//const isCorrectWeekOfMonth = avail.repeatWeekly ? true : avail.weekOfMonth === weekOfMonth;
return isAfterStartTime && isBeforeEndTime && isCorrectDayOfWeek && isExactDateMatch && isBeforeEndDate;
});
return availability;
}
async function getAvailabilities(userId) {
const prismaClient = common.getPrismaClient();
const items = await prismaClient.availability.findMany({
where: {
publisherId: userId,
},
select: {
id: true,
name: true,
isactive: true,
isFromPreviousAssignment: true,
dayofweek: true,
dayOfMonth: true,
startTime: true,
endTime: true,
repeatWeekly: true,
endDate: true,
publisher: {
select: {
firstName: true,
lastName: true,
id: true,
},
},
},
});
// Convert Date objects to ISO strings
const serializableItems = items.map(item => ({
...item,
startTime: item.startTime.toISOString(),
endTime: item.endTime.toISOString(),
name: common.getTimeFomatted(item.startTime) + "-" + common.getTimeFomatted(item.endTime),
//endDate can be null
endDate: item.endDate ? item.endDate.toISOString() : null,
type: 'availability',
// Convert other Date fields similarly if they exist
}));
/*model Assignment {
id Int @id @default(autoincrement())
shift Shift @relation(fields: [shiftId], references: [id], onDelete: Cascade)
shiftId Int
publisher Publisher @relation(fields: [publisherId], references: [id], onDelete: Cascade)
publisherId String
isactive Boolean @default(true)
isConfirmed Boolean @default(false)
isWithTransport Boolean @default(false)
Report Report[]
}*/
//get assignments for this user
const assignments = await prismaClient.assignment.findMany({
where: {
publisherId: userId,
},
select: {
id: true,
isTentative: true,
isConfirmed: true,
isWithTransport: true,
shift: {
select: {
id: true,
name: true,
startTime: true,
endTime: true,
//select all assigned publishers names as name - comma separated
assignments: {
select: {
publisher: {
select: {
firstName: true,
lastName: true,
}
}
}
}
}
}
}
});
const serializableAssignments = assignments.map(item => ({
...item,
startTime: item.shift.startTime.toISOString(),
endTime: item.shift.endTime.toISOString(),
// name: item.shift.publishers.map(p => p.firstName + " " + p.lastName).join(", "),
//name: item.shift.assignments.map(a => a.publisher.firstName[0] + " " + a.publisher.lastName).join(", "),
name: common.getTimeFomatted(new Date(item.shift.startTime)) + "-" + common.getTimeFomatted(new Date(item.shift.endTime)),
type: 'assignment',
//delete shift object
shift: null,
publisher: { id: userId }
}));
serializableItems.push(...serializableAssignments);
return serializableItems;
}
const fs = require('fs');
const path = require('path');
async function runSqlFile(filePath) {
const prisma = common.getPrismaClient();
// Seed the database with some sample data
try {
// Read the SQL file
const sql = fs.readFileSync(filePath, { encoding: 'utf-8' });
// Split the file content by semicolon and filter out empty statements
const statements = sql.split(';').map(s => s.trim()).filter(s => s.length);
// Execute each statement
for (const statement of statements) {
await prisma.$executeRawUnsafe(statement);
}
console.log('SQL script executed successfully.');
} catch (error) {
console.error('Error executing SQL script:', error);
}
}
module.exports = {
findPublisher,
findPublisherAvailability,
runSqlFile,
getAvailabilities
};

162
src/helpers/email.js Normal file
View File

@ -0,0 +1,162 @@
// helper module to send emails with nodemailer
const fs = require("fs");
const { MailtrapClient } = require("mailtrap");
const nodemailer = require("nodemailer");
const CON = require("./const");
const CAL = require("./calendar");
// const { google } = require("googleapis");
// const OAuth2 = google.auth.OAuth2;
const { Shift, Publisher, PrismaClient } = require("@prisma/client");
const TOKEN = process.env.TOKEN || "a7d7147a530235029d74a4c2f228e6ad";
const SENDER_EMAIL = "pw@d-popov.com";
const sender = { name: "JW Cart: Shift Info", email: SENDER_EMAIL };
const client = new MailtrapClient({ token: TOKEN });
const mailtrapTestClient = new MailtrapClient({
username: '8ec69527ff2104',//not working now
password: 'c7bc05f171c96c'
});
// ------------------ Email sending ------------------
var lastResult = null;
function setResult(result) {
lastResult = result;
}
exports.GetLastResult = function () {
return lastResult;
};
exports.SendEmail = async function (to, subject, text, html) {
const message = {
from: sender,
to,
subject,
text,
html,
};
};
exports.SendEmail_Test = async function (to, subject, text, html) {
const message = {
from: sender,
to,
subject,
text,
html,
};
await mailtrapTestClient
.send(message)
.then(console.log, console.error, setResult);
}
// https://mailtrap.io/blog/sending-emails-with-nodemailer/
exports.SendTestEmail = async function (to) {
// await client
// .send({
// from: sender,
// to: [{ email: RECIPIENT_EMAIL }],
// subject: "Hello from Mailtrap!",
// text: "Welcome to Mailtrap Sending!",Shift Info"
// })
// .then(console.log, console.error, setResult);
// return lastResult;
const welcomeImage = fs.readFileSync(
path.join(CON.contentPath, "welcome.png")
);
await client
.send({
category: "test",
custom_variables: {
hello: "world",
year: 2022,
anticipated: true,
},
from: sender,
to: [{ email: to }],
subject: "Hello from Mailtrap!",
html: `<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body style="font-family: sans-serif;">
<div style="display: block; margin: auto; max-width: 600px;" class="main">
<h1 style="font-size: 18px; font-weight: bold; margin-top: 20px">Congrats for sending test email with Mailtrap!</h1>
<p>Inspect it using the tabs you see above and learn how this email can be improved.</p>
<img alt="Inspect with Tabs" src="cid:welcome.png" style="width: 100%;">
<p>Now send your email using our fake SMTP server and integration of your choice!</p>
<p>Good luck! Hope it works.</p>
</div>
<!-- Example of invalid for email html/css, will be detected by Mailtrap: -->
<style>
.main { background-color: white; }
a:hover { border-left-width: 1em; min-height: 2em; }
</style>
</body>
</html>`,
attachments: [
{
filename: "welcome.png",
content_id: "welcome.png",
disposition: "inline",
content: welcomeImage,
},
],
})
.then(console.log, console.error, setResult);
};
exports.SendEmail_NewShifts = async function (publisher, shifts) {
if (shifts.length == 0) return;
var date = new Date(shifts[0].startTime);
//generate ICS calendar links for all shifts
const icsLink = CAL.GenerateICS(shifts);
const shftStr = shifts
.map((s) => {
return ` ${CON.weekdaysBG[s.startTime.getDay()]
} ${CON.GetDateFormat(s.startTime)} ${s.cartEvent.location.name
} ${CON.GetTimeFormat(s.startTime)} - ${CON.GetTimeFormat(
s.endTime
)}`;
})
.join("\n");
await client.send({
from: sender,
to: [
{
email: "dobromir.popov@gmail.com",//publisher.email,
name: publisher.firstName + " " + publisher.lastName,
},
],
subject: "[CCC]: вашите смени през " + CON.monthNamesBG[date.getMonth()],
text:
"Здравейте, " + publisher.firstName + " " + publisher.lastName + "!\n\n" +
"Вие сте регистриран да получавате известия за нови смени на количка.\n" +
`За месец ${CON.monthNamesBG[date.getMonth()]} имате следните смени:\n` +
` ${shftStr} \n\n\n` +
"Поздрави,\n" +
"Специално Свидетелстване София",
attachments: [
{
filename: "calendar.ics",
content_id: "calendar.ics",
disposition: "inline",
content: icsLink,
},
],
})
.then(console.log, console.error, setResult);
};

822
src/helpers/excel.js Normal file
View File

@ -0,0 +1,822 @@
const path = require("path");
const fs = require("fs");
const dotenv = require("dotenv");
dotenv.config();
// dotenv.config({ path: ".env.local" });
const { Shift, Publisher, PrismaClient } = require("@prisma/client");
const CON = require("./const");
const common = require("./common");
const data = require("./data");
//const { fi } = require("date-fns/locale");
//Works with nextjs, but fails with nodejs
// for nodejs
//const api = require("./pages/api/index");
exports.GenerateExcel = async function (req, res) {
const prisma = common.getPrismaClient();
const year = req.params.year;
const month = parseInt(req.params.month) - 1;
const fromDate = new Date(year, month, 1); // month is 0 based
// to last day of the month. special case december
const toDate = new Date(year, month + 1, 0); // month is 0 based
// toDate.setMonth(fromDate.getMonth() + 1);
//get all shiifts for the month
var shifts = await prisma.shift.findMany({
where: {
startTime: {
gte: fromDate,
lt: toDate,
},
},
include: {
cartEvent: {
include: {
location: true,
},
},
publishers: true,
},
});
var filePath = path.join(CON.contentPath, "График КОЛИЧКИ.xlsx");
const bеgin = new Date();
//----------------- exit "График КОЛИЧКИ.xlsx" with exceljs ----------------
const ExcelJS = require("exceljs");
const xjswb = new ExcelJS.Workbook();
if (req.params.process == "1") {
try {
xjswb.xlsx
.readFile(filePath)
.then(function () {
try {
var worksheet = xjswb.getWorksheet(13);
//get row 2 with all the styles
var weekHeader = worksheet.getRow(2);
var newWorksheet = xjswb.addWorksheet(
CON.monthNamesBG[month]
);
newWorksheet.name = CON.monthNamesBG[month].toUpperCase();
//copy each row from the template with all the styles
worksheet.eachRow(
{ includeEmpty: true },
function (row, rowNumber) {
var newRow = newWorksheet.getRow(rowNumber);
newRow.height = row.height;
row.eachCell(
{ includeEmpty: true },
function (cell, colNumber) {
var newCell = newRow.getCell(colNumber);
newCell.value = cell.value;
newCell.font = cell.font;
newCell.alignment = cell.alignment;
newCell.border = cell.border;
newCell.fill = cell.fill;
newCell.numberFormat = cell.numberFormat;
newCell.protection = cell.protection;
}
);
}
);
// worksheet.eachRow({ includeEmpty: true }, function (row, rowNumber) {
// row.eachCell({ includeEmpty: true }, function (cell, colNumber) {
// newWorksheet.getCell(rowNumber, colNumber).value = cell.value;
// });
// });
for (let i = start.row; i <= end.row; i++) {
const leftBorderCell = worksheet.getCell(i, start.col);
//hide original sheet
worksheet.state = "hidden";
//save file
xjswb.xlsx.writeFile(
path.join(
contentPath,
`График КОЛИЧКИ ${year}-${month + 1}.xlsx`
)
);
//send the file to the client
res.setHeader(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
);
res.setHeader(
"Content-Disposition",
"attachment; filename=" +
encodeURI(`График КОЛИЧКИ ${year}-${month + 1}.xlsx`)
);
xjswb.xlsx.write(res);
}
} catch (err) {
console.log(err);
res.end(
"[" +
new Date().toLocaleString() +
"] (" +
(new Date() - bеgin) +
"ms) " +
err
);
}
})
.then(function () {
console.log("done");
//show cyrillic text in response
res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end(
"Генериране на График КОЛИЧКИ${year}-${month}.xlsx завършено успешно за " +
(new Date() - bеgin) +
"ms"
);
});
} catch (err) {
console.log(err);
res.end(err.message);
}
}
//----------------- exit "График КОЛИЧКИ.xlsx" with xlsx-style ----------------
if (req.params.process == "2") {
const XLSX = require('xlsx');
const wb = XLSX.utils.book_new();
wb.Props = {
Title: "График КОЛИЧКИ",
Subject: "График КОЛИЧКИ",
};
wb.SheetNames.push("График КОЛИЧКИ");
const ws_data = [
[1, 2, 3],
[true, false, null, "sheetjs"],
["foo", "bar", new Date("2014-02-19T14:30Z"), "0.3"],
["baz", null, "qux"]
];
const ws = XLSX.utils.aoa_to_sheet(ws_data);
wb.Sheets["График КОЛИЧКИ"] = ws;
const xlsxstyle = require("xlsx-style");
try {
const workbook = xlsxstyle.readFile(filePath);
const sheetNames = workbook.SheetNames;
//find the sheet with the name "Зима" in sheetNames
const sheetName = sheetNames.find((name) => name === "Зима");
// Get the data of "Sheet1"
const data = xlsxstyle.utils.sheet_to_json(workbook.Sheets[sheetNames[2]]);
var worksheet = workbook.Sheets[sheetName];
//copy worksheet to new workbook
//add new worksheet to new workbook with month name
// var newWorksheet = wb.addWorksheet(CON.monthNamesBG[month]);
var rows = xlsxstyle.utils.sheet_to_row_object_array(worksheet, {
header: 1,
});
XLSX.utils.book_append_sheet(wb, worksheet, "_" + CON.monthNamesBG[month]);
//save file
XLSX.writeFile(wb, path.join(CON.contentPath, `рафик КОЛИЧКИ ${year}-${month + 1}.xlsx`));
//save file
xlsxstyle.writeFile(
newWorkbook,
path.join(
contentPath,
`График КОЛИЧКИ ${year}-${month + 1}.xlsx`
)
);
//send the file to the client
res.setHeader(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
);
res.setHeader(
"Content-Disposition",
"attachment; filename=" +
encodeURI(`График КОЛИЧКИ ${year}-${month + 1}.xlsx`)
);
xlsxstyle.writeFile(newWorkbook, res);
res.setHeader("Content-Type", "text/html; charset=utf-8");
res.end(
"Генериране на График КОЛИЧКИ${year}-${month}.xlsx завършено успешно за " +
(new Date() - bеgin) +
" ms!"
);
} catch (err) {
console.log(err);
res.end("[" + new Date().toLocaleString() + "] " + err);
}
}
//----------------- exit "График КОЛИЧКИ.xlsx" with node-excel-export ----------------
if (req.params.process == "3") {
// // uses xlsx-style in the background
// // https://github.com/protobi/js-xlsx#cell-styles
// // https://www.npmjs.com/package/node-excel-export
// var excel = require('node-excel-export');
// var rows = [[
// { value: "EXTREMELY LONG TITLE 1", bold: 1, autoWidth: true },
// { value: "TITLE2" },
// { value: "TITLE3" }
// ]];
// var styles = {
// headerHilight: {
// fill: {
// fgColor: {
// rgb: 'FFE36600'
// }
// },
// font: {
// color: {
// rgb: 'FFFFFFFF'
// },
// sz: 10,
// bold: true,
// // underline: true
// }
// },
// cellOdd: {
// fill: {
// fgColor: {
// rgb: 'FFF8F8F7'
// }
// }
// }
// };
// const heading = [
// [{value: 'b1', style: styles.headerHilight},
// {value: 'd1', style: styles.headerHilight},
// {value: 'e1', style: styles.headerHilight}],
// ['b2', 'd2', 'e2'] // <-- It can be only values
// ];
// var specification = {
// "shiftTime": {
// displayName: 'Смяна',
// headerStyle: styles.headerHilight,
// cellStyle: styles.cellOdd,
// width: 60
// },
// "publisherName": {
// "displayName": 'ПЛИСКА ПОНЕДЕЛНИК',
// "headerStyle": styles.headerHilight,
// "width": 250
// },
// "Col2": {
// "displayName": 'СТАДИОН СРЯДА',
// "headerStyle": styles.headerHilight,
// "width": 215
// },
// "Col3": {
// displayName: 'УНИВЕРСИТЕТ ЧЕТВЪРТЪК',
// headerStyle: styles.headerHilight,
// width: 150
// }
// }
// var report = excel.buildExport(
// [{
// name: `График КОЛИЧКИ ${year}-${month + 1}.xlsx`,
// specification: specification,
// heading: heading, // <- Raw heading array (optional)
// data: rows
// }]);
// //save file to disk
// fs.writeFile(path.join(contentPath, `График КОЛИЧКИ ${year}-${month + 1}.xlsx`), report, 'binary', function (err) { });
// res.setHeader('Content-Type', 'text/html; charset=utf-8');
// res.end("Генериране на График КОЛИЧКИ${year}-${month}.xlsx завършено успешно за " + (new Date() - bеgin) + " ms!");
// //send the file to the client
// console.log("excel genarated in " + (new Date() - bеgin) + "ms");
// // res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
// // res.setHeader("Content-Disposition", "attachment; filename=" + encodeURI(`График КОЛИЧКИ ${year}-${month + 1}.xlsx`));
// // res.end(report);
}
}
exports.ImportFromExcel = function (req, res) {
}
exports.processEvents = async function (events, year, monthNumber, progressCallback, createAvailabilities) {
const prisma = common.getPrismaClient();
const d = new Date(year, monthNumber - 1);//month is 0 based in js
const monthDatesInfo = common.getMonthDatesInfo(d); //CAL.GetMonthDatesInfo(date);
try {
await prisma.shift.deleteMany({
where: {
startTime: {
gte: monthDatesInfo.firstMonday,
lt: monthDatesInfo.lastSunday,
},
},
});
} catch (e) {
console.log(e);
}
var shifts = await prisma.shift.findMany({
where: {
isactive: true,
startTime: {
gte: monthDatesInfo.firstMonday,
lt: monthDatesInfo.lastSunday,
},
}
});
var locations = await prisma.location.findMany({ where: { isactive: true, } });
var cartEvents = await prisma.cartEvent.findMany({ where: { isactive: true, } });
var publishers = await prisma.publisher.findMany({
where: { isactive: true, },
include: {
availabilities: { where: { isactive: true, }, },
assignments: { include: { shift: true, }, },
},
});
const totalEvents = events.length;
for (let i = 0; i < totalEvents; i++) {
const event = events[i];
const progress = (i / totalEvents) * 100;
if (progress > 1) {
progressCallback(progress);
}
try {
const date = new Date(event.date);
let startStr, endStr;
if (event.time) {
startStr = event.time.split("-")[0].trim();
endStr = event.time.split("-")[1].trim();
}
else {
//get the start event time and event.shiftNr and calculate start and end based on that
const shift = shifts.find((s) => s.nr === event.shiftNr);
if (!shift) {
console.warn(`Could not find shift with nr '${event.shiftNr}'`);
continue;
}
startStr = shift.startTime;
endStr = shift.endTime;
}
let st = new Date(event.date);
st.setHours(startStr.split(":")[0]);
st.setMinutes(startStr.split(":")[1]);
const start = st;
st = new Date(event.date);
st.setHours(endStr.split(":")[0]);
st.setMinutes(endStr.split(":")[1]);
const end = st
var location = locations.find((l) =>
l.name.toLowerCase().includes(event.placeOfEvent.toLowerCase())
);
if (!location) {
console.warn(`Could not find location with name '${event.placeOfEvent}'`);
//await prisma.location.create({ data: { name: event.placeOfEvent } });
continue;
}
var dayofWeek = common.getDayOfWeekNameEnEnum(date);
const cartEvent = cartEvents.find(
(ce) =>
ce.locationId === location.id &&
ce.dayofweek === dayofWeek
);
if (!cartEvent) {
console.warn(`Could not find cart event for date '${date}' and location '${event.placeOfEvent}'`);
continue;
}
let shift = shifts.find((s) =>
s.cartEventId === cartEvent.id &&
new Date(s.startTime).getTime() === new Date(start).getTime()
);
if (!shift) {
//if shiftnr = 1, notes = "Докарва" + event.transport
//if shiftnr = 8, notes = "Взема" + event.transport
let note = event.shiftNr === 1 ? "Докарва количка от Люлин - " + event.transport :
event.shiftNr === 6 ? "Прибира количка в Люлин - " + event.transport : "";
const shiftEntity = await prisma.shift.create({
data: {
name: event.dayOfWeek + " " + event.dayOfMonth + ", " + start.toLocaleTimeString() + " - " + end.toLocaleTimeString(),
startTime: start,
endTime: end,
notes: note,
cartEvent: {
connect: {
id: cartEvent.id,
},
},
},
});
shift = shiftEntity;
console.log(`Created shift with ID ${shiftEntity.id} for cart event with ID ${cartEvent.id} on ${date} from ${start.toLocaleTimeString()} to ${end.toLocaleTimeString()}`);
}
for (const nameOrFamily of event.names) {
for (const name of common.separateFamilyMemberNames2(nameOrFamily)) {
var publisher = null
const pubs = await data.findPublisher(name, null, "id,email,firstName,lastName", true);
publisher = pubs[0];
if (!publisher) {
const fuzzyPublisher = common.fuzzySearch(publishers, name);
if (fuzzyPublisher) {
console.log(
`Found publisher '${fuzzyPublisher.firstName} ${fuzzyPublisher.lastName}' through fuzzy search for '${name}'`
);
publisher = fuzzyPublisher;
} else {
console.warn(`NO publisher found! Could not find publisher with name '${name}'. Creating new publisher from known info.`);
//continue;
try {
let firstname = name.substring(0, name.lastIndexOf(" ")).trim();
let lastname = name.substring(name.lastIndexOf(" ") + 1).trim();
// Remove the last letter if it is "а" or "и"
// if (lastname.endsWith('а') || lastname.endsWith('и')) {
if (lastname.endsWith('и')) {
lastname = lastname.slice(0, -1);
}
//if any name is empty, skip this publisher
if (firstname == "" || lastname == "") {
console.warn(`NO publisher found! Could not find publisher with name '${name}', but we need both first and last name. Skipping this publisher.`);
continue;
}
//var name = names[i].trim();
// //cut last letter of name if it is "a" or "и" (bulgarian feminine ending)
// if (name.endsWith("a") || name.endsWith("и")) {
// name = name.substring(0, name.length - 1);
// }
var manualPub = {
email: name.toLowerCase().replace(/ /g, "."), // + "@gmail.com"
firstName: firstname,
lastName: lastname,
isactive: true,
isImported: true,
// role: "EXTERNAL",
};
publisher = await prisma.publisher.create({ data: manualPub });
console.log(`Created publisher with ID ${publisher.id} for name '${name}'`);
// create availability with the same date as the event.
//ToDo: add parameter to control if we want to create availability for each event. can be done whe we import previous shifts.
// if (createAvailabilities) {
// const dayofWeek = common.getDayOfWeekNameEnEnum(date);
// const availability = await prisma.availability.create({
// data: {
// publisherId: publisher.id,
// //date: date,
// dayofweek: dayofWeek,
// startTime: startTime,
// endTime: endTime,
// name: `от предишен график, ${publisher.firstName} ${publisher.lastName}`,
// isFromPreviousAssignment: true,
// isactive: true,
// },
// });
// console.log(`Created WEEKLY availability with ID ${availability.id} for date '${date.toDateString()}' and publisher '${publisher.firstName} ${publisher.lastName}'`);
// }
// const personResponse = await axiosInstance.post("/publishers", manualPub);
// let personId = personResponse.data.id;
} catch (e) {
console.error(`shiftCache: error adding MANUAL publisher to the system (${manualPub.email} ${manualPub.firstName} ${manualPub.lastName}): ` + e);
}
}
}
if (location != null && publisher != null && shift != null) {
const assignment = await prisma.assignment.create({
data: {
//publisherId: publisher.id,
// shiftId: shift.id,
publisher: {
connect: {
id: publisher.id,
},
},
shift: {
connect: {
id: shift.id,
},
},
},
});
//ToDo: fix findPublisherAvailability and creation of availabilities
// check if there is an availability for this publisher on this date, and if not, create one
// const availability = await data.findPublisherAvailability(publisher.id, start);
// if (!availability && createAvailabilities) {
// const dayofWeek = common.getDayOfWeekNameEnEnum(date);
// const availability = await prisma.availability.create({
// data: {
// publisherId: publisher.id,
// //date: date,
// dayofweek: dayofWeek,
// //weekOfMonth: common.getWeekOfMonth(date),
// startTime: start,
// endTime: end,
// name: `от предишен график, ${publisher.firstName} ${publisher.lastName}`,
// isFromPreviousAssignment: true,
// },
// });
// console.log(`Created WEEKLY availability with ID ${availability.id} for date '${date.toDateString()}' and publisher '${publisher.firstName} ${publisher.lastName}'`);
// }
console.log(`Created assignment with ID ${assignment.id} for date '${date.toDateString()}' and location '${event.placeOfEvent}'. publisher: ${publisher.firstName} ${publisher.lastName}}`);
}
}
}
} catch (e) {
console.log(e);
}
}
console.log("Done");
}
//We used GPT to generate the file so far - day by day, by copy-pasting it in the chat. See PROMPTS.md
exports.ReadDocxFileForMonth = async function (filePath, buffer, month, year, progressCallback, createAvailabilities) {
try {
const JSZip = require("jszip");
const zip = new JSZip();
//if filepath is not null read it. otherwise use buffer
if (filePath != null) {
buffer = await fs.readFileSync(filePath);
}
const zipFile = await zip.loadAsync(buffer);
const documentXml = await zipFile.file("word/document.xml").async("string");
const xml2js = require("xml2js");
const parser = new xml2js.Parser({
explicitArray: false,
ignoreAttrs: true,
});
const json = await parser.parseStringPromise(documentXml);
//const tableData = parsedXml['w:document']['w:body']['w:tbl']['w:tr'][1]['w:tc']['w:p']['w:r']['w:t'];
// addParentReferences(json);
// const xmlJs = require('xml-js');
// const jsonstring = xmlJs.xml2json(json, { compact: true });
const cleanedJsonObj = common.jsonRemoveEmptyNodes(json);
//let filename = `График source ${year}-${month}.json`;
//fs.writeFileSync("./content/temp/" + filename, JSON.stringify(cleanedJsonObj)); //initial json source for previous shifts
const extractedData = extractData(cleanedJsonObj, month, year);
//console.log(extractedData);
//modify the file
// try {
// let filename = `График ${year}-${month}.json`;
// fs.writeFileSync("./content/temp/" + filename, JSON.stringify(extractedData));
// } catch (e) {
// console.log(e);
// }
await exports.processEvents(extractedData, year, month, progressCallback, createAvailabilities);
} catch (err) {
console.log(err);
}
};
const weekNames = [
"Понеделник",
"Вторник",
"Сряда",
"Четвъртък",
"Петък",
"Събота",
"Неделя",
];
function findWeekNameNodes(obj, path = []) {
let result = [];
if (Array.isArray(obj)) {
for (let i = 0; i < obj.length; i++) {
const newPath = path.slice();
newPath.push(i);
result = result.concat(findWeekNameNodes(obj[i], newPath));
}
} else if (typeof obj === "object") {
for (const key in obj) {
const newPath = path.slice();
newPath.push(key);
result = result.concat(findWeekNameNodes(obj[key], newPath));
}
} else if (typeof obj === "string") {
const matches = obj.match(/(\S+) (\d+)/);
if (matches && weekNames.includes(matches[1])) {
result.push({ weekName: matches[1], dayOfMonth: matches[2], path });
}
}
return result;
}
function findShifts(node) {
if (node === null || typeof node !== "object") return null;
if (node.hasOwnProperty("w:tbl")) {
return node["w:tbl"];
} else {
return findShifts(node._parent);
}
}
function extractData(parsedJson, month, year) {
const weekNameNodes = findWeekNameNodes(parsedJson);
const data = [];
let lastDay = 0;
let monthOverflow = false;
for (const node of weekNameNodes) {
const { weekName, dayOfMonth, path } = node;
let currentNode = parsedJson;
let baseNode = null;
let parentNode = null;
const dom = parseInt(dayOfMonth);
if (lastDay > dom) {
monthOverflow = true;
}
lastDay = dom;
let date = new Date(year, month - (monthOverflow ? 0 : 1), dom);
for (const key of path) {
if (currentNode[key] && currentNode[key]["w:tc"]) {
parentNode = currentNode;
baseNode = currentNode[key];
}
currentNode = currentNode[key];
}
// for (const key of path) {
// parentNode = currentNode;
// currentNode = currentNode[key];
// }
console.log("Processing " + weekName + " " + dayOfMonth + " " + CON.monthNamesBG[date.getMonth()]);
const dailyData = extractDataForDay(parentNode, weekName, date);
dailyData.forEach((item) => {
if (!data.some((existingItem) =>
existingItem.date === item.date &&
existingItem.shiftNr === item.shiftNr &&
existingItem.dayOfMonth === item.dayOfMonth
)) {
data.push(item);
}
});
}
return data;
}
function extractDataForDay(weeknameNode, weekName, date) {
let result = [];
weekName = weekName.split(" ")[0];
let placeOfEvent = weeknameNode[0]["w:tc"]["w:p"][1]["w:r"]["w:t"] ?? weeknameNode[0]["w:tc"]["w:p"][1]["w:r"]["0"]["w:t"];
let names = [];
let shiftNr = 0;
let tbl = weeknameNode;
for (const trKey in tbl) {
try {
const weekNameNodes = findWeekNameNodes(tbl[trKey]);
if (weekNameNodes.length > 0) {
if (names && names.length > 0) {
shiftNr = 0;
}
dayOfMonth = date.getDate(); //tbl[trKey]["w:tc"]["w:p"][0]["w:r"]["w:t"].match(/(\d+)/)[1];
weekName = weekName; // tbl[trKey]["w:tc"]["w:p"][0]["w:r"]["w:t"].match(/(\S+) (\d+)/)[1];
placeOfEvent = placeOfEvent.trim();//tbl[trKey]["w:tc"]["w:p"][1]["w:r"]["w:t"];
continue;
}
const tr = tbl[trKey];
console.log("Processin row: " + JSON.stringify(tr));
let time = tr["w:tc"]?.[1]?.["w:p"]?.["w:r"]?.["w:t"] ?? tr["w:tc"]?.[1]?.["w:p"]?.[0]?.["w:r"]?.["w:t"];
let transport = tr["w:tc"]?.[3]?.["w:p"]?.["w:r"]?.[1]?.["w:t"];
let namesPath = ["w:tc", 2, "w:p"];
try {
names = [getTextContent(safelyAccess(tr, namesPath))].join("").trim();
} catch (e) {
console.log("try to parse names:" + names + "; " + e + " " + JSON.stringify(tr["w:tc"] + " " + trKey) + e.stack);
}
//if starts with "Докарва" or empty - try the first cell instead of second
if (names.startsWith("Докарва") || names.startsWith("Прибира ") || names === "") {
transport = names;
time = getTextContent(safelyAccess(tr, ["w:tc", 0, "w:p"]));
namesPath = ["w:tc", 1, "w:p"];
try {
names = [getTextContent(safelyAccess(tr, namesPath))].join("").trim();
} catch (e) {
console.log("try to parse names:" + names + "; " + e + " " + JSON.stringify(tr["w:tc"] + " " + trKey) + e.stack);
}
}
names = names.split(",").map((name) => name.trim()).filter((name) => name !== "");
shiftNr++;
result.push({
date,
dayOfWeek: weekName,
dayOfMonth: date.getDate(),
placeOfEvent,
shiftNr,
time,
names,
transport,
});
} catch (e) {
console.log("failed extracting data from node " + trKey + ": " + e + ": " + e.stack);
}
}
return result;
}
function safelyAccess(obj, path) {
return path.reduce((acc, key) => (acc && key in acc) ? acc[key] : undefined, obj);
}
const getTextContent = (obj) => {
let textContent = '';
const traverse = (node) => {
if (typeof node === 'string') {
textContent += node;
} else if (Array.isArray(node)) {
node.forEach((child) => traverse(child));
} else if (typeof node === 'object') {
Object.values(node).forEach((child) => traverse(child));
}
};
traverse(obj);
return textContent;
};
// ImportSchedule("./content/sources/march.json", 3);
//
function GenerateFlatJsonFile() {
let data = JSON.parse(fs.readFileSync("./content/sources/test.json", "utf8"));
let data_flat = transformJsonToFlat(data, 3);
fs.writeFileSync(
"./content/sources/march_flat.json",
JSON.stringify(data_flat)
);
}
function transformJsonToFlat(inputJson, month) {
const output = [];
let dayNr = 0;
inputJson.events.forEach((event) => {
const date = new Date(2023, month - 1, event.dayOfMonth + 1);
dayNr++;
let shiftNr = 1;
event.shifts.forEach((shift) => {
const shiftNames = shift.names.split(",").map((name) => name.trim());
// if (shift.transport !== null) {
// shiftNames.push(shift.transport);
// }
output.push({
date: common.getISODateOnly(date),
dayOfWeek: event.dayOfWeek,
dayNr,
shiftNr: shiftNr++,
time: shift.time,
names: shiftNames,
transport: shift.transport,
});
});
});
return output;
}

4
src/helpers/imports.js Normal file
View File

@ -0,0 +1,4 @@
//??? can we consolidate all imports into one file?
import ProtectedRoute from '../../../components/protectedRoute';
import axiosInstance from '../../../src/axiosSecure';
import Layout from "../../../components/layout";