import { catchAsyncAction, generateToken, makeResponse } from "../../helper/index.js"; import { responseMessages, statusCodes } from '../../helper/makeResponse/index.js'; import { addUserAnswersByUserId, addUserDrillsByUserId, createQuestions, getAllQuestions, getQuestionById, getUserAnswersByUserId, getUserTrainingByUserId, updateQuestionById } from "../../services/assessment.js"; import { getUserById } from "../../services/users.js"; import { privateKey } from "../../config/privateKeys.js"; import axios from "axios"; /** * API handler to add assessment questions. * Validates input and creates new questions in the database. * * @param {Object} req - The request object containing assessment questions. * @param {Object} res - The response object used to send responses back to the client. * @returns {Object} - A response object indicating the success or failure of the operation. */ export const addAssessmentQuestions = catchAsyncAction(async (req, res) => { const questions = req.body.assessment; // Extracting assessment questions from the request body // Input validation: ensure the array is not empty if (!Array.isArray(questions) || questions.length === 0) { return makeResponse(res, statusCodes.BAD_REQUEST, false, 'Request body must be an array of questions.', {}); } for (const questionData of questions) { const { question } = questionData; // Destructuring question from questionData // Validate that all necessary fields are present if (!question) { return makeResponse(res, statusCodes.BAD_REQUEST, false, 'All fields are required.', {}); } questionData.options = JSON.stringify(questionData.options || []); // Stringify options if they exist } try { const questionData = await createQuestions(questions); // Create questions in the database return makeResponse(res, statusCodes.SUCCESS, true, responseMessages.QUESTIONS_CREATED, questionData); } catch (error) { const code = error?.code || statusCodes.SERVER_ERROR; const message = error?.message || responseMessages.SOMETHING_WRONG; return makeResponse(res, code, false, message, {}); } }); /** * API handler to update an assessment question by its ID. * Validates input and updates the question in the database. * * @param {Object} req - The request object containing the question ID and updated data. * @param {Object} res - The response object used to send responses back to the client. * @returns {Object} - A response object indicating the success or failure of the operation. */ export const updateAssessmentQuestionById = catchAsyncAction(async (req, res) => { const { id } = req.params; // Get the question id from the URL const { question, options, orderId } = req.body; // Get the updated data from the request body try { // Find the question by ID const foundQuestion = await getQuestionById(id); if (!foundQuestion) { return makeResponse(res, statusCodes.NOT_FOUND, false, responseMessages.QUESTIONS_NOT_FOUND, {}); } // Update the question with new data foundQuestion.question = question || foundQuestion.question; // Update question if provided foundQuestion.options = JSON.stringify(options) || foundQuestion.options; // Update options if provided foundQuestion.orderId = orderId || foundQuestion.orderId; // Update orderId if provided await updateQuestionById(foundQuestion); // Save the updated question return makeResponse(res, statusCodes.SUCCESS, true, responseMessages.QUESTIONS_UPDATED, foundQuestion); // Return success response } catch (error) { const code = error?.code || statusCodes.SERVER_ERROR; const message = error?.message || responseMessages.SOMETHING_WRONG; return makeResponse(res, code, false, message, {}); } }); /** * API handler to get all assessment questions. * Fetches all questions from the database and returns them. * * @param {Object} req - The request object. * @param {Object} res - The response object used to send responses back to the client. * @returns {Object} - A response object containing the fetched questions or an error message. */ export const getAssessmentQuestions = catchAsyncAction(async (req, res) => { try { const questions = await getAllQuestions(); // Get all questions from DB if (!questions || questions.length === 0) { return makeResponse(res, statusCodes.NOT_FOUND, false, responseMessages.QUESTIONS_NOT_FOUND, {}); } // Return all questions in the response return makeResponse(res, statusCodes.SUCCESS, true, responseMessages.QUESTIONS_FETCHED, questions); } catch (error) { const code = error?.code || statusCodes.SERVER_ERROR; const message = error?.message || responseMessages.SOMETHING_WRONG; return makeResponse(res, code, false, message, {}); } }); /** * API handler to add user assessment responses. * Validates user input and saves user responses to the database. * * @param {Object} req - The request object containing user responses. * @param {Object} res - The response object used to send responses back to the client. * @returns {Object} - A response object indicating the success or failure of the operation. */ export const addUserAssessmentResponse = catchAsyncAction(async (req, res) => { const { userId } = req.user; // Get userId from decoded JWT try { // Fetch current user details to check for changes const currentUser = await getUserById(userId); if (!currentUser) { return makeResponse(res, statusCodes.NOT_FOUND, false, responseMessages.USER_NOT_FOUND, {}); } // Initialize reqBody with default empty values let reqBody = { experience: "", frequency: "", struggle_area: "", playing_style: "", improvement_focus: "", session_duration: "" }; const answers = req.body.answers || []; // Extract answers from request body answers.forEach((answer) => { switch (answer.questionId) { case 1: reqBody.experience = answer.selectedOption; // Set experience based on selectedOption break; case 2: reqBody.frequency = answer.selectedOption; // Set frequency based on selectedOption break; case 3: reqBody.struggle_area = answer.selectedOption; // Set struggle_area based on selectedOption break; case 4: reqBody.playing_style = answer.selectedOption; // Set playing_style based on selectedOption break; case 6: reqBody.improvement_focus = answer.selectedOption; // Set improvement_focus based on selectedOption break; case 7: reqBody.session_duration = answer.selectedOption; // Set session_duration based on selectedOption break; default: break; // Ignore unrecognized question IDs } }); // Check if all keys in reqBody are non-empty const missingFields = []; for (const [key, value] of Object.entries(reqBody)) { if (value === "" || value === null || value === undefined) { missingFields.push(key); } } if (missingFields.length > 0) { // If there are missing fields, return an error response return makeResponse(res, statusCodes.BAD_REQUEST, false, "Please submit all answers", {}); } const analysisData = await getTrainingAnalysis(reqBody); // Get training analysis based on user responses if (!analysisData) { return makeResponse(res, statusCodes.BAD_REQUEST, false, "Something went wrong", {}); } const trainingData = { drills: analysisData.data.drills, recommendation: analysisData.data.recommendation, videos: analysisData.data.videos, yoga_exercises: analysisData.data.yoga_exercises, weekly_plan: analysisData.data.weekly_plan, videos_watched: analysisData.data.videos_watched ?? [], } trainingData.user_id = userId; const addFields = { user_id: userId, answers: JSON.stringify(answers) || "" }; const data = await addUserAnswersByUserId(addFields); // Save user answers const trainingDrillsData = await addUserDrillsByUserId(trainingData); // Save training data console.log(trainingDrillsData); // Log training drills data for debugging return makeResponse(res, statusCodes.SUCCESS, true, responseMessages.DATA_ADDED, { answersRes: data, trainingDrillsData }); } catch (error) { const code = error?.code || statusCodes.SERVER_ERROR; const message = error?.message || responseMessages.SOMETHING_WRONG; return makeResponse(res, code, false, message, {}); } }); /** * API handler to get user answers. * Fetches user answers from the database and returns them. * * @param {Object} req - The request object. * @param {Object} res - The response object used to send responses back to the client. * @returns {Object} - A response object containing the fetched answers or an error message. */ export const getUserAnswers = catchAsyncAction(async (req, res) => { const { userId } = req.user; // Get userId from decoded JWT try { // Fetch current user details to check for changes const currentUser = await getUserById(userId); if (!currentUser) { return makeResponse(res, statusCodes.NOT_FOUND, false, responseMessages.USER_NOT_FOUND, {}); } const data = await getUserAnswersByUserId(userId); // Get user answers // Return the updated user data (excluding password) return makeResponse(res, statusCodes.SUCCESS, true, responseMessages.DATA_FETCHED, data); } catch (error) { const code = error?.code || statusCodes.SERVER_ERROR; const message = error?.message || responseMessages.SOMETHING_WRONG; return makeResponse(res, code, false, message, {}); } }); /** * Helper function to handle AI analysis. * Sends user data to the AI server for analysis and returns the response. * * @param {Object} body - The data to be sent to the AI server. * @returns {Object|null} - The response from the AI server or null if an error occurs. */ const getTrainingAnalysis = async (body) => { const postApiUrl = `${privateKey.AI_SERVER_URL}recommend`; // Construct the API URL console.log(postApiUrl); // Log the API URL for debugging try { const aiResponse = await axios.post(postApiUrl, body); // Send POST request to AI server return aiResponse; } catch (err) { console.error('AI analysis error:', err); return null; } }; /** * API handler to get user training details for a specific day. * Fetches training data from the database and filters it by day. * * @param {Object} req - The request object containing the day query parameter. * @param {Object} res - The response object used to send responses back to the client. * @returns {Object} - A response object containing the training data for the specified day or an error message. */ export const getUserDayTraining = catchAsyncAction(async (req, res) => { const { userId } = req.user; // Get userId from decoded JWT try { const { day } = req.query; // Get day from query parameters const currentUser = await getUserById(userId); if (!currentUser) { return makeResponse(res, statusCodes.NOT_FOUND, false, responseMessages.USER_NOT_FOUND, {}); } const data = await getUserTrainingByUserId(userId); if (!data) { return makeResponse(res, statusCodes.NOT_FOUND, false, 'Training Details Not Found', {}); } let dayObject = data; if (day) { dayObject = data?.dataValues?.weekly_plan.filter(item => item.day === `Day ${day}`)[0]; // Filter training data by day dayObject.videos_watched = data?.dataValues?.videos_watched; } return makeResponse(res, statusCodes.SUCCESS, true, responseMessages.DATA_FETCHED, dayObject ?? 'No training for the day'); // Return training data or message } catch (error) { const code = error?.code || statusCodes.SERVER_ERROR; const message = error?.message || responseMessages.SOMETHING_WRONG; return makeResponse(res, code, false, message, {}); } }); /** * API handler to update the list of watched videos by the user. * Validates input and updates the training record in the database. * * @param {Object} req - The request object containing the day query parameter. * @param {Object} res - The response object used to send responses back to the client. * @returns {Object} - A response object indicating the success or failure of the operation. */ export const updateVideoWatchByUser = catchAsyncAction(async (req, res) => { const { userId } = req.user; // Get userId from decoded JWT try { const { day } = req.query; // Get day from query parameters // Fetch current user details to check for changes const currentUser = await getUserById(userId); if (!currentUser) { return makeResponse(res, statusCodes.NOT_FOUND, false, responseMessages.USER_NOT_FOUND, {}); } const training = await getUserTrainingByUserId(userId); if (!training) { return makeResponse(res, statusCodes.NOT_FOUND, false, 'Training Details Not Found', {}); } // Ensure videos_watched is initialized as an empty array if it's null or undefined if (!Array.isArray(training.videos_watched)) { training.videos_watched = []; } const newVideo = day; // Set new video to be added if (!training.videos_watched.includes(newVideo)) { // Add the new value to the array if it doesn't exist training.videos_watched = [...training.videos_watched, newVideo]; // Update the watched videos array } // Save the updated training record const data = await training.save(); // Persist changes to the database return makeResponse(res, statusCodes.SUCCESS, true, responseMessages.DATA_FETCHED, data); } catch (error) { const code = error?.code || statusCodes.SERVER_ERROR; const message = error?.message || responseMessages.SOMETHING_WRONG; return makeResponse(res, code, false, message, {}); } });