comment added #1
6
sequelize-cli.js
Normal file
6
sequelize-cli.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
// sequelize-cli.js
|
||||
require('module-alias/register');
|
||||
// const { config } = require('./config/config.json'); // Import the configuration file
|
||||
|
||||
import { sequelize } from './server/loaders';
|
||||
module.exports = sequelize;
|
39
server/app.js
Normal file
39
server/app.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
import express from 'express';
|
||||
import { createServer } from 'http';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import logger from 'morgan';
|
||||
import indexRouter from './routes/index.js';
|
||||
import { renderFile } from 'ejs';
|
||||
import dotenv from "dotenv";
|
||||
import cors from "cors";
|
||||
dotenv.config();
|
||||
import './loaders/index.js';
|
||||
import { makeResponse } from './helper/index.js';
|
||||
|
||||
const app = express();
|
||||
const server = createServer(app);
|
||||
|
||||
app.use(logger('dev'));
|
||||
app.use(express.json());
|
||||
// enable cors
|
||||
app.use(cors());
|
||||
app.options('*', cors());
|
||||
app.use('/public/', express.static('public'));
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
app.use(cookieParser());
|
||||
app.engine('html', renderFile);
|
||||
app.set('view engine', 'html');
|
||||
app.use('/', indexRouter);
|
||||
// cron job
|
||||
// Custom error-handling middleware
|
||||
app.use((err, req, res, next) => {
|
||||
if (err) {
|
||||
const statusCode = err.customCode || 500;
|
||||
const message = err.message || 'Internal Server Error';
|
||||
return makeResponse(res, statusCode, false, message)
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
export default app;
|
77
server/bin/index.js
Normal file
77
server/bin/index.js
Normal file
|
@ -0,0 +1,77 @@
|
|||
import app from "../app.js";
|
||||
import debugLib from "debug";
|
||||
import http from "http";
|
||||
const debug = debugLib("your-project-name:server");
|
||||
import dotenv from "dotenv";
|
||||
import { Server } from "socket.io";
|
||||
dotenv.config();
|
||||
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
const port = process.env.PORT || "3001";
|
||||
console.log("App is running on port", port);
|
||||
app.set("port", port);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
|
||||
const server = http.createServer(app);
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
|
||||
server.listen(port);
|
||||
server.on("error", onError);
|
||||
server.on("listening", onListening);
|
||||
|
||||
const io = new Server(server, {
|
||||
maxHttpBufferSize: 1e9,
|
||||
//transports: ['websocket'], upgrade: false,
|
||||
pingInterval: 1000 * 60 * 5,
|
||||
pingTimeout: 1000 * 60 * 3,
|
||||
cors: {
|
||||
origin: "*",
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
* @param {Object} error - The error object containing details about the error.
|
||||
* @throws {Error} - Rethrows the error if it's not a listening error.
|
||||
*/
|
||||
|
||||
function onError(error) {
|
||||
if (error.syscall !== "listen") {
|
||||
throw error;
|
||||
}
|
||||
|
||||
let bind = typeof port === "string" ? "Pipe " + port : "Port " + port;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case "EACCES":
|
||||
console.error(bind + " requires elevated privileges");
|
||||
process.exit(1);
|
||||
break;
|
||||
case "EADDRINUSE":
|
||||
console.error(bind + " is already in use");
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "listening" event.
|
||||
*/
|
||||
|
||||
function onListening() {
|
||||
const addr = server.address();
|
||||
const bind = typeof addr === "string" ? "pipe " + addr : "port " + addr.port;
|
||||
debug("Listening on " + bind);
|
||||
}
|
9
server/config/config.json
Normal file
9
server/config/config.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"development": {
|
||||
"username": "tennish",
|
||||
"password": "Tensskdb@124",
|
||||
"database": "Ai-Tennis-coach",
|
||||
"host": "14.97.60.131",
|
||||
"dialect": "postgres"
|
||||
}
|
||||
}
|
22
server/config/privateKeys.js
Executable file
22
server/config/privateKeys.js
Executable file
|
@ -0,0 +1,22 @@
|
|||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
let {
|
||||
NODE_ENV,
|
||||
PORT,
|
||||
DB_STRING_DEV,
|
||||
TOKEN_SECRET,
|
||||
REFRESH_TOKEN_SECRET,
|
||||
NODE_SERVER_URL,
|
||||
AI_SERVER_URL
|
||||
} = process.env;
|
||||
|
||||
export const privateKey = {
|
||||
'NODE_ENV':NODE_ENV,
|
||||
'PORT':PORT,
|
||||
'DB_STRING_DEV':DB_STRING_DEV,
|
||||
'TOKEN_SECRET':TOKEN_SECRET,
|
||||
'REFRESH_TOKEN_SECRET':REFRESH_TOKEN_SECRET,
|
||||
'NODE_SERVER_URL':NODE_SERVER_URL,
|
||||
'AI_SERVER_URL':AI_SERVER_URL
|
||||
}
|
348
server/controller/assessment/index.js
Normal file
348
server/controller/assessment/index.js
Normal file
|
@ -0,0 +1,348 @@
|
|||
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, {});
|
||||
}
|
||||
});
|
1389
server/controller/users/index.js
Normal file
1389
server/controller/users/index.js
Normal file
File diff suppressed because it is too large
Load diff
8
server/helper/catchAsyncAction/index.js
Executable file
8
server/helper/catchAsyncAction/index.js
Executable file
|
@ -0,0 +1,8 @@
|
|||
import { makeResponse, statusCodes } from "../index.js";
|
||||
|
||||
// Wrapper for catch block
|
||||
export const catchAsyncAction = fn => {
|
||||
return (req, res, next) => {
|
||||
fn(req, res, next).catch((err) => makeResponse(res, statusCodes.BAD_REQUEST, false, err.message));
|
||||
};
|
||||
};
|
5
server/helper/index.js
Executable file
5
server/helper/index.js
Executable file
|
@ -0,0 +1,5 @@
|
|||
export * from './makeResponse/index.js';
|
||||
export * from './catchAsyncAction/index.js';
|
||||
export * from './qrcode_generator/index.js';
|
||||
export * from './s3/index.js';
|
||||
export * from './jwt/index.js';
|
59
server/helper/jwt/index.js
Normal file
59
server/helper/jwt/index.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
import jwt from 'jsonwebtoken';
|
||||
import { privateKey } from '../../config/privateKeys.js';
|
||||
import { makeResponse } from '../makeResponse/index.js';
|
||||
const { sign, verify } = jwt;
|
||||
|
||||
|
||||
// This function creates tokens based on user data and the rememberMe flag
|
||||
export const generateToken = async (data, rememberMe) => {
|
||||
// Set expiration times
|
||||
const accessTokenExpiresIn = rememberMe ? '365d' : '1d'; // Access token expires in 365 days or 1 day
|
||||
const refreshTokenExpiresIn = '7d'; // Refresh token expires in 7 days
|
||||
|
||||
try {
|
||||
const accessToken = sign({ data }, privateKey.TOKEN_SECRET, { expiresIn: accessTokenExpiresIn });
|
||||
const refreshToken = sign({ data }, privateKey.REFRESH_TOKEN_SECRET, { expiresIn: refreshTokenExpiresIn });
|
||||
|
||||
return { accessToken, refreshToken };
|
||||
} catch (error) {
|
||||
throw new Error("Error generating tokens: " + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// This function checks the validity of a given token using the appropriate secret
|
||||
export const verifyToken = async (token, type = 'access') => {
|
||||
try {
|
||||
// Use different secrets for access token and refresh token
|
||||
const secret = type === 'access' ? privateKey.TOKEN_SECRET : privateKey.REFRESH_TOKEN_SECRET;
|
||||
|
||||
return verify(token, secret);
|
||||
} catch (error) {
|
||||
throw new Error("Token verification failed: " + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
// This function handles the logic for refreshing access tokens using a valid refresh token
|
||||
export const refreshAccessToken = async (req, res) => {
|
||||
const { refreshToken } = req.body;
|
||||
|
||||
if (!refreshToken) {
|
||||
return makeResponse(res, 403, false, 'Refresh Token is required'); // Respond if no refresh token is provided
|
||||
}
|
||||
|
||||
try {
|
||||
// Verify the refresh token
|
||||
const decoded = await verifyToken(refreshToken, 'refresh');
|
||||
|
||||
// Generate new access token (and optionally a new refresh token)
|
||||
const { accessToken, refreshToken: newRefreshToken } = await generateToken(decoded.data, true); // Example with rememberMe = true
|
||||
|
||||
return makeResponse(res, 200, true, 'New access and refresh token', {
|
||||
token: accessToken,
|
||||
refreshToken: newRefreshToken // Optionally send a new refresh token
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return makeResponse(res, 403, false, 'Invalid or expired refresh token'); // Handle invalid refresh token
|
||||
}
|
||||
};
|
51
server/helper/makeResponse/index.js
Executable file
51
server/helper/makeResponse/index.js
Executable file
|
@ -0,0 +1,51 @@
|
|||
export const responseMessages = {
|
||||
USER_CREATED: "Users created successfully!",
|
||||
USER_ALREADY_EXISTS: "Email is already in use",
|
||||
USERNAME_ALREADY_EXISTS: "Username is already in use",
|
||||
INVALID_CREDENTIALS: "Invalid Password",
|
||||
USER_NOT_FOUND: "User doesn't exists",
|
||||
UNAUTHORIZED: "Unauthorized",
|
||||
SOMETHING_WRONG: "Something went wrong",
|
||||
LOGIN_SUCCESS: "Login successfully",
|
||||
USER_UPDATED: "User profile updated successfully",
|
||||
|
||||
QUESTIONS_CREATED: "Questions created successfully!",
|
||||
QUESTIONS_UPDATED: "Question updated successfully",
|
||||
QUESTIONS_NOT_FOUND: 'Question not found',
|
||||
QUESTIONS_FETCHED: "Questions Fetched Successfully",
|
||||
|
||||
DATA_ADDED: "Data added successfully",
|
||||
DATA_FETCHED: "Data fetched successfully",
|
||||
INVALID_PARAMETERS: "Parameters are missing",
|
||||
SHOT_FAILURE: "You haven't played this shot"
|
||||
}
|
||||
|
||||
export const notificationPayload = {}
|
||||
|
||||
export const statusCodes = {
|
||||
'SUCCESS': 200,
|
||||
'RECORD_CREATED': 201,
|
||||
'BAD_REQUEST': 400,
|
||||
'AUTH_ERROR': 401,
|
||||
'FORBIDDEN': 403,
|
||||
'NOT_FOUND': 404,
|
||||
'INVALID_REQUEST': 405,
|
||||
'RECORD_ALREADY_EXISTS': 409,
|
||||
'SERVER_ERROR': 500,
|
||||
'UNAUTHORIZED': 400
|
||||
}
|
||||
|
||||
const makeResponse = async (res, statusCode, success, message, payload = null, meta = {}) =>
|
||||
new Promise(resolve => {
|
||||
res.status(statusCode)
|
||||
.send({
|
||||
success,
|
||||
code: statusCode,
|
||||
message,
|
||||
data: payload,
|
||||
meta
|
||||
});
|
||||
resolve(statusCode);
|
||||
});
|
||||
|
||||
export { makeResponse };
|
12
server/helper/qrcode_generator/index.js
Executable file
12
server/helper/qrcode_generator/index.js
Executable file
|
@ -0,0 +1,12 @@
|
|||
import QRCode from 'qrcode'
|
||||
|
||||
//Genrate QR code
|
||||
const generateQR = async text => {
|
||||
try {
|
||||
return QRCode.toDataURL(text);
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
export default generateQR;
|
35
server/helper/s3/index.js
Executable file
35
server/helper/s3/index.js
Executable file
|
@ -0,0 +1,35 @@
|
|||
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
||||
import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
||||
import { privateKey } from '../../config/privateKeys.js';
|
||||
|
||||
const useGetSignedUrl = (key,bucketName) => {
|
||||
const client = new S3Client({
|
||||
credentials: {
|
||||
accessKeyId: privateKey.S3_ACCESS_KEY,
|
||||
secretAccessKey: privateKey.S3_SECRET_KEY
|
||||
},
|
||||
region: privateKey.S3_REGION
|
||||
})
|
||||
|
||||
const getUrl = async (key) => {
|
||||
try {
|
||||
const url = await getSignedUrl(
|
||||
client,
|
||||
new GetObjectCommand({
|
||||
Bucket: bucketName,
|
||||
Key: key,
|
||||
}),
|
||||
{ expiresIn: 3600 }
|
||||
);
|
||||
return url;
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
};
|
||||
return getUrl(key);
|
||||
|
||||
};
|
||||
|
||||
export {
|
||||
useGetSignedUrl
|
||||
}
|
23
server/loaders/db/index.js
Executable file
23
server/loaders/db/index.js
Executable file
|
@ -0,0 +1,23 @@
|
|||
import { Sequelize, DataTypes } from 'sequelize';
|
||||
import { privateKey } from '../../config/privateKeys.js'; // Make sure your DB string is stored here
|
||||
|
||||
// Create a new Sequelize instance with the PostgreSQL connection string
|
||||
const sequelize = new Sequelize(privateKey.DB_STRING_DEV, {
|
||||
dialect: 'postgres', // The database dialect
|
||||
logging: false, // Disable logging (optional)
|
||||
});
|
||||
|
||||
// Test the connection to ensure it's working
|
||||
const connectDb = async () => {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log('Database connection established');
|
||||
} catch (err) {
|
||||
console.error('Error connecting to the database:', err);
|
||||
}
|
||||
};
|
||||
|
||||
connectDb();
|
||||
|
||||
// Export the Sequelize instance to use in other parts of the application
|
||||
export { sequelize };
|
1
server/loaders/index.js
Executable file
1
server/loaders/index.js
Executable file
|
@ -0,0 +1 @@
|
|||
export * from './db/index.js';
|
39
server/middlewares/auth.js
Normal file
39
server/middlewares/auth.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { statusCodes, makeResponse, responseMessages } from '../helper/index.js';
|
||||
import { verifyToken } from '../helper/index.js';
|
||||
|
||||
// Middleware function for authentication
|
||||
export default async function auth(req, res, next) {
|
||||
try {
|
||||
// Retrieve the token from the request headers
|
||||
const token = req.headers["Authorization"] || req.headers["authorization"];
|
||||
|
||||
// Check if token is present or not
|
||||
if (!token) {
|
||||
|
||||
return makeResponse(res, statusCodes.AUTH_ERROR, false, responseMessages.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
// Verify the token using the verifyToken function
|
||||
const decode = await verifyToken(token, 'access');
|
||||
|
||||
// Verify if token is valid or not
|
||||
if (!decode) {
|
||||
return makeResponse(res, statusCodes.AUTH_ERROR, false, responseMessages.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
// Check if the decoded token contains an email
|
||||
if (decode.data?.email == null || decode.data?.email == undefined) {
|
||||
|
||||
return makeResponse(res, statusCodes.AUTH_ERROR, false, responseMessages.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
// Attach the user data to the request object
|
||||
req.user = decode.data;
|
||||
|
||||
|
||||
next();
|
||||
} catch (err) {
|
||||
|
||||
return makeResponse(res, statusCodes.AUTH_ERROR, false, err.message);
|
||||
}
|
||||
};
|
70
server/middlewares/upload.js
Normal file
70
server/middlewares/upload.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
import multer from "multer";
|
||||
import { diskStorage } from "multer";
|
||||
import path from 'path';
|
||||
|
||||
// Allowed video file extensions
|
||||
const allowedVideoTypes = ['.mp4', '.mov', '.avi', '.temp'];
|
||||
|
||||
// Filter to check if the file is an image or a video (with specific extensions)
|
||||
const multerFilter = (req, file, cb) => {
|
||||
// Check if the file is an image
|
||||
if (file.mimetype.startsWith("image")) {
|
||||
cb(null, true);
|
||||
} else if (file.mimetype.startsWith("video")) {
|
||||
// Check if the video has a valid extension (.mp4, .mov, .avi)
|
||||
const extname = path.extname(file.originalname).toLowerCase();
|
||||
if (allowedVideoTypes.includes(extname)) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error("Only .temp, .mp4, .mov, or .avi video files are allowed."), false); // Reject invalid video types
|
||||
}
|
||||
} else {
|
||||
cb(new Error("Please upload only images or videos."), false); // Reject if neither image nor video
|
||||
}
|
||||
};
|
||||
|
||||
// Storage settings
|
||||
const storage = diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
// Dynamically set the destination folder based on MIME type
|
||||
if (file.mimetype.startsWith("image")) {
|
||||
cb(null, './public/images');
|
||||
} else if (file.mimetype.startsWith("video")) {
|
||||
cb(null, './public/videos');
|
||||
}
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
|
||||
const originalName = path.basename(file.originalname, path.extname(file.originalname));
|
||||
|
||||
|
||||
const customFileName = `${originalName}_${Date.now()}${path.extname(file.originalname)}`;
|
||||
|
||||
|
||||
cb(null, customFileName); // E.g., "myImage_1634567890123.jpg" or "videoClip_1634567890123.mp4"
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
fileFilter: multerFilter,
|
||||
limits: {
|
||||
fileSize: 1024 * 1024 * 50 // 20MB file size limit
|
||||
}
|
||||
});
|
||||
|
||||
// Error handling middleware for multer
|
||||
export const multerErrorHandler = (err, req, res, next) => {
|
||||
if (err instanceof multer.MulterError) {
|
||||
// If the error is related to file size or other Multer error
|
||||
return res.status(400).json({ message: err.message });
|
||||
} else if (err) {
|
||||
|
||||
return res.status(400).json({ message: err.message });
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
export default upload;
|
123
server/migrations/20241212070757-add-metrics-to-user-uploads.cjs
Normal file
123
server/migrations/20241212070757-add-metrics-to-user-uploads.cjs
Normal file
|
@ -0,0 +1,123 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: async (queryInterface, Sequelize) => {
|
||||
const { NODE_ENV } = process.env; // Get the current environment from NODE_ENV
|
||||
|
||||
// Conditional logic based on environment
|
||||
if (NODE_ENV === 'development' || NODE_ENV === 'staging') {
|
||||
console.log(`Applying migrations for ${NODE_ENV} environment...`);
|
||||
|
||||
await queryInterface.changeColumn('user_uploads', 'file_name', {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: false, // You can keep it as required, adjust as needed
|
||||
});
|
||||
|
||||
// Change the 'file_name' column to TEXT type
|
||||
await queryInterface.changeColumn('user_uploads', 'file_path', {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: false, // You can keep it as required, adjust as needed
|
||||
});
|
||||
// Change the 'file_name' column to TEXT type
|
||||
await queryInterface.changeColumn('user_uploads', 'file_type', {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: false, // You can keep it as required, adjust as needed
|
||||
});
|
||||
// Change the 'file_name' column to TEXT type
|
||||
await queryInterface.changeColumn('user_uploads', 'overall_metrics', {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: false, // You can keep it as required, adjust as needed
|
||||
});
|
||||
// Change the 'file_name' column to TEXT type
|
||||
await queryInterface.changeColumn('user_uploads', 'backhand', {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: false, // You can keep it as required, adjust as needed
|
||||
});
|
||||
await queryInterface.changeColumn('user_uploads', 'service', {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: false, // You can keep it as required, adjust as needed
|
||||
});
|
||||
await queryInterface.changeColumn('user_uploads', 'smash', {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: false, // You can keep it as required, adjust as needed
|
||||
});
|
||||
await queryInterface.changeColumn('user_uploads', 'forehand', {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: false, // You can keep it as required, adjust as needed
|
||||
});
|
||||
|
||||
// Add columns only for development or staging environments
|
||||
await queryInterface.addColumn('user_uploads', 'overall_metrics', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
});
|
||||
await queryInterface.addColumn('user_uploads', 'backhand', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
});
|
||||
await queryInterface.addColumn('user_uploads', 'forehand', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
});
|
||||
await queryInterface.addColumn('user_uploads', 'service', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
});
|
||||
await queryInterface.addColumn('user_uploads', 'smash', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Optionally, you could apply the columns to the production environment as well:
|
||||
if (NODE_ENV === 'production') {
|
||||
console.log('Applying migrations for production environment...');
|
||||
// You can add production-specific migration logic if needed here.
|
||||
// For now, it's the same as dev and staging
|
||||
await queryInterface.addColumn('user_uploads', 'overall_metrics', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
});
|
||||
await queryInterface.addColumn('user_uploads', 'backhand', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
});
|
||||
await queryInterface.addColumn('user_uploads', 'forehand', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
});
|
||||
await queryInterface.addColumn('user_uploads', 'service', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
});
|
||||
await queryInterface.addColumn('user_uploads', 'smash', {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
const { NODE_ENV } = process.env; // Get the current environment from NODE_ENV
|
||||
|
||||
// Remove columns based on the environment
|
||||
if (NODE_ENV === 'development' || NODE_ENV === 'staging') {
|
||||
console.log(`Rolling back migrations for ${NODE_ENV} environment...`);
|
||||
await queryInterface.removeColumn('user_uploads', 'overall_metrics');
|
||||
await queryInterface.removeColumn('user_uploads', 'backhand');
|
||||
await queryInterface.removeColumn('user_uploads', 'forehand');
|
||||
await queryInterface.removeColumn('user_uploads', 'service');
|
||||
await queryInterface.removeColumn('user_uploads', 'smash');
|
||||
}
|
||||
|
||||
// Optionally, you could apply the rollback for production as well:
|
||||
if (NODE_ENV === 'production') {
|
||||
console.log('Rolling back migrations for production environment...');
|
||||
await queryInterface.removeColumn('user_uploads', 'overall_metrics');
|
||||
await queryInterface.removeColumn('user_uploads', 'backhand');
|
||||
await queryInterface.removeColumn('user_uploads', 'forehand');
|
||||
await queryInterface.removeColumn('user_uploads', 'service');
|
||||
await queryInterface.removeColumn('user_uploads', 'smash');
|
||||
}
|
||||
}
|
||||
};
|
40
server/model/assessment.js
Normal file
40
server/model/assessment.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { sequelize } from './../loaders/index.js';
|
||||
import { DataTypes } from 'sequelize';
|
||||
|
||||
const assessmentQuestion = sequelize.define('assessmentQuestion', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
orderId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
|
||||
},
|
||||
question: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
options: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
}
|
||||
}, {
|
||||
tableName: 'assessment_questions', // Define the name of the table
|
||||
});
|
||||
|
||||
// Sync the model with the database
|
||||
const syncDb = async () => {
|
||||
try {
|
||||
await sequelize.sync({ alter: false });
|
||||
console.log('assessmentQuestion table created (or already exists)');
|
||||
} catch (err) {
|
||||
console.error('Error syncing the database:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// Call the sync function
|
||||
syncDb();
|
||||
|
||||
export { assessmentQuestion };
|
56
server/model/training.js
Normal file
56
server/model/training.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { sequelize } from './../loaders/index.js';
|
||||
import { DataTypes } from 'sequelize';
|
||||
|
||||
const Training = sequelize.define('training', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
user_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
|
||||
},
|
||||
drills: {
|
||||
type: DataTypes.ARRAY(DataTypes.STRING), // Defines an array of strings,
|
||||
allowNull: true,
|
||||
},
|
||||
recommendation: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
videos: {
|
||||
type: DataTypes.ARRAY(DataTypes.STRING),
|
||||
allowNull: true,
|
||||
},
|
||||
yoga_exercises: {
|
||||
type: DataTypes.ARRAY(DataTypes.STRING),
|
||||
allowNull: true,
|
||||
},
|
||||
weekly_plan: {
|
||||
type: DataTypes.JSONB, // Defines a JSONB column,
|
||||
allowNull: true,
|
||||
},
|
||||
videos_watched: {
|
||||
type: DataTypes.ARRAY(DataTypes.STRING),
|
||||
allowNull: true,
|
||||
},
|
||||
}, {
|
||||
tableName: 'training_content', // Define the name of the table
|
||||
});
|
||||
|
||||
// Sync the model with the database
|
||||
const syncDb = async () => {
|
||||
try {
|
||||
await sequelize.sync({ alter: false });
|
||||
console.log('training table created (or already exists)');
|
||||
} catch (err) {
|
||||
console.error('Error syncing the database:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// Call the sync function
|
||||
syncDb();
|
||||
|
||||
export { Training };
|
70
server/model/user.js
Normal file
70
server/model/user.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { sequelize } from './../loaders/index.js'; // Import the sequelize instance
|
||||
import { DataTypes } from 'sequelize';
|
||||
|
||||
// Define the 'User' model
|
||||
const User = sequelize.define('User', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
},
|
||||
full_name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false,
|
||||
unique: true, // Email should be unique
|
||||
validate: {
|
||||
isEmail: true, // Validate email format
|
||||
},
|
||||
},
|
||||
password: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false, // Password must be provided
|
||||
},
|
||||
profile_pic: {
|
||||
type: DataTypes.TEXT, // Profile picture can be a URL or base64 string
|
||||
allowNull: true,
|
||||
},
|
||||
phone_number: {
|
||||
type: DataTypes.STRING(15),
|
||||
allowNull: true, // Phone number is optional
|
||||
},
|
||||
age: {
|
||||
type: DataTypes.STRING(15),
|
||||
allowNull: true,
|
||||
},
|
||||
gender: {
|
||||
type: DataTypes.STRING(15),
|
||||
allowNull: true,
|
||||
},
|
||||
location: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
}
|
||||
}, {
|
||||
tableName: 'users', // Define the name of the table
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
// Sync the model with the database (creates the table if it doesn't exist)
|
||||
const syncDb = async () => {
|
||||
try {
|
||||
await sequelize.sync({ force: false });
|
||||
console.log('User table created (or already exists)');
|
||||
} catch (err) {
|
||||
console.error('Error syncing the database:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// Call the sync function
|
||||
syncDb();
|
||||
|
||||
// Export the model to use it in other parts of your application
|
||||
export { User };
|
48
server/model/userAnswered.js
Normal file
48
server/model/userAnswered.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { sequelize } from './../loaders/index.js';
|
||||
import { DataTypes } from 'sequelize';
|
||||
import { User } from './user.js'; // Assuming your User model is in a file named user.model.js
|
||||
|
||||
|
||||
const UserAnswers = sequelize.define('UserAnswers', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
user_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'users', // The name of the referenced table (ensure it matches the table name)
|
||||
key: 'id', // The primary key of the User model
|
||||
},
|
||||
onDelete: 'CASCADE', // If the user is deleted, their answers are also deleted
|
||||
},
|
||||
answers: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
}
|
||||
}, {
|
||||
tableName: 'user_answers', // Define the name of the table
|
||||
timestamps: true, // If you don't want Sequelize to automatically create `createdAt` and `updatedAt` columns
|
||||
});
|
||||
|
||||
UserAnswers.belongsTo(User, {
|
||||
foreignKey: 'user_id',
|
||||
as: 'user' // Optional alias for the reverse relation
|
||||
});
|
||||
|
||||
// Sync the model with the database
|
||||
const syncDbnow = async () => {
|
||||
try {
|
||||
await sequelize.sync({ force: false });
|
||||
console.log('User Answer table created (or already exists)');
|
||||
} catch (err) {
|
||||
console.error('Error syncing the database:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// Call the sync function
|
||||
syncDbnow();
|
||||
|
||||
export { UserAnswers };
|
99
server/model/userUpload.js
Normal file
99
server/model/userUpload.js
Normal file
|
@ -0,0 +1,99 @@
|
|||
import { sequelize } from './../loaders/index.js';
|
||||
import { DataTypes } from 'sequelize';
|
||||
import { User } from './user.js'; // Assuming your User model is in a file named user.model.js
|
||||
|
||||
// Define the 'UserUploads' model
|
||||
const UserUploads = sequelize.define(
|
||||
'UserUploads',
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
},
|
||||
user_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'users', // The name of the referenced table (ensure it matches the table name)
|
||||
key: 'id', // The primary key of the User model
|
||||
},
|
||||
onDelete: 'CASCADE', // If the user is deleted, their uploads are also deleted
|
||||
},
|
||||
file_name: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
file_path: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
file_type: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: false,
|
||||
},
|
||||
total_duration: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
upload_day: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false,
|
||||
},
|
||||
upload_time: {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW,
|
||||
},
|
||||
overall_metrics: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
backhand: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
forehand: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
service: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
smash: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
tableName: 'user_uploads', // Define the name of the table
|
||||
timestamps: false, // If you don't want Sequelize to automatically create `createdAt` and `updatedAt` columns
|
||||
}
|
||||
);
|
||||
|
||||
// User model associations
|
||||
User.hasMany(UserUploads, {
|
||||
foreignKey: 'user_id',
|
||||
onDelete: 'CASCADE',
|
||||
as: 'uploads', // Add alias 'uploads' here
|
||||
});
|
||||
|
||||
UserUploads.belongsTo(User, {
|
||||
foreignKey: 'user_id',
|
||||
as: 'user', // Optional alias for the reverse relation
|
||||
});
|
||||
|
||||
// Sync the model with the database
|
||||
const syncDb = async () => {
|
||||
try {
|
||||
await sequelize.sync({ force: false });
|
||||
console.log('UserUploads table created (or already exists)');
|
||||
} catch (err) {
|
||||
console.error('Error syncing the database:', err);
|
||||
}
|
||||
};
|
||||
|
||||
// Call the sync function
|
||||
syncDb();
|
||||
|
||||
export { UserUploads };
|
43
server/models/index.js
Normal file
43
server/models/index.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const Sequelize = require('sequelize');
|
||||
const process = require('process');
|
||||
const basename = path.basename(__filename);
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
const config = require(__dirname + '/../config/config.json')[env];
|
||||
const db = {};
|
||||
|
||||
let sequelize;
|
||||
if (config.use_env_variable) {
|
||||
sequelize = new Sequelize(process.env[config.use_env_variable], config);
|
||||
} else {
|
||||
sequelize = new Sequelize(config.database, config.username, config.password, config);
|
||||
}
|
||||
|
||||
fs
|
||||
.readdirSync(__dirname)
|
||||
.filter(file => {
|
||||
return (
|
||||
file.indexOf('.') !== 0 &&
|
||||
file !== basename &&
|
||||
file.slice(-3) === '.js' &&
|
||||
file.indexOf('.test.js') === -1
|
||||
);
|
||||
})
|
||||
.forEach(file => {
|
||||
const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
|
||||
db[model.name] = model;
|
||||
});
|
||||
|
||||
Object.keys(db).forEach(modelName => {
|
||||
if (db[modelName].associate) {
|
||||
db[modelName].associate(db);
|
||||
}
|
||||
});
|
||||
|
||||
db.sequelize = sequelize;
|
||||
db.Sequelize = Sequelize;
|
||||
|
||||
module.exports = db;
|
14
server/routes/assessment.js
Normal file
14
server/routes/assessment.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import express from "express";
|
||||
import { addAssessmentQuestions, getAssessmentQuestions, updateAssessmentQuestionById, getUserAnswers, addUserAssessmentResponse, getUserDayTraining, updateVideoWatchByUser } from "../controller/assessment/index.js";
|
||||
import auth from "../middlewares/auth.js";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post("/questions", addAssessmentQuestions);
|
||||
router.patch("/update-question/:id", updateAssessmentQuestionById );
|
||||
router.get("/get-all-questions", getAssessmentQuestions);
|
||||
router.post("/add-user-answers", auth, addUserAssessmentResponse);
|
||||
router.get("/get-user-answers", auth, getUserAnswers);
|
||||
router.get("/get-day-wise-training", auth, getUserDayTraining);
|
||||
router.patch("/video-watch-for-day", auth, updateVideoWatchByUser);
|
||||
export default router;
|
15
server/routes/index.js
Normal file
15
server/routes/index.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import express from 'express';
|
||||
import userRoute from './users/user.js';
|
||||
import assessmentRoute from './assessment.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', function (req, res, next) {
|
||||
res.render('index', { title: 'Express' });
|
||||
});
|
||||
|
||||
router.use('/users', userRoute);
|
||||
router.use('/assessment', assessmentRoute);
|
||||
|
||||
export default router;
|
58
server/routes/users/user.js
Normal file
58
server/routes/users/user.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
import express from "express";
|
||||
import {
|
||||
checkUploadCount,
|
||||
getAnalysisOfVideo,
|
||||
getUserLastRecorderVideo,
|
||||
getUserPerformanceStats,
|
||||
getUserRecorderVideo,
|
||||
isQuizPlayed,
|
||||
updateUserProfileHandler,
|
||||
updateUserRecorderMiniVideo,
|
||||
updateUserRecorderVideo,
|
||||
uploadManualEntry,
|
||||
userLogin,
|
||||
userSignUp
|
||||
} from "../../controller/users/index.js";
|
||||
import auth from "../../middlewares/auth.js";
|
||||
import upload from "../../middlewares/upload.js";
|
||||
import { refreshAccessToken } from "../../helper/index.js";
|
||||
|
||||
const router = express.Router(); // Create a new router instance
|
||||
|
||||
// Route to refresh the access token
|
||||
router.post('/refresh-token', refreshAccessToken);
|
||||
|
||||
// Route for user sign-up
|
||||
router.post('/sign-up', userSignUp);
|
||||
|
||||
// Route for user login
|
||||
router.post('/login-in', userLogin);
|
||||
|
||||
// Route to update user profile, requires authentication and file upload
|
||||
router.patch('/update-user-profile', auth, upload.fields([{ name: 'profilePic', maxCount: 1 }]), updateUserProfileHandler);
|
||||
|
||||
// Route to upload a recorder video, requires authentication and checks upload count
|
||||
router.post('/upload-recorder-video', auth, checkUploadCount, upload.fields([{ name: 'userVideo', maxCount: 1 }]), updateUserRecorderVideo);
|
||||
|
||||
// Route to get user uploaded videos, requires authentication
|
||||
router.get('/get-user-uploaded', auth, getUserRecorderVideo);
|
||||
|
||||
// Route to get analysis by uploaded video ID, requires authentication
|
||||
router.get('/get-analysis-by-uploaded-id/:id', auth, getAnalysisOfVideo);
|
||||
|
||||
// Route to get the last recorded video, requires authentication
|
||||
router.get('/last-recorded-video', auth, getUserLastRecorderVideo);
|
||||
|
||||
// Route to get performance stats by week, requires authentication
|
||||
router.get('/performance-stats-by-week', auth, getUserPerformanceStats);
|
||||
|
||||
// Route to check if a quiz has been played by the user, requires authentication
|
||||
router.get('/quiz-played-by-user', auth, isQuizPlayed);
|
||||
|
||||
// Route to upload a manual entry, requires authentication
|
||||
router.post('/upload-manual-entry', auth, uploadManualEntry);
|
||||
|
||||
// Route to upload a mini recorder video, requires authentication and checks upload count
|
||||
router.post('/upload-mini-recorder-video', auth, checkUploadCount, upload.fields([{ name: 'userVideo', maxCount: 1 }]), updateUserRecorderMiniVideo);
|
||||
|
||||
export default router; // Export the router for use in other parts of the application
|
94
server/services/assessment.js
Normal file
94
server/services/assessment.js
Normal file
|
@ -0,0 +1,94 @@
|
|||
import { Op } from "sequelize";
|
||||
import { assessmentQuestion } from "../model/assessment.js";
|
||||
import { Training } from "../model/training.js";
|
||||
import { User } from "../model/user.js";
|
||||
import { UserAnswers } from "../model/userAnswered.js";
|
||||
|
||||
export const createQuestions = async (payload) => {
|
||||
try {
|
||||
// Insert all questions at once using bulkCreate
|
||||
const createdQuestions = await assessmentQuestion.bulkCreate(payload);
|
||||
return createdQuestions; // Return the user object
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw new Error('Error creating questions');
|
||||
}
|
||||
};
|
||||
|
||||
export const getQuestionById = async (id) => {
|
||||
try {
|
||||
const question = await assessmentQuestion.findByPk(id);
|
||||
return question; // Return the user object
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw new Error('Error getting questions');
|
||||
}
|
||||
};
|
||||
|
||||
export const updateQuestionById = async (question) => {
|
||||
try {
|
||||
return await question.save();
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw new Error('Error getting questions');
|
||||
}
|
||||
};
|
||||
|
||||
// Function to get all questions
|
||||
export const getAllQuestions = async () => {
|
||||
try {
|
||||
return await assessmentQuestion.findAll({
|
||||
order: [['id', 'ASC']],
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error fetching questions:', err);
|
||||
throw new Error('Error fetching questions'); // Throw an error if something goes wrong
|
||||
}
|
||||
};
|
||||
|
||||
export const addUserAnswersByUserId = async (payload) => {
|
||||
try {
|
||||
const upload = await UserAnswers.create(payload);
|
||||
return upload;
|
||||
} catch (error) {
|
||||
console.error('Error adding res:', error);
|
||||
throw new Error('Error adding res');
|
||||
}
|
||||
};
|
||||
|
||||
export const addUserDrillsByUserId = async (payload) => {
|
||||
try {
|
||||
const data = await Training.create(payload);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Error adding res drills:', error);
|
||||
throw new Error('Error adding res drills');
|
||||
}
|
||||
};
|
||||
|
||||
export const getUserAnswersByUserId = async (userId) => {
|
||||
try {
|
||||
// Find the user and include their uploads
|
||||
const user = await UserAnswers.findAll({
|
||||
where: { user_id: userId }, // Filter by user ID
|
||||
});
|
||||
return user;
|
||||
} catch (error) {
|
||||
console.error('Error uploading file:', error);
|
||||
throw new Error('Error uploading file');
|
||||
}
|
||||
};
|
||||
|
||||
export const getUserTrainingByUserId = async (userId) => {
|
||||
try {
|
||||
const userTraining = await Training.findOne({
|
||||
where: { user_id: userId }, // Filter by user ID
|
||||
order: [['createdAt', 'DESC']], // Order by createdAt in descending order
|
||||
});
|
||||
return userTraining;
|
||||
} catch (error) {
|
||||
console.error('Error Training:', error);
|
||||
throw new Error('Error Training');
|
||||
}
|
||||
};
|
156
server/services/users.js
Normal file
156
server/services/users.js
Normal file
|
@ -0,0 +1,156 @@
|
|||
import { Op } from "sequelize";
|
||||
import { User } from "../model/user.js";
|
||||
import { UserUploads } from "../model/userUpload.js";
|
||||
|
||||
|
||||
export const countUsers = async () => {
|
||||
try {
|
||||
const count = await User.count();
|
||||
return count;
|
||||
} catch (error) {
|
||||
throw new Error('Error counting users');
|
||||
}
|
||||
};
|
||||
|
||||
// Function to get a user by email
|
||||
export const getUserByEmail = async (email) => {
|
||||
try {
|
||||
const user = await User.findOne({ where: { email } });
|
||||
|
||||
return user; // Return the user object
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw new Error('Error while checking user by email');
|
||||
}
|
||||
};
|
||||
|
||||
export const getUserByFilter = async (payload) => {
|
||||
try {
|
||||
const user = await User.findOne({ where: payload });
|
||||
|
||||
return user; // Return the user object
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw new Error('Error while checking user by filter');
|
||||
}
|
||||
};
|
||||
|
||||
// Function to create a new user
|
||||
export const createUser = async (payload) => {
|
||||
try {
|
||||
const user = await User.create(payload);
|
||||
|
||||
return user; // Return the created user
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw new Error('Error while creating user');
|
||||
}
|
||||
};
|
||||
|
||||
// Function to get a user by ID (for example, to fetch profile data)
|
||||
export const getUserById = async (id) => {
|
||||
try {
|
||||
const user = await User.findByPk(id); // Find the user by primary key (ID)
|
||||
|
||||
return user; // Return the user data
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw new Error('Error while fetching user by ID');
|
||||
}
|
||||
};
|
||||
|
||||
export const countUserUploadsByUserId = async (userId) => {
|
||||
try {
|
||||
const count = await UserUploads.count({
|
||||
where: {
|
||||
user_id: userId
|
||||
}
|
||||
});
|
||||
console.log(`Total uploads for user ID ${userId}: ${count}`);
|
||||
return count;
|
||||
} catch (error) {
|
||||
console.error('Error counting user uploads:', error);
|
||||
throw new Error('Error counting user uploads');
|
||||
}
|
||||
};
|
||||
|
||||
// Update user profile
|
||||
export const updateUserProfile = async (id, updatedFields) => {
|
||||
try {
|
||||
const user = await User.findByPk(id);
|
||||
|
||||
// Dynamically update the fields based on the input
|
||||
await user.update(updatedFields); // Sequelize handles the query and update
|
||||
|
||||
return user; // Return the updated user
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw new Error('Error while updating user profile');
|
||||
}
|
||||
};
|
||||
|
||||
export const createUpload = async (payload) => {
|
||||
try {
|
||||
const upload = await UserUploads.create(payload);
|
||||
return upload;
|
||||
} catch (error) {
|
||||
console.error('Error uploading file:', error);
|
||||
throw new Error('Error uploading file');
|
||||
}
|
||||
};
|
||||
|
||||
export const getUserUpload = async (userId) => {
|
||||
try {
|
||||
// Find the user and include their uploads
|
||||
const user = await User.findOne({
|
||||
where: { id: userId }, // Filter by user ID
|
||||
include: [
|
||||
{
|
||||
model: UserUploads, // Include the user uploads
|
||||
as: 'uploads', // Alias defined in associations
|
||||
}
|
||||
]
|
||||
});
|
||||
return user;
|
||||
} catch (error) {
|
||||
console.error('Error uploading file:', error);
|
||||
throw new Error('Error uploading file');
|
||||
}
|
||||
};
|
||||
|
||||
export const getUploadVideoById = async (payload) => {
|
||||
try {
|
||||
const userData = await UserUploads.findOne({ where: payload });
|
||||
|
||||
return userData;
|
||||
} catch (error) {
|
||||
console.error('Error file:', error);
|
||||
throw new Error('Error file');
|
||||
}
|
||||
};
|
||||
|
||||
export const getUserUploadByDateFilter = async (userId, startDate, endDate) => {
|
||||
try {
|
||||
|
||||
// Find the user and include their uploads
|
||||
const user = await User.findOne({
|
||||
where: { id: userId }, // Filter by user ID
|
||||
include: [
|
||||
{
|
||||
model: UserUploads, // Include the user uploads
|
||||
as: 'uploads', // Alias defined in associations
|
||||
where: {
|
||||
upload_time: {
|
||||
[Op.between]: [startDate, endDate], // Filter uploads within the current week
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return user;
|
||||
} catch (error) {
|
||||
console.error('Error fetching user uploads:', error);
|
||||
throw new Error('Error fetching user uploads');
|
||||
}
|
||||
};
|
Loading…
Reference in a new issue