diff --git a/src/controllers/collaborationController.js b/src/controllers/collaborationController.js
index 518e880cf4..4673494e90 100644
--- a/src/controllers/collaborationController.js
+++ b/src/controllers/collaborationController.js
@@ -1,3 +1,6 @@
+const { ObjectId } = require('mongoose').Types;
+// const express = require('express');
+// const axios = require('axios');
const Form = require('../models/JobFormsModel');
const Response = require('../models/jobApplicationsModel');
@@ -88,6 +91,343 @@ exports.getFormResponses = async (req, res) => {
res.status(500).json({ message: 'Error fetching form responses.', error });
}
};
+//
+function tryParseJSON(str) {
+ try {
+ return JSON.parse(str);
+ } catch {
+ return null;
+ }
+}
+// post all responses of a form
+exports.postFormResponseUpload = async (req, res) => {
+ console.log('inside postFormResponseUpload');
+ console.log(req.body);
+ console.log(req.file);
+ const uploadFile = req.file;
+
+ console.log('uploadFile.size');
+ console.log(uploadFile.size);
+
+ const errorMap = {
+ expired_access_token: 'Dropbox access token expired. Please reconnect Dropbox.',
+ shared_link_already_exists: 'A shared link already exists for this file.',
+ path_conflict: 'A file with this name already exists in Dropbox.',
+ };
+
+ const accessTokenResponse = await fetch('https://api.dropboxapi.com/oauth2/token', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: new URLSearchParams({
+ grant_type: 'refresh_token',
+ refresh_token: process.env.DROPBOX_REFRESH_TOKEN,
+ client_id: process.env.DROPBOX_APP_KEY,
+ client_secret: process.env.DROPBOX_APP_SECRET,
+ }),
+ });
+
+ try {
+ // 18 and 19 Software Developers
+ // need to be changed for different jobForms
+ // const resumeFile = req.answers[18];
+ // if (!uploadFile) return res.status (400, 'No file uploaded');
+ if (!uploadFile) return res.status(400).json({ message: `No file uploaded` });
+
+ if (uploadFile.size > 5 * 1024 * 1024) {
+ uploadFile.value = null;
+ return res.status(500).json({ message: `File size should be less than or equal to 5MB` });
+ }
+ console.log('resumeFile');
+ console.log(uploadFile.originalname);
+
+ if (
+ ![
+ 'application/pdf',
+ 'application/doc',
+ 'application/docx',
+ 'image/jpeg',
+ 'image/png',
+ 'image/bmp',
+ ].includes(uploadFile.mimetype)
+ ) {
+ return res.status(500).json({
+ message: `Invalid file type. Please upload a PDF, DOC, DOCX, JPG, PNG, or BMP file.`,
+ });
+ }
+
+ const dropboxPath = `${process.env.DROPBOX_PATH}/${uploadFile.originalname}`;
+
+ // const dropboxPath = `/resumes-upload/${uploadFile.originalname}`;
+ console.log(dropboxPath);
+
+ // to upload the files
+ // const dropboxAccessToken = process.env.DROPBOX_ACCESS_TOKEN;
+
+ const tokenData = await accessTokenResponse.json();
+ console.log('New access token:', tokenData);
+ console.log('New access token:', tokenData.access_token);
+
+ const uploadFileResponse = await fetch('https://content.dropboxapi.com/2/files/upload', {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${tokenData.access_token}`,
+
+ 'Dropbox-API-Arg': JSON.stringify({
+ autorename: true,
+ mode: 'add',
+ path: dropboxPath,
+ }),
+ 'Content-Type': 'application/octet-stream',
+ },
+ body: uploadFile.buffer,
+ });
+ // Dropbox returns JSON *if* successful
+ if (!uploadFileResponse.ok) {
+ const text = await uploadFileResponse.text();
+ console.error('Dropbox error:', text);
+ // throw new Error(`Dropbox upload failed: ${text}`);
+ let shortMessage = 'Dropbox upload failed';
+ const errorObj = tryParseJSON(text);
+ // JSON.parse(text);
+ if (errorObj) {
+ const tag = errorObj?.error?.['.tag'];
+ const summary = errorObj?.error_summary;
+
+ shortMessage = errorMap[tag] || `Dropbox error: ${summary || tag || 'Unknown error'}`;
+ }
+ // else {
+ // Not JSON — plain text error (e.g., malformed token)
+ else if (text.includes('malformed')) {
+ shortMessage = 'Dropbox access token is malformed. Please re-authenticate.';
+ } else if (text.includes('expired_access_token')) {
+ shortMessage = 'Dropbox access token expired. Please reconnect Dropbox.';
+ } else {
+ shortMessage = `Dropbox upload failed: ${text}`;
+ }
+
+ console.log(`shortMessage: ${shortMessage}`);
+ return res.status(500).json({ message: `${shortMessage}` });
+ }
+ const uploadFileResponseData = await uploadFileResponse.json();
+
+ console.log('Dropbox Response');
+ console.log(uploadFileResponseData);
+
+ const uploadFileSharedLinkRes = await fetch(
+ 'https://api.dropboxapi.com/2/sharing/create_shared_link_with_settings',
+ {
+ method: 'POST',
+ headers: {
+ Authorization: `Bearer ${tokenData.access_token}`,
+ 'Content-Type': 'application/json',
+ },
+ settings: {
+ access: 'viewer',
+ allow_download: true,
+ audience: 'public',
+ requested_visibility: 'public',
+ },
+ body: JSON.stringify({
+ path: dropboxPath,
+ }),
+ },
+ );
+ console.log(uploadFileSharedLinkRes);
+
+ if (!uploadFileSharedLinkRes.ok) {
+ const uploadFileSharedLinkResText = await uploadFileSharedLinkRes.text();
+ console.error(uploadFileSharedLinkResText);
+ console.error('Dropbox Shared File Link error:', uploadFileSharedLinkResText);
+ // throw new Error(`Dropbox Shared File Link failed: ${uploadFileSharedLinkResText}`);
+ let sharedShortMessage = 'Dropbox upload failed';
+ const sharedErrorObj = tryParseJSON(uploadFileSharedLinkResText);
+ if (sharedErrorObj) {
+ const sharedTag = sharedErrorObj?.error?.['.tag'];
+ const summary = sharedErrorObj?.error_summary;
+
+ sharedShortMessage =
+ errorMap[sharedTag] || `Dropbox error: ${summary || sharedTag || 'Unknown error'}`;
+ }
+ // else {
+ // Not JSON — plain text error (e.g., malformed token)
+ else if (uploadFileSharedLinkResText.includes('malformed')) {
+ sharedShortMessage = 'Dropbox access token is malformed. Please re-authenticate.';
+ } else if (uploadFileSharedLinkResText.includes('expired_access_token')) {
+ sharedShortMessage = 'Dropbox access token expired. Please reconnect Dropbox.';
+ } else {
+ sharedShortMessage = `Dropbox upload failed: ${uploadFileSharedLinkResText}`;
+ }
+
+ console.log(`shortMessage: ${sharedShortMessage}`);
+
+ return res.status(500).json({ message: `${sharedShortMessage}` });
+ }
+ const uploadFileSharedLinkResData = await uploadFileSharedLinkRes.json();
+ console.log('uploadFileSharedLinkResData');
+ console.log(uploadFileSharedLinkResData);
+
+ res.status(200).json({ data: uploadFileSharedLinkResData });
+ } catch (error) {
+ console.log(error);
+ res.status(500).json({ message: 'Error Uploading', error });
+ }
+};
+
+// post all responses of a form
+exports.postFormResponses = async (req, res) => {
+ console.log('inside postFormResponses');
+ try {
+ const { answers } = req.body;
+ const formId = new ObjectId(req.body.formId);
+ console.log('formId');
+ console.log(formId);
+ console.log('req.body.formId');
+ console.log(req.body.formId);
+ // console.log(answers);
+
+ const respondent = answers[1]?.answer;
+ console.log('respondent');
+ console.log(respondent);
+
+ // Check if form exists
+ // const form = await Form.findById(formId);
+ const form = await Form.findById(formId);
+ if (!form) {
+ return res.status(404).json({ message: 'Form not found.' });
+ }
+
+ if (!form || !Array.isArray(form.questions)) {
+ console.error('jobform not found or has no questions');
+ return;
+ }
+
+ // const questionMap = Object.fromEntries(
+
+ const formMap = Object.fromEntries(
+ form.questions.map((q) => [
+ q._id.toString(),
+ {
+ questionText: q._doc?.questionText || q.questionText,
+ isRequired: q._doc?.isRequired || q.isRequired,
+ answer: null,
+ },
+ ]),
+ );
+ console.log(`formMap: ${JSON.stringify(formMap)}`);
+ let answerError = null;
+ answers.some((ans) => {
+ const qid = ans.questionId.toString();
+ console.log(`qid: ${qid}`);
+ if (!formMap[qid]) {
+ // Return a special error object
+ answerError = {
+ status: 404,
+ message: `Invalid question ID: ${qid}`,
+ };
+ console.log('before return true');
+ return true; // stop iteration
+ }
+
+ formMap[qid].answer = ans.answer;
+ console.log(`formMap[qid].answer: ${formMap[qid].answer}`);
+ console.log('before return false');
+ return false;
+ });
+ console.log(`answerError: ${JSON.stringify(answerError)}`);
+ if (answerError) {
+ return res.status(answerError.status).json({
+ message: answerError.message,
+ });
+ }
+
+ let requiredError = null;
+ Object.entries(formMap).some(([item]) => {
+ const empty = item.answer === '' || item.answer === null || item.answer === undefined;
+
+ if (item.isRequired && empty) {
+ requiredError = {
+ status: 400,
+ message: `Answer required for question: ${item.questionText}`,
+ };
+ return true; // stop iteration
+ }
+
+ return false;
+ });
+
+ if (requiredError) {
+ return res.status(requiredError.status).json({
+ message: requiredError.message,
+ });
+ }
+
+ // Create and save the form
+ const response = new Response({
+ formId,
+ answers,
+ respondent,
+ });
+ console.log(response);
+ // Save the responses for the form
+
+ await response.save();
+
+ // Send email to oneCommunity Email Address
+ const emailSender = require('../utilities/emailSender');
+ const emailBody = `
+ subject: ${form.title} Application Received from ${respondent}!,
+ html:
+
+
Application Received ${respondent}!
+
We’ve successfully received your application. Below are your responses:
+
+
+ ${answers
+ // Sort by _id ascending
+ // .sort((a, b) => (a._id > b._id ? 1 : -1))
+ .map((answer) => {
+ console.log(`answer.questionId: ${answer.questionId}`);
+
+ const computedQuestionId = answer.questionId?.toString?.() ?? String(answer.questionId);
+ console.log(`computedQuestionId: ${computedQuestionId}`);
+ const questionText = formMap[computedQuestionId].questionText || 'Unknown Question';
+ console.log(`questionText: ${questionText}`);
+ return `
+
+
+ ${questionText}
+
+ ${answer.answer}
+
+ `;
+ })
+ .join('')}
+
+
+
+
Regards,
Software Team HGN
+
+`;
+ console.log(`JOB_APPLICATION_RECIPENT_EMAIL is process.env.JOB_APPLICATION_RECIPENT_EMAIL`);
+ emailSender(
+ process.env.JOB_APPLICATION_RECIPIENT_EMAIL, // recipents ,
+ `${form.title} Application Received from ${respondent}!`, // subject
+ // 'Application Received!!!!!!!!!!', // subject
+ emailBody, // message
+ null, // attachments
+ null, // cc
+ 'jae@onecommunityglobal.org', // reply to
+ );
+ console.log('email sent');
+
+ res.status(201).json({ message: 'Responses submitted successfully.', response });
+ } catch (error) {
+ console.log(error);
+ res.status(500).json({ message: 'Error Saving form responses.', error });
+ }
+};
// Get formats of all forms
exports.getAllFormsFormat = async (req, res) => {
diff --git a/src/controllers/jobsController.js b/src/controllers/jobsController.js
index 6c052a9016..afd74af426 100644
--- a/src/controllers/jobsController.js
+++ b/src/controllers/jobsController.js
@@ -1,8 +1,27 @@
const Job = require('../models/jobs'); // Import the Job model
const JobPositionCategory = require('../models/jobPositionCategory');
+const JobForms = require('../models/JobFormsModel');
+// const { hasPermission, canRequestorUpdateUser } = require('../utilities/permissions');
+const helper = require('../utilities/permissions');
+
+function getWordCount(input) {
+ const excludedSymbols = new Set(['.', '#', '$', '*', '-', '–', '—', '_']);
+
+ const wordCount = input
+ .trim()
+ .split(/\s+/) // still use this to handle any whitespace
+ .filter((word) => {
+ // Remove empty strings and standalone symbols
+ const cleanedWord = word.trim();
+ return cleanedWord && !excludedSymbols.has(cleanedWord);
+ }).length;
+
+ return wordCount;
+}
+const EIGHTEEN = 18;
// Controller to fetch all jobs with pagination, search, and filtering
const paginationForJobs = async (req, res) => {
- const { page = 1, limit = 18, search = '', category = '', position = '' } = req.query;
+ const { page = 1, limit = EIGHTEEN, search = '', category = '', position = '' } = req.query;
try {
// Validate query parameters
@@ -140,7 +159,7 @@ const getJobTitleSuggestions = async (req, res) => {
};
const resetJobsFilters = async (req, res) => {
- const { page = 1, limit = 18 } = req.query;
+ const { page = 1, limit = EIGHTEEN } = req.query;
try {
// Validate pagination parameters
@@ -194,7 +213,10 @@ const getCategories = async (req, res) => {
};
const getPositions = async (req, res) => {
try {
- const positions = await JobPositionCategory.distinct('position', {});
+ const categoryIn = req?.query?.category || '';
+ const filterCategory = categoryIn ? { category: categoryIn } : {};
+
+ const positions = await JobPositionCategory.distinct('position', filterCategory);
// Sort categories alphabetically
positions.sort((a, b) => a.localeCompare(b));
@@ -220,9 +242,113 @@ const getJobById = async (req, res) => {
}
};
+const MIN_WORDS = 30;
+
+// Need to check permision here
+const checkPermission = async function (req, permission) {
+ return helper.hasPermission(req.body.requestor, permission);
+};
+
+const validateCategory = async function (category) {
+ const result = await JobPositionCategory.find({ category });
+ if (!result || result.length === 0) {
+ return { error: 'Category not found' };
+ }
+ return null;
+};
+const validateTitle = async function (title) {
+ const jobPosition = await JobPositionCategory.find({ position: title });
+ if (!jobPosition || jobPosition.length === 0) {
+ return { error: 'Title not found' };
+ }
+ return null;
+};
+const validateTitleCategoryMatch = async function (title, category) {
+ const jobPositionCategory = await JobPositionCategory.find({ position: title, category });
+ if (!jobPositionCategory || jobPositionCategory.length === 0) {
+ return { error: 'Title and Category not matched' };
+ }
+ return null;
+};
+const validateApplyLink = async function (applyLink) {
+ const formId = applyLink.split('jobforms/')[1];
+
+ const jobForms = await JobForms.find({ _id: formId });
+ if (!jobForms || jobForms.length === 0) {
+ return { error: 'Mismatched ApplyLink' };
+ }
+ return null;
+};
+
// Controller to create a new job
const createJob = async (req, res) => {
- const { title, category, description, imageUrl, location, applyLink, jobDetailsLink } = req.body;
+ const {
+ title,
+ category,
+ description,
+ imageUrl,
+ // location,
+ applyLink,
+ // jobDetailsLink,
+ requirements,
+ projects,
+ ourCommunity,
+ } = req.body;
+ if (!(await checkPermission(req, 'createCollabJobAds')))
+ return res.status(403).json({ error: 'Forbidden: Insufficient permissions' });
+ // Validate Category and Position
+
+ const categoryValidation = await validateCategory(category);
+ if (categoryValidation) {
+ res.status(500).json(categoryValidation);
+ }
+
+ const titleValidation = await validateTitle(title);
+ if (titleValidation) {
+ res.status(500).json(titleValidation);
+ }
+
+ const titleCategoryMatch = await validateTitleCategoryMatch(title, category);
+ if (titleCategoryMatch) {
+ return res.status(404).json(titleCategoryMatch);
+ }
+
+ const applyLinkValidation = await validateApplyLink(applyLink);
+ if (applyLinkValidation) {
+ return res.status(404).json(applyLinkValidation);
+ }
+
+ // desctiption word count should be >= 30
+ const descriptionWordCount = getWordCount(description);
+
+ if (descriptionWordCount < MIN_WORDS) {
+ return res.status(403).json({
+ error: `Please enter a minimum of ${MIN_WORDS} words for description`,
+ });
+ }
+
+ // requirements word count should be >= 30
+ const requirementsWordCount = getWordCount(requirements);
+
+ if (requirementsWordCount < MIN_WORDS) {
+ return res.status(403).json({
+ error: `Please enter a minimum of ${MIN_WORDS} words for Requireents`,
+ });
+ }
+
+ // projects is required
+ if (!projects || projects.length === 0) {
+ return res.status(403).json({
+ error: 'Please enter at least one project',
+ });
+ }
+ const ourCommunityWordCount = getWordCount(ourCommunity);
+
+ if (ourCommunityWordCount < MIN_WORDS) {
+ return res.status(403).json({
+ error: `Please enter a minimum of ${MIN_WORDS} words for ourCommunity`,
+ });
+ }
try {
// Find the highest displayOrder value currently in use
@@ -231,14 +357,16 @@ const createJob = async (req, res) => {
const newJob = new Job({
title,
- // summaries,
category,
description,
imageUrl,
- location,
+ location: 'remote',
applyLink,
- jobDetailsLink,
+ // jobDetailsLink,
displayOrder: newDisplayOrder,
+ requirements,
+ projects,
+ ourCommunity,
});
const savedJob = await newJob.save();
diff --git a/src/models/JobFormsModel.js b/src/models/JobFormsModel.js
index 8eb3b3e1f5..044d6259a3 100644
--- a/src/models/JobFormsModel.js
+++ b/src/models/JobFormsModel.js
@@ -7,13 +7,13 @@ const formSchema = new mongoose.Schema(
questions: [
{
questionText: { type: String, required: true },
- visible: { type: Boolean, required: true },
+ visible: { type: Boolean, required: true, default: true },
questionType: {
type: String,
required: true,
- enum: ['textbox', 'textarea', 'checkbox', 'radio', 'dropdown', 'date'],
+ enum: ['textbox', 'textarea', 'checkbox', 'radio', 'dropdown', 'date', 'file', 'email'],
},
- isRequired: { type: Boolean, default: false },
+ isRequired: { type: Boolean, default: true },
options: [String],
},
],
diff --git a/src/models/jobApplicationsModel.js b/src/models/jobApplicationsModel.js
index 0e5220dfb9..9bc261d312 100644
--- a/src/models/jobApplicationsModel.js
+++ b/src/models/jobApplicationsModel.js
@@ -1,32 +1,33 @@
-const mongoose = require("mongoose");
+const mongoose = require('mongoose');
const responseSchema = new mongoose.Schema(
{
- formId: {
- type: mongoose.Schema.Types.ObjectId,
- ref: "CollaborationForm",
- required: true
+ formId: {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: 'CollaborationForm',
+ required: true,
}, // Reference to the associated form
answers: [
{
- questionId: {
- type: mongoose.Schema.Types.ObjectId,
- required: true
+ questionText: { type: String, required: true },
+ questionId: {
+ type: mongoose.Schema.Types.ObjectId,
+ required: true,
}, // Reference to the associated question
answer: mongoose.Schema.Types.Mixed, // Store the user's answer (String, Array, etc.)
},
],
- respondent: {
- type: String,
- default: "Anonymous"
+ respondent: {
+ type: String,
+ default: 'Anonymous',
}, // Optional: Name or ID of the respondent
- submittedAt: {
- type: Date,
- default: Date.now
+ submittedAt: {
+ type: Date,
+ default: Date.now,
}, // Timestamp for submission
},
- { timestamps: true }
+ { timestamps: true },
);
-const Response = mongoose.model("JobApplications", responseSchema);
+const Response = mongoose.model('JobApplications', responseSchema);
module.exports = Response;
diff --git a/src/models/jobs.js b/src/models/jobs.js
index dd38c306f8..865f7d2327 100644
--- a/src/models/jobs.js
+++ b/src/models/jobs.js
@@ -6,6 +6,13 @@ const jobSchema = new Schema({
title: { type: String, required: true }, // Job title
category: { type: String, required: true }, // General category (e.g., Engineering, Marketing)
description: { type: String, required: true }, // Detailed job description
+ // about: { type: String, required: true }, // Detailed about section
+ requirements: { type: String, required: true }, // Detailed requirements section
+ // skills: { type: String, required: true }, // Detailed skills section
+ projects: { type: String, required: true }, // list of projects seperated by comma
+ // whoareyou: { type: String, required: true }, // Detailed who are you section
+ ourCommunity: { type: String, required: true }, // Detailed who we are section
+ // whoweare: { type: String, required: true }, // Detailed who we are section
imageUrl: { type: String, required: true }, // URL of the job-related image
location: { type: String, required: true }, // Job location (optional for remote jobs)
applyLink: { type: String, required: true }, // URL for the application form
@@ -15,4 +22,16 @@ const jobSchema = new Schema({
displayOrder: { type: Number, default: 0 }, // Order for displaying jobs on the landing page
});
+jobSchema.pre('validate', function (next) {
+ if (!this.jobDetailsLink) {
+ const baseFrontendUrl =
+ `${process.env.BASE_FRONTEND_URL}/jobDetailsLink` || 'http://localhost:5173/jobDetailsLink';
+ console.log(`baseFrontendUrl: ${baseFrontendUrl}`);
+ this.jobDetailsLink = `${baseFrontendUrl}/${this._id}`;
+ console.log(`this.jobDetailsLink: ${this.jobDetailsLink}`);
+ console.log(`baseFrontendUrl: ${baseFrontendUrl}`);
+ console.log(`this.jobDetailsLink: ${this.jobDetailsLink}`);
+ }
+ next();
+});
module.exports = mongoose.model('Job', jobSchema);
diff --git a/src/routes/collaborationRouter.js b/src/routes/collaborationRouter.js
index cf2c523720..aa647180f1 100644
--- a/src/routes/collaborationRouter.js
+++ b/src/routes/collaborationRouter.js
@@ -1,8 +1,9 @@
+const multer = require('multer');
const express = require('express');
const formController = require('../controllers/collaborationController');
const router = express.Router();
-
+const upload = multer({ storage: multer.memoryStorage() });
// Create a new form
router.post('/jobforms', formController.createForm);
@@ -15,7 +16,17 @@ router.put('/jobforms', formController.updateFormFormat);
router.get('/jobforms/:formId', formController.getFormFormat);
// Get all responses of a form
-router.get('/jobforms/:formId/responses', formController.getFormResponses);
+router.get('/jobforms/responses/formId', formController.getFormResponses);
+
+// post the responseUpload of a form
+router.post(
+ '/jobforms/responses/upload',
+ upload.single('file'),
+ formController.postFormResponseUpload,
+);
+
+// post the responses of a form
+router.post('/jobforms/responses', formController.postFormResponses);
// Question management routes
router.post('/jobforms/:formId/questions', formController.addQuestion);
diff --git a/src/routes/jobsRouter.js b/src/routes/jobsRouter.js
index 7eba92cc93..f2a84d2be3 100644
--- a/src/routes/jobsRouter.js
+++ b/src/routes/jobsRouter.js
@@ -9,7 +9,7 @@ router.get('/reset-filters', jobsController.resetJobsFilters);
router.get('/summaries', jobsController.getJobSummaries);
router.get('/', jobsController.getJobs);
router.get('/categories', jobsController.getCategories);
-router.get('/positions', jobsController.getPositions);
+router.get('/positions/:category?', jobsController.getPositions);
router.get('/:id', jobsController.getJobById);
router.post('/', jobsController.createJob);
router.put('/:id', jobsController.updateJob);
diff --git a/src/startup/middleware.js b/src/startup/middleware.js
index c4815fd794..cfd7d3c21a 100644
--- a/src/startup/middleware.js
+++ b/src/startup/middleware.js
@@ -68,6 +68,21 @@ module.exports = function (app) {
return;
}
+ if (req.originalUrl.startsWith('/api/jobforms') && req.method === 'GET') {
+ next();
+ return;
+ }
+
+ if (req.originalUrl.startsWith('/api/jobforms/responses') && req.method === 'POST') {
+ next();
+ return;
+ }
+
+ if (req.originalUrl.startsWith('/api/jobforms/responses/upload') && req.method === 'POST') {
+ next();
+ return;
+ }
+
// Public analytics tracking endpoints (no auth required)
if (
(req.originalUrl === '/api/applicant-analytics/track-interaction' ||