Compare commits

..

1 commit
main ... dev

Author SHA1 Message Date
mihir_dml 777de35c65 comment added 2025-02-11 11:23:59 +05:30
29 changed files with 3009 additions and 0 deletions

6
sequelize-cli.js Normal file
View 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
View 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
View 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);
}

View 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
View 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
}

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

File diff suppressed because it is too large Load diff

View 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
View 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';

View 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
}
};

View 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 };

View 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
View 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
View 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
View file

@ -0,0 +1 @@
export * from './db/index.js';

View 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);
}
};

View 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;

View 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');
}
}
};

View 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
View 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
View 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 };

View 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 };

View 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
View 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;

View 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
View 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;

View 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

View 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
View 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');
}
};