AI-Tennis-Coach/server/controller/assessment/index.js
2025-02-11 11:23:59 +05:30

349 lines
15 KiB
JavaScript

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, {});
}
});