"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/backend/test/server.test.js:
--------------------------------------------------------------------------------
1 | const app = require("../app")
2 | const request = require("supertest");
3 | const config = require('config');
4 | const mongoose = require('mongoose');
5 |
6 | test('Test server is starts',async ()=> {
7 | console.log(config.get('mongodb.connectionString'))
8 | await request(app).get('/')
9 | .expect(200);
10 | mongoose.connection.close();
11 | })
--------------------------------------------------------------------------------
/backend/services/tool.js:
--------------------------------------------------------------------------------
1 | const bcrypt = require('bcrypt');
2 | const saltRounds = 10;
3 |
4 | var hashPassword = (pass) => {
5 | return (new Promise( (resolve, reject)=> {
6 | bcrypt.hash(pass, saltRounds)
7 | .then((hash)=>{
8 | resolve(hash);
9 | })
10 | .catch((err)=>{
11 | reject(err)
12 | })
13 | }))
14 | }
15 |
16 | module.exports = {hashPassword};
--------------------------------------------------------------------------------
/frontend/src/components/StudentRegister/StudentRegisterPage/studentRegister.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Registerform from "../RegisterForm/registerform";
3 |
4 | const StudentRegister = ()=> {
5 | return (
6 |
11 | );
12 | }
13 |
14 | export default StudentRegister;
--------------------------------------------------------------------------------
/frontend/src/services/Auth.js:
--------------------------------------------------------------------------------
1 | class Auth {
2 | constructor(){
3 | this.token = null;
4 | }
5 |
6 | retriveToken = ()=>{
7 | return localStorage.getItem('Token');
8 | }
9 |
10 | storeToken = (t)=>{
11 | localStorage.setItem('Token',t);
12 | }
13 |
14 | deleteToken = ()=>{
15 | localStorage.removeItem('Token');
16 | }
17 |
18 |
19 | }
20 | export default new Auth();;
21 |
--------------------------------------------------------------------------------
/backend/schemas/testRegistration.js:
--------------------------------------------------------------------------------
1 | var mongoose = require("mongoose");
2 |
3 | var testRegistrationSchema = new mongoose.Schema({
4 | user : {
5 | type : mongoose.Schema.Types.ObjectId,
6 | ref : 'userModel'
7 | },
8 | test : {
9 | type : mongoose.Schema.Types.ObjectId,
10 | ref : 'testModel'
11 | }
12 | },{
13 | timestamps:{}
14 | })
15 |
16 | module.exports = testRegistrationSchema
--------------------------------------------------------------------------------
/user-portal-frontend/src/helper/Auth.js:
--------------------------------------------------------------------------------
1 | class Auth {
2 | constructor(){
3 | this.token = null;
4 | }
5 |
6 | retriveToken = ()=>{
7 | return localStorage.getItem('Token');
8 | }
9 |
10 | storeToken = (t)=>{
11 | localStorage.setItem('Token',t);
12 | }
13 |
14 | deleteToken = ()=>{
15 | localStorage.removeItem('Token');
16 | }
17 |
18 |
19 | }
20 | export default new Auth();;
21 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/frontend/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/user-portal-frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/frontend/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/frontend/src/services/axiosCalls.js:
--------------------------------------------------------------------------------
1 | // import apis from "./Apis";
2 | // import axios from 'axios';
3 |
4 | // // export const Get = async (url, options) => {
5 | // // console.log('get api call')
6 | // // return (await axios.get(url, options));
7 | // // }
8 |
9 | // // export const Post =(p)=>{
10 | // // return axios({
11 | // // baseURL : apis.BASE,
12 | // // method:'post',
13 | // // ...p,
14 | // // })
15 | // // }
--------------------------------------------------------------------------------
/user-portal-frontend/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/user-portal-frontend/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/user-portal-frontend/src/redux/actions/alertAction.js:
--------------------------------------------------------------------------------
1 | import { ActionTypes } from "../action-types"
2 |
3 |
4 | export const setAlert = (details)=>{
5 | return async(dispatch) => {
6 | dispatch({
7 | type:ActionTypes.SET_ALERT,
8 | payload : details
9 | })
10 | }
11 | }
12 |
13 | export const clearAlert = () => {
14 | return async(dispatch) => {
15 | dispatch({
16 | type:ActionTypes.CLEAR_ALERT,
17 | payload:""
18 | })
19 | }
20 | }
--------------------------------------------------------------------------------
/backend/schemas/subject.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose')
2 |
3 | var subjectSchema = new mongoose.Schema({
4 | name : {
5 | type : String,
6 | required : true,
7 | unique : true
8 | },
9 | status : {
10 | type : Boolean,
11 | required : true,
12 | default : true
13 | },
14 | createdBy : {
15 | type : mongoose.Schema.Types.ObjectId,
16 | ref : 'adminModel'
17 | }
18 | },
19 | {
20 | timestamps : {}
21 | })
22 |
23 | module.exports = subjectSchema
--------------------------------------------------------------------------------
/frontend/src/components/StudentRegister/RegisterForm/registerform.css:
--------------------------------------------------------------------------------
1 | .register-box {
2 | position: absolute;
3 | top: 50%;
4 | left: 50%;
5 | width: 400px;
6 | padding: 40px;
7 | transform: translate(-50%, -50%);
8 | background: rgba(0, 0, 0, 0.5);
9 | box-sizing: border-box;
10 | box-shadow: 0 15px 25px rgba(0, 0, 0, 0.6);
11 | border-radius: 10px;
12 | color: white;
13 | }
14 |
15 | h2 {
16 | color: white;
17 | }
18 |
19 | input {
20 | background: rgba(0, 0, 0, 0.5);
21 | }
22 |
--------------------------------------------------------------------------------
/frontend/src/redux/reducers/studentDetails.js:
--------------------------------------------------------------------------------
1 | import { ActionTypes } from "../constants/action-types"
2 |
3 | const initialState = {
4 | list : [],
5 | retrived : false
6 | }
7 | export const getAllStudentReducer = (state=initialState, {type,payload})=> {
8 | switch(type) {
9 | case ActionTypes.GET_ALL_STUDENT:
10 | return {
11 | ...state,
12 | list : payload.studentlist,
13 | retrived : true
14 | };
15 | default:
16 | return state;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/frontend/src/redux/reducers/subjectDetails.js:
--------------------------------------------------------------------------------
1 | import { ActionTypes } from "../constants/action-types"
2 |
3 | const initialState = {
4 | list : [],
5 | retrived : false
6 | }
7 | export const getAllSubjectsReducer = (state=initialState, {type,payload})=> {
8 | switch(type) {
9 | case ActionTypes.GET_ALL_SUBJECT:
10 | return {
11 | ...state,
12 | list : payload.subjectlist,
13 | retrived : true
14 | };
15 | default:
16 | return state;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/frontend/src/redux/reducers/teacherDetails.js:
--------------------------------------------------------------------------------
1 | import { ActionTypes } from "../constants/action-types"
2 |
3 | const initialState = {
4 | list : [],
5 | retrived : false
6 | }
7 | export const getAllTeachersReducer = (state=initialState, {type,payload})=> {
8 | switch(type) {
9 | case ActionTypes.GET_ALL_TEACHER:
10 | return {
11 | ...state,
12 | list : payload.teacherlist,
13 | retrived : true
14 | };
15 | default:
16 | return state;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/frontend/src/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose} from 'redux';
2 | import thunk from 'redux-thunk';
3 | import logger from 'redux-logger';
4 | import rootReducer from './redux/reducers/index';
5 |
6 | const initialState={};
7 | const middleware=[thunk,logger];
8 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
9 | const store = createStore(
10 | rootReducer,
11 | initialState,
12 | composeEnhancers(applyMiddleware(...middleware))
13 | );
14 |
15 | export default store;
--------------------------------------------------------------------------------
/user-portal-frontend/src/redux/reducers/login.js:
--------------------------------------------------------------------------------
1 | import { ActionTypes } from "../action-types";
2 |
3 | const initialState = {
4 | isLoggedIn : false,
5 | userDetails : {}
6 | }
7 |
8 | export const loginUserReducer = (state = initialState,{type,payload})=>{
9 | switch(type) {
10 | case ActionTypes.LOGIN:
11 | return {...state,isLoggedIn:payload.isLoggedIn,userDetails:payload.userDetails};
12 | case ActionTypes.LOGOUT:
13 | return initialState;
14 | default :
15 | return state;
16 | }
17 | }
--------------------------------------------------------------------------------
/user-portal-frontend/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose} from 'redux';
2 | import thunk from 'redux-thunk';
3 | import logger from 'redux-logger';
4 | import rootReducer from './reducers/index';
5 |
6 | const initialState = {};
7 | const middleware = [thunk, logger];
8 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
9 | const store = createStore(
10 | rootReducer,
11 | initialState,
12 | composeEnhancers(applyMiddleware(...middleware))
13 | );
14 |
15 | export default store;
--------------------------------------------------------------------------------
/frontend/src/redux/reducers/login.js:
--------------------------------------------------------------------------------
1 | import { ActionTypes } from "../constants/action-types";
2 |
3 | const initialState = {
4 | isLoggedIn : false,
5 | userDetails : {}
6 | }
7 |
8 | export const loginUserReducer = (state = initialState,{type,payload})=>{
9 | switch(type) {
10 | case ActionTypes.LOGIN:
11 | return {...state,isLoggedIn:payload.isLoggedIn,userDetails:payload.userDetails};
12 | case ActionTypes.LOGOUT:
13 | return {...state,isLoggedIn:false,userDetails:{}};
14 | default :
15 | return state;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/user-portal-frontend/src/redux/reducers/subject.js:
--------------------------------------------------------------------------------
1 | import { ActionTypes } from "../action-types";
2 |
3 |
4 | const initialState = {
5 | list : [],
6 | retrived : false
7 | }
8 | export const getAllSubjectsReducer = (state=initialState, {type,payload})=> {
9 | switch(type) {
10 | case ActionTypes.GET_ALL_SUBJECT:
11 | return {
12 | ...state,
13 | list : payload.subjectlist,
14 | retrived : true
15 | };
16 | case ActionTypes.LOGOUT:
17 | return initialState;
18 | default:
19 | return state;
20 | }
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/frontend/src/components/basic/header/header.css:
--------------------------------------------------------------------------------
1 | .header-container-2 {
2 | position: absolute;
3 | width: 100%;
4 | }
5 | .logo {
6 | width: 80px;
7 | margin-left: 30px;
8 | margin-top: 30px;
9 | }
10 | .header-container .logo,
11 | .header-container .navigation {
12 | display: inline;
13 | }
14 | .navigation {
15 | list-style: none;
16 | position: relative;
17 | right: 50px;
18 | float: right;
19 | top: 50px;
20 | }
21 | .navigation li {
22 | display: inline;
23 | color: aliceblue;
24 | font-size: 17px;
25 | }
26 | .p {
27 | padding-right: 50px;
28 | }
29 |
--------------------------------------------------------------------------------
/user-portal-frontend/src/helper/common.js:
--------------------------------------------------------------------------------
1 |
2 | export const getDatePretty = (str) => {
3 | try {
4 | var date = new Date(Date.parse(str));
5 | return date.getDate()+"-"+(date.getMonth()+1)+"-"+ date.getFullYear() +" "+date.getHours() +"-"+date.getMinutes();
6 | } catch (err) {
7 | console.log(err);
8 | }
9 | }
10 |
11 | export const getTimePretty= (seconds) => {
12 | try{
13 | var h = Math.floor(seconds/3600);
14 | var m = Math.floor((seconds%3600)/60);
15 | // var s = (seconds%60);
16 | return h+":"+m;
17 | } catch (err) {
18 | console.log(err);
19 | }
20 | }
--------------------------------------------------------------------------------
/frontend/src/redux/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux";
2 | import { dashBoardCountReducer } from "./counts";
3 | import { loginUserReducer } from "./login";
4 | import { getAllStudentReducer } from "./studentDetails";
5 | import { getAllSubjectsReducer } from "./subjectDetails";
6 | import { getAllTeachersReducer } from "./teacherDetails";
7 |
8 | export default combineReducers({
9 | user : loginUserReducer,
10 | dashboardDetails : dashBoardCountReducer,
11 | teachers : getAllTeachersReducer,
12 | students : getAllStudentReducer,
13 | subjects : getAllSubjectsReducer
14 | });
--------------------------------------------------------------------------------
/user-portal-frontend/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/user-portal-frontend/src/redux/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from "redux";
2 | import { alertReducer } from "./alert";
3 | import { loginUserReducer } from "./login";
4 | import { getQuestionReducer } from "./question";
5 | import { getAllSubjectsReducer } from "./subject";
6 | import { TakeTestReducer } from "./taketest";
7 | import { TestReducer } from "./test";
8 |
9 | export default combineReducers({
10 | user : loginUserReducer,
11 | alertDetails : alertReducer,
12 | subjectDetails : getAllSubjectsReducer,
13 | questionDetails : getQuestionReducer,
14 | testDetails : TestReducer,
15 | takeTestDetails : TakeTestReducer
16 | });
--------------------------------------------------------------------------------
/frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 | import { Provider } from 'react-redux';
7 | import store from './store';
8 |
9 | ReactDOM.render(
10 | <>
11 |
12 |
13 |
14 | >,
15 | document.getElementById('root')
16 | );
17 |
18 | // If you want to start measuring performance in your app, pass a function
19 | // to log results (for example: reportWebVitals(console.log))
20 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
21 | reportWebVitals();
22 |
--------------------------------------------------------------------------------
/backend/schemas/user.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 |
3 | var userSchema = new mongoose.Schema({
4 | username : {
5 | type : String,
6 | required : true
7 | },
8 | email : {
9 | type : String,
10 | required : true,
11 | unique : true
12 | },
13 | usertype : {
14 | type : String,
15 | enum : ['TEACHER', 'STUDENT'],
16 | required : true
17 | },
18 | password : {
19 | type : String,
20 | required : true
21 | },
22 | status : {
23 | type : Boolean,
24 | default : true
25 | },
26 | createdBy : {
27 | type : mongoose.Schema.Types.ObjectId,
28 | ref : 'adminModel'
29 | }
30 |
31 | },
32 | {
33 | timestamps:{}
34 | })
35 |
36 | module.exports = userSchema
--------------------------------------------------------------------------------
/user-portal-frontend/src/redux/actions/subjectAction.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import apis from "../../helper/Apis"
3 | import Auth from '../../helper/Auth'
4 | import { ActionTypes } from "../action-types";
5 |
6 | export const getSubjectDetails = () => {
7 | return async(dispatch) => {
8 | axios.get(apis.BASE + apis.GET_ALL_SUBJECT, {
9 | headers : {
10 | 'Authorization':`Bearer ${Auth.retriveToken()}`
11 | }
12 | }).then(response => {
13 |
14 | if(response.data.success) {
15 | dispatch({
16 | type: ActionTypes.GET_ALL_SUBJECT,
17 | payload : {
18 | subjectlist : response.data.subjects
19 | }
20 | })
21 | }
22 | })
23 | }
24 | }
--------------------------------------------------------------------------------
/backend/schemas/answersheet.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose')
2 |
3 | var answersheetSchema = new mongoose.Schema({
4 | test : {
5 | type : mongoose.Schema.Types.ObjectId,
6 | ref : 'testModel',
7 | required : true
8 | },
9 | student : {
10 | type : mongoose.Schema.Types.ObjectId,
11 | ref : 'userModel',
12 | required : true
13 | },
14 | score : {
15 | type : Number,
16 | default : 0,
17 | required : true
18 | },
19 | answers : [{
20 | type : String
21 | }],
22 | startTime : {
23 | type : Date
24 | },
25 | completed : {
26 | type : Boolean,
27 | required : true,
28 | default : false
29 | }
30 | },{
31 | timestamps : {}
32 | })
33 |
34 | module.exports = answersheetSchema;
--------------------------------------------------------------------------------
/backend/services/student.js:
--------------------------------------------------------------------------------
1 | const userModel = require("../models/user")
2 |
3 |
4 |
5 | var getAllStudents = (req, res, next) => {
6 | userModel.find({usertype:"STUDENT"}, (err, users)=>{
7 | if(err) {
8 | res.status(500).json({
9 | success:false,
10 | message : 'Internal server error'
11 | })
12 | } else {
13 | var students = []
14 | users.forEach((student)=>{
15 | students.push({
16 | "id" : student._id,
17 | "name" : student.username,
18 | "status" : student.status
19 | })
20 | })
21 | res.json({
22 | success : true,
23 | students
24 | })
25 | }
26 | })
27 | }
28 |
29 | module.exports = {
30 | getAllStudents
31 | }
--------------------------------------------------------------------------------
/user-portal-frontend/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRoot } from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
5 | import { Provider } from 'react-redux';
6 | import reportWebVitals from './reportWebVitals';
7 | import store from './redux/store';
8 |
9 | const container = document.getElementById('root');
10 | const root = createRoot(container);
11 | root.render(
12 | <>
13 |
14 |
15 |
16 | >
17 | );
18 |
19 | // If you want to start measuring performance in your app, pass a function
20 | // to log results (for example: reportWebVitals(console.log))
21 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
22 | reportWebVitals();
23 |
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/atoms/LogoutButton/LogoutButton.js:
--------------------------------------------------------------------------------
1 | import { Button } from "@material-ui/core";
2 | import React from "react";
3 | import { connect } from "react-redux";
4 | import { logoutUser } from "../../../redux/actions/loginAction";
5 |
6 | class LogoutButton extends React.Component {
7 |
8 | constructor(props) {
9 | super(props);
10 | this.state = {}
11 | }
12 |
13 | render(){
14 | return(
15 | (this.props.logoutUser())}
19 | >
20 | Logout
21 | )
22 | }
23 | }
24 |
25 | const mapStatetoProps = state =>({
26 | user:state.user
27 | })
28 |
29 | export default connect(mapStatetoProps,{
30 | logoutUser
31 | })(LogoutButton)
--------------------------------------------------------------------------------
/user-portal-frontend/src/redux/reducers/alert.js:
--------------------------------------------------------------------------------
1 | import { ActionTypes } from "../action-types";
2 |
3 | const initState = {
4 | isAlert:false,
5 | type:"",
6 | title:"",
7 | message : ""
8 | }
9 |
10 | export const alertReducer = (state = initState,{type,payload})=>{
11 | switch(type) {
12 | case ActionTypes.SET_ALERT:
13 | return {...state,isAlert:true,type:payload.type,title:payload.title,message:payload.message};
14 | case ActionTypes.CLEAR_ALERT:
15 | return {...state,isAlert:false,type:"",title:"",message:""};
16 | case ActionTypes.LOGOUT:
17 | return initState;
18 | case ActionTypes.END_TEST:
19 | return {...state,isAlert:true,type:"info",title:"Test Ended",message:""}
20 | default :
21 | return state;
22 | }
23 | }
--------------------------------------------------------------------------------
/frontend/src/App.css:
--------------------------------------------------------------------------------
1 | @import "~antd/dist/antd.css";
2 | .App {
3 | text-align: center;
4 | }
5 |
6 | .App-logo {
7 | height: 40vmin;
8 | pointer-events: none;
9 | }
10 |
11 | @media (prefers-reduced-motion: no-preference) {
12 | .App-logo {
13 | animation: App-logo-spin infinite 20s linear;
14 | }
15 | }
16 |
17 | .App-header {
18 | background-color: #282c34;
19 | min-height: 100vh;
20 | display: flex;
21 | flex-direction: column;
22 | align-items: center;
23 | justify-content: center;
24 | font-size: calc(10px + 2vmin);
25 | color: white;
26 | }
27 |
28 | .App-link {
29 | color: #61dafb;
30 | }
31 |
32 | @keyframes App-logo-spin {
33 | from {
34 | transform: rotate(0deg);
35 | }
36 | to {
37 | transform: rotate(360deg);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/frontend/src/components/basic/Homepage/Homepage.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import './homepage.css';
3 | import './homepage.jpeg';
4 | import { Navigate } from 'react-router-dom';
5 | import { HomepageHeader } from "../header/header";
6 | import Login from "../login/login";
7 | import Auth from '../../../services/Auth';
8 | import logoImg from './main.jpg';
9 |
10 | function Homepage(){
11 | console.log("on home page");
12 | if(Auth.retriveToken() && Auth.retriveToken()!=='undefined'){
13 | return ( );
14 | }
15 | else {
16 | return (
17 |
23 | );
24 | }
25 | }
26 |
27 | export default Homepage;
28 |
--------------------------------------------------------------------------------
/backend/services/connection.js:
--------------------------------------------------------------------------------
1 | var mongoose = require("mongoose");
2 | var config = require('config');
3 | const adminService = require("../services/admin");
4 |
5 |
6 | //database connection
7 | mongoose.Promise = global.Promise;
8 | let options = {
9 | autoIndex: false,
10 | reconnectTries: 100,
11 | reconnectInterval: 500,
12 | poolSize: 10,
13 | bufferMaxEntries: 0,
14 | useNewUrlParser: true,
15 | useFindAndModify : false
16 | };
17 |
18 | if(process.env.NODE_ENV==="docker") {
19 | options.authSource = config.get('mongodb.authDB')
20 | }
21 |
22 | mongoose.connect(config.get('mongodb.connectionString'),options).then(()=>{
23 | console.log("connected to mongoDB");
24 | adminService.addAdminIfNotFound();
25 |
26 | }).catch((err)=>{
27 | console.log("Error connecting to database",err);
28 | })
29 |
30 |
31 | module.exports=mongoose;
--------------------------------------------------------------------------------
/backend/schemas/question.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose')
2 |
3 | var questionSchema = new mongoose.Schema({
4 | body : {
5 | type : String,
6 | required : true
7 | },
8 | explanation : {
9 | type : String
10 | },
11 | options : [ {
12 | type : String,
13 | required : true
14 | }],
15 | subject : {
16 | type : mongoose.Schema.Types.ObjectId,
17 | ref : 'subjectModel',
18 | required : true
19 | },
20 | answer : {
21 | type : String,
22 | required : true
23 | },
24 | marks : {
25 | type : Number,
26 | requried : true
27 | },
28 | status : {
29 | type : Boolean,
30 | required : true,
31 | default : true
32 | },
33 | createdBy : {
34 | type : mongoose.Schema.Types.ObjectId,
35 | ref : 'userModel'
36 | }
37 | },
38 | {
39 | timestamps: {}
40 | })
41 |
42 | module.exports = questionSchema;
--------------------------------------------------------------------------------
/frontend/src/services/alert.js:
--------------------------------------------------------------------------------
1 | import { Modal } from 'antd';
2 |
3 | export default function Alert(s='warning',h,b) {
4 | if(s==='success'){
5 | return (
6 | Modal.success({
7 | title: h,
8 | content:({b}
),
9 | })
10 | )
11 | }
12 | else if(s==='error'){
13 | return (
14 | Modal.error({
15 | title: h,
16 | content:({b}
),
17 | })
18 | )
19 | }
20 | else if(s==='warning'){
21 | return (
22 | Modal.warning({
23 | title: h,
24 | content:({b}
),
25 | })
26 | )
27 | } else {
28 | return (
29 | Modal.info({
30 | title: h,
31 | content:({b}
)
32 | })
33 | )
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/frontend/src/redux/reducers/counts.js:
--------------------------------------------------------------------------------
1 | import { ActionTypes } from "../constants/action-types"
2 |
3 | const initialState = {
4 | subjectActive: 0,
5 | subjectBlocked: 0,
6 | studentActive : 0,
7 | studentBlocked : 0,
8 | teacherActive : 0,
9 | teacherBlocked : 0,
10 | retrived : false
11 | }
12 |
13 | export const dashBoardCountReducer = (state = initialState, {type,payload})=>{
14 | switch(type) {
15 | case ActionTypes.DASHBOARD_COUNT:
16 | return {
17 | ...state,
18 | subjectActive : payload.activeSubject,
19 | subjectBlocked:payload.blockedSubject,
20 | studentActive : payload.activeStudent,
21 | studentBlocked : payload.blockedStudent,
22 | teacherActive : payload.activeTeacher,
23 | teacherBlocked : payload.blockedTeacher,
24 | retrived:payload.retrived
25 | };
26 | default:
27 | return state;
28 | }
29 | }
--------------------------------------------------------------------------------
/user-portal-frontend/src/redux/reducers/taketest.js:
--------------------------------------------------------------------------------
1 | import { ActionTypes } from "../action-types";
2 |
3 |
4 | const initialState = {
5 | isRetrived: false,
6 | test: {},
7 | answersheet : {},
8 | questionid : []
9 | }
10 |
11 | export const TakeTestReducer = (state = initialState,{type, payload}) => {
12 | switch(type) {
13 | case ActionTypes.START_TEST:
14 | return {
15 | ...state,
16 | isRetrived : true,
17 | test : payload.test,
18 | answersheet : payload.answersheet,
19 | questionid : payload.questions
20 | }
21 | case ActionTypes.SELECT_ANSWER:
22 | var newans = state.answersheet;
23 | newans.answers[payload.index] = payload.ans;
24 | return {
25 | ...state,
26 | answersheet : newans
27 | }
28 | case ActionTypes.END_TEST:
29 | return initialState;
30 | default:
31 | return state;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/user-portal-frontend/src/redux/actions/registerStudentAction.js:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 | import apis from "../../helper/Apis"
3 | import { ActionTypes } from "../action-types";
4 |
5 | export const registerStudentAction = (details) => {
6 | return async(dispatch) => {
7 | const response = await axios.post(apis.BASE + apis.STUDENT_REGISTER,details);
8 | if(response.data.success) {
9 | dispatch({
10 | type:ActionTypes.SET_ALERT,
11 | payload : {
12 | isAlert : true,
13 | title : "Success",
14 | type : "success",
15 | message : response.data.message
16 | }
17 | })
18 | }
19 | else {
20 | dispatch({
21 | type:ActionTypes.SET_ALERT,
22 | payload : {
23 | isAlert : true,
24 | title : "Error",
25 | type : "error",
26 | message : response.data.message
27 | }
28 | })
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/frontend/src/App.js:
--------------------------------------------------------------------------------
1 | import './App.css';
2 | import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
3 | import Homepage from './components/basic/Homepage/Homepage';
4 | import DashboradMain from './components/Dashboard/Main/dashboradMain';
5 | import StudentRegister from './components/StudentRegister/StudentRegisterPage/studentRegister';
6 | import AddTeacher from './components/Dashboard/AddTeacher/AddTeacher';
7 | import AddSubject from './components/Dashboard/AddSubject/AddSubject';
8 | function App() {
9 | return (
10 |
11 |
12 | }/>
13 | } />
14 | } />
15 | }/>
16 | }/>
17 |
18 |
19 | );
20 | }
21 |
22 | export default App;
23 |
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/atoms/Alertbox/AlertBox.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Alert from '@material-ui/lab/Alert';
3 | import AlertTitle from '@material-ui/lab/AlertTitle';
4 | import { connect } from 'react-redux';
5 | import { clearAlert } from '../../../redux/actions/alertAction';
6 |
7 |
8 | class AlertBox extends React.Component {
9 | constructor(props) {
10 | super(props);
11 | this.state = {}
12 | }
13 |
14 | render() {
15 | if(this.props.alertDetails.isAlert) {
16 | return(
17 |
18 | {this.props.alertDetails.title}
19 | {this.props.alertDetails.message}
20 | )
21 | }
22 | else {
23 |
24 | return(
)
25 | }
26 | }
27 | }
28 |
29 | const mapStatetoProps = state => ({
30 | alertDetails : state.alertDetails
31 | })
32 |
33 | export default connect(mapStatetoProps,{
34 | clearAlert
35 | })(AlertBox);
--------------------------------------------------------------------------------
/user-portal-frontend/src/App.js:
--------------------------------------------------------------------------------
1 | import './App.css';
2 | import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
3 | import LoginPage from './components/pages/loginPage/loginPage';
4 | import StudentHomepage from './components/pages/studentHomepage/studentHomepage';
5 | import TeacherHomepage from './components/pages/teacherHomepage/teacherHomepage';
6 | import StudentRegisterPage from './components/pages/studentRegisterPage/studentRegisterPage';
7 | import TestPage from './components/pages/TakeTest/TestPage';
8 |
9 | function App() {
10 | return (
11 |
12 |
13 | }/>
14 | }/>
15 | }/>
16 | }/>
17 | }/>
18 |
19 |
20 | );
21 | }
22 |
23 | export default App;
24 |
--------------------------------------------------------------------------------
/frontend/src/components/basic/header/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import AppBar from '@material-ui/core/AppBar';
3 | import Toolbar from '@material-ui/core/Toolbar';
4 | import { createTheme, makeStyles, ThemeProvider } from '@material-ui/core/styles';
5 | import './header.css';
6 |
7 | const useStyles = makeStyles({
8 | logoimg : {
9 | margin:0,
10 | padding:0,
11 | height:50
12 | },
13 | name :{
14 | margin : 20,
15 | color:'#07cfda'
16 | }
17 | })
18 |
19 | const theme = createTheme({
20 | palette:{
21 | primary:{
22 | main:'#00000077'
23 | }
24 | }
25 | })
26 |
27 | export const HomepageHeader = (props)=>{
28 | const classes = useStyles();
29 | return (
30 |
31 |
32 |
33 |
34 |
35 | {props.title}
36 |
37 |
38 |
39 |
40 |
41 | );
42 | }
--------------------------------------------------------------------------------
/frontend/src/redux/actions/dashboardDetails.js:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 | import apis from "../../services/Apis"
3 | import { ActionTypes } from "../constants/action-types"
4 | import Auth from "../../services/Auth"
5 |
6 | export const getDashboardCount = () => {
7 | return async(dispatch) => {
8 | axios.get(apis.BASE + apis.GET_DASHBOARD_COUNT, {
9 | headers : {
10 | 'Authorization':`Bearer ${Auth.retriveToken()}`
11 | }
12 | }).then(response => {
13 | if(response.data.success) {
14 | dispatch({
15 | type: ActionTypes.DASHBOARD_COUNT,
16 | payload : {
17 | activeStudent: response.data.activeStudent,
18 | activeSubject: response.data.activeSubject,
19 | activeTeacher: response.data.activeTeacher,
20 | blockedStudent: response.data.blockedStudent,
21 | blockedSubject: response.data.blockedSubject,
22 | blockedTeacher: response.data.blockedTeacher,
23 | retrived : true
24 | }
25 | })
26 | }
27 | })
28 | }
29 | }
--------------------------------------------------------------------------------
/frontend/src/services/Apis.js:
--------------------------------------------------------------------------------
1 | const environment = process.env.NODE_ENV;
2 |
3 | let base_local_url = 'http://localhost:3000';
4 | let base_backend_url = 'http://localhost:5000';
5 | if(environment==='docker') {
6 | base_local_url = 'http://user-frontend-app:3000';
7 | base_backend_url = 'http://backend:5000';
8 | }
9 |
10 | const apis = {
11 | BASE_LOCAL_URL:base_local_url,
12 | BASE : base_backend_url,
13 | LOGIN : "/api/v1/adminlogin/",
14 | REGISTER_USER : "/api/v1/public/register",
15 | GET_ADMIN_DETAILS : "/api/v1/admin/details",
16 | GET_DASHBOARD_COUNT : "/api/v1/admin/getDashboardCount",
17 | GET_TEACHER_DETAILS : "/api/v1/admin/getAllTeachers",
18 | REMOVE_USER : "/api/v1/admin/removeUser",
19 | UNBLOCK_USER : "/api/v1/admin/unblockUser",
20 | GET_STUDENT_DETAILS : "/api/v1/admin/getAllStudent",
21 | GET_SUBJECT_DETAILS : "/api/v1/admin/getAllSubjects",
22 | REMOVE_SUBJECT : "/api/v1/admin/removeSubject",
23 | UNBLOCK_SUBJECT : "/api/v1/admin/unblockSubject",
24 | ADD_TEACHER : "/api/v1/admin/register",
25 | ADD_SUBJECT : "/api/v1/admin/addSubject"
26 | }
27 |
28 | export default apis;
--------------------------------------------------------------------------------
/backend/routes/admin.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 |
4 | var adminService = require('../services/admin');
5 | var subjectService = require('../services/subject');
6 | var teacherService = require('../services/teacher');
7 | var studentService = require('../services/student');
8 |
9 | router.get('/details',adminService.adminDetails);
10 |
11 | router.post('/register',adminService.teacherRegister);
12 | router.post('/removeUser',adminService.userRemove);
13 | router.post('/unblockUser',adminService.unblockUser);
14 | router.post('/addSubject', adminService.addSubject);
15 | router.post('/removeSubject',adminService.subjectRemove);
16 | router.post('/unblockSubject',adminService.unblockSubject);
17 |
18 | router.get('/getDashboardCount',adminService.getDashboardCount);
19 | router.get('/getAllSubjects',subjectService.getAllSubject);
20 | router.get('/getSubjectCount',subjectService.getStatusCount);
21 | router.get('/getAllTeachers',teacherService.getAllTeacher);
22 | router.get('/getTeacherStatusCount',teacherService.getTeacherStatusCount);
23 | router.get('/getAllStudent',studentService.getAllStudents);
24 | module.exports = router;
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/templates/HeaderAppBar/HeaderAppBar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { withStyles } from "@material-ui/core/styles";
3 | import { connect } from "react-redux";
4 | import { AppBar, Toolbar, Typography } from "@material-ui/core";
5 |
6 |
7 | const useStyles = (theme)=> ({
8 |
9 | })
10 |
11 | class HeaderAppBar extends React.Component {
12 | constructor(props){
13 | super(props);
14 | this.state = {}
15 | }
16 |
17 | render() {
18 | return(
19 |
20 |
23 |
24 |
25 | {this.props.title}
26 |
27 |
28 | welcome, {this.props.user.userDetails.username} !!
29 |
30 |
31 |
32 |
33 |
34 | )
35 | }
36 | }
37 |
38 | const mapStatetoProps = state => ({
39 | user : state.user
40 | })
41 |
42 | export default withStyles(useStyles)(connect(mapStatetoProps,{})(HeaderAppBar));
--------------------------------------------------------------------------------
/user-portal-frontend/src/redux/action-types.js:
--------------------------------------------------------------------------------
1 | export const ActionTypes = {
2 | LOGIN : "LOGIN",
3 | LOGOUT : "LOGOUT",
4 | SET_ALERT : "SET_ALERT",
5 | CLEAR_ALERT : "CLEAR_ALERT",
6 | GET_USER_DETAILS : "GET_USER_DETAILS",
7 | STUDENT_REGISTER : "STUDENT_REGISTER",
8 | GET_ALL_SUBJECT : "GET_ALL_SUBJECTS",
9 | SEARCH_QUESTION : "SEARCH_QUESTION",
10 | CHANGE_QUESTION_STATUS : "CHANGE_QUESTION_STATUS",
11 | VIEW_QUESTION : 'VIEW_QUESTION',
12 | GET_BACK_TO_SEARCH : "GET_BACK_TO_SEARCH",
13 | UPDATE_QUESTION : "UPDATE_QUESTION",
14 | GET_ALL_TESTS : "GET_ALL_TESTS",
15 | GET_ALL_TESTS_STUDENT : "GET_ALL_TESTS_STUDENT",
16 | CHANGE_TEST_REGISTER : "CHANGE_TEST_REGISTER",
17 | GET_UPCOMING_TESTS_STUDENT : "GET_UPCOMING_TESTS_STUDENT",
18 | VIEW_TEST_DETAILS : "VIEW_TEST_DETAILS",
19 | START_TEST : "START_TEST",
20 | SELECT_ANSWER : "SELECT_ANSWER",
21 | END_TEST : "END_TEST",
22 | GET_ALL_COMPLETED_TEST_STUDENT:"GET_ALL_COMPLETED_TEST_STUDENT",
23 | GET_TEST_RESULT_STUDENT : "GET_TEST_RESULT_STUDENT",
24 | GET_RESULT_QUESTIONS_STUDENTS : "GET_RESULT_QUESTIONS_STUDENTS",
25 | GET_TEST_DETAILS_TEACHER : "GET_TEST_DETAILS_TEACHER",
26 | Go_BACK_ALL_TEST_TEACHER : "GO_BACK_ALL_TEST_TEACHER"
27 | };
--------------------------------------------------------------------------------
/frontend/src/components/Dashboard/Card/card.js:
--------------------------------------------------------------------------------
1 | import { makeStyles } from "@material-ui/core"
2 |
3 | const useStyles = makeStyles({
4 | card_main:{
5 | background:'white',
6 | display:'inline-block',
7 | padding:'20px 0px 10px 10px',
8 | margin:30,
9 | borderRadius:10
10 | },
11 | name:{
12 | marginBottom:20,
13 | marginLeft:10,
14 | fontSize:15,
15 | fontWeight:500
16 | },
17 | d:{
18 | display:'flex',
19 | color:'darkblue'
20 | },
21 | d1:{
22 | paddingTop:10,
23 | fontSize:40,
24 | fontWeight:700
25 | },
26 | d2:{
27 | paddingTop:30,
28 | fontSize:20,
29 | fontWeight:600
30 | },
31 | img:{
32 | marginLeft:30,
33 | width:120,
34 | height:100
35 | }
36 | })
37 |
38 | export const MainCard = (props)=>{
39 | const classes = useStyles();
40 | return(
41 |
42 |
43 | {props.title}
44 |
45 |
46 |
{props.value}
47 |
/{props.total}
48 |
49 |
50 |
51 | )
52 | }
--------------------------------------------------------------------------------
/user-portal-frontend/src/redux/reducers/question.js:
--------------------------------------------------------------------------------
1 | import { ActionTypes } from "../action-types";
2 |
3 |
4 | const initialState = {
5 | list : [],
6 | searched : false,
7 | question : {}
8 | }
9 |
10 |
11 | export const getQuestionReducer = (state=initialState, {type,payload})=> {
12 | switch(type) {
13 | case ActionTypes.SEARCH_QUESTION:
14 | return {
15 | ...state,
16 | list : payload.questionList,
17 | searched : true,
18 | answer : -1,
19 | question : {}
20 | };
21 | case ActionTypes.CHANGE_QUESTION_STATUS:
22 | var newlist = state.list.map((q)=>( q._id===payload.id ?
23 | {
24 | _id:payload.id,
25 | status:payload.status,
26 | body:q.body
27 | } :q ));
28 | return {
29 | ...state,
30 | list : newlist,
31 | searched : true
32 | }
33 | case ActionTypes.VIEW_QUESTION:
34 | return {
35 | ...state,
36 | question : payload,
37 | searched : false
38 | }
39 | case ActionTypes.GET_BACK_TO_SEARCH:
40 | return {
41 | ...state,
42 | searched : true
43 | }
44 | case ActionTypes.LOGOUT:
45 | return initialState;
46 | default:
47 | return state;
48 | }
49 | }
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "admin-portal-frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@material-ui/core": "^4.12.4",
7 | "@material-ui/icons": "^4.11.3",
8 | "@testing-library/jest-dom": "^5.16.2",
9 | "@testing-library/react": "^12.1.3",
10 | "@testing-library/user-event": "^13.5.0",
11 | "antd": "^4.18.9",
12 | "axios": "^0.26.0",
13 | "react": "^17.0.2",
14 | "react-dom": "^17.0.2",
15 | "react-redux": "^7.2.6",
16 | "react-router-dom": "^6.2.2",
17 | "react-scripts": "5.0.0",
18 | "redux": "^4.1.2",
19 | "redux-logger": "^3.0.6",
20 | "redux-thunk": "^2.4.1",
21 | "web-vitals": "^2.1.4"
22 | },
23 | "scripts": {
24 | "start": "react-scripts start",
25 | "build": "react-scripts build",
26 | "test": "react-scripts test",
27 | "eject": "react-scripts eject"
28 | },
29 | "eslintConfig": {
30 | "extends": [
31 | "react-app",
32 | "react-app/jest"
33 | ]
34 | },
35 | "browserslist": {
36 | "production": [
37 | ">0.2%",
38 | "not dead",
39 | "not op_mini all"
40 | ],
41 | "development": [
42 | "last 1 chrome version",
43 | "last 1 firefox version",
44 | "last 1 safari version"
45 | ]
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/frontend/src/redux/actions/studentDetails.js:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 | import apis from "../../services/Apis"
3 | import { ActionTypes } from "../constants/action-types"
4 | import Auth from "../../services/Auth"
5 |
6 |
7 | export const getStudentDetails = () => {
8 | return async(dispatch) => {
9 | axios.get(apis.BASE + apis.GET_STUDENT_DETAILS, {
10 | headers : {
11 | 'Authorization':`Bearer ${Auth.retriveToken()}`
12 | }
13 | }).then(response => {
14 |
15 | if(response.data.success) {
16 | dispatch({
17 | type: ActionTypes.GET_ALL_STUDENT,
18 | payload : {
19 | studentlist : response.data.students
20 | }
21 | })
22 | }
23 | })
24 | }
25 | }
26 |
27 | export const StudentToggleStatus = (status,id,callback) => {
28 | var apisuffix = status ? apis.REMOVE_USER : apis.UNBLOCK_USER;
29 | return async() => {
30 | await axios.post(apis.BASE + apisuffix,{
31 | _id : id
32 | },{
33 | headers:{
34 | 'Authorization':`Bearer ${Auth.retriveToken()}`
35 | }
36 | }).then(response => {
37 | if(response.data.success) {
38 | callback();
39 | } else {
40 | console.log(response.data);
41 | }
42 | })
43 | }
44 |
45 | }
--------------------------------------------------------------------------------
/frontend/src/redux/actions/teacherDetails.js:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 | import apis from "../../services/Apis"
3 | import { ActionTypes } from "../constants/action-types"
4 | import Auth from "../../services/Auth"
5 |
6 |
7 | export const getTeacherDetails = () => {
8 | return async(dispatch) => {
9 | axios.get(apis.BASE + apis.GET_TEACHER_DETAILS, {
10 | headers : {
11 | 'Authorization':`Bearer ${Auth.retriveToken()}`
12 | }
13 | }).then(response => {
14 |
15 | if(response.data.success) {
16 | dispatch({
17 | type: ActionTypes.GET_ALL_TEACHER,
18 | payload : {
19 | teacherlist : response.data.teachers
20 | }
21 | })
22 | }
23 | })
24 | }
25 | }
26 |
27 | export const TeacherToggleStatus = (status,id,callback) => {
28 | var apisuffix = status ? apis.REMOVE_USER : apis.UNBLOCK_USER;
29 | return async() => {
30 | await axios.post(apis.BASE + apisuffix,{
31 | _id : id
32 | },{
33 | headers:{
34 | 'Authorization':`Bearer ${Auth.retriveToken()}`
35 | }
36 | }).then(response => {
37 | if(response.data.success) {
38 | callback();
39 | } else {
40 | console.log(response.data);
41 | }
42 | })
43 | }
44 |
45 | }
--------------------------------------------------------------------------------
/frontend/src/redux/actions/subjectDetails.js:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 | import apis from "../../services/Apis"
3 | import { ActionTypes } from "../constants/action-types"
4 | import Auth from "../../services/Auth"
5 |
6 |
7 | export const getSubjectDetails = () => {
8 | return async(dispatch) => {
9 | axios.get(apis.BASE + apis.GET_SUBJECT_DETAILS, {
10 | headers : {
11 | 'Authorization':`Bearer ${Auth.retriveToken()}`
12 | }
13 | }).then(response => {
14 |
15 | if(response.data.success) {
16 | dispatch({
17 | type: ActionTypes.GET_ALL_SUBJECT,
18 | payload : {
19 | subjectlist : response.data.subjects
20 | }
21 | })
22 | }
23 | })
24 | }
25 | }
26 |
27 | export const SubjectToggleStatus = (status,id,callback) => {
28 | var apisuffix = status ? apis.REMOVE_SUBJECT : apis.UNBLOCK_SUBJECT;
29 | return async() => {
30 | await axios.post(apis.BASE + apisuffix,{
31 | _id : id
32 | },{
33 | headers:{
34 | 'Authorization':`Bearer ${Auth.retriveToken()}`
35 | }
36 | }).then(response => {
37 | if(response.data.success) {
38 | console.log(response.data);
39 | callback();
40 | } else {
41 | console.log(response.data);
42 | }
43 | })
44 | }
45 |
46 | }
--------------------------------------------------------------------------------
/backend/schemas/test.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose')
2 |
3 | var testSchema = new mongoose.Schema({
4 | title : {
5 | type : String,
6 | required : true
7 | },
8 | subjects : [{
9 | type : mongoose.Schema.Types.ObjectId,
10 | ref : 'subjectModel'
11 | }],
12 | questions : [{
13 | type : mongoose.Schema.Types.ObjectId,
14 | ref : 'questionModel'
15 | }],
16 | maxmarks : {
17 | type : Number,
18 | required : true
19 | },
20 | queTypes : [{
21 | type : Number
22 | }],
23 | startTime : {
24 | type : Date
25 | },
26 | endTime : {
27 | type : Date,
28 | required : true
29 | },
30 | duration : {
31 | type : Number,
32 | required : true
33 | },
34 | regStartTime : {
35 | type : Date,
36 | required : true
37 | },
38 | regEndTime : {
39 | type : Date,
40 | required : true
41 | },
42 | resultTime : {
43 | type : Date,
44 | required : true
45 | },
46 | status : {
47 | type : String,
48 | enum : ['CREATED','REGISTRATION_STARTED','REGISTRATION_COMPLETE','TEST_STARTED','TEST_COMPLETE','RESULT_DECLARED','CANCELLED'],
49 | default : 'CREATED'
50 | },
51 | createdBy : {
52 | type : mongoose.Schema.Types.ObjectId,
53 | ref : 'userModel',
54 | required : true
55 | }
56 | },
57 | {
58 | timestamps : {}
59 | })
60 |
61 | module.exports = testSchema
--------------------------------------------------------------------------------
/user-portal-frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "user-portal-frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@material-ui/core": "^4.12.4",
7 | "@material-ui/icons": "^4.11.3",
8 | "@material-ui/lab": "^4.0.0-alpha.61",
9 | "@material-ui/pickers": "^3.3.10",
10 | "@testing-library/jest-dom": "^5.16.5",
11 | "@testing-library/react": "^13.3.0",
12 | "@testing-library/user-event": "^13.5.0",
13 | "axios": "^0.27.2",
14 | "react": "^18.2.0",
15 | "react-dom": "^18.2.0",
16 | "react-redux": "^8.0.2",
17 | "react-router-dom": "^6.3.0",
18 | "react-scripts": "5.0.1",
19 | "redux": "^4.2.0",
20 | "redux-logger": "^3.0.6",
21 | "redux-thunk": "^2.4.1",
22 | "web-vitals": "^2.1.4"
23 | },
24 | "scripts": {
25 | "start": "react-scripts start",
26 | "build": "react-scripts build",
27 | "test": "react-scripts test",
28 | "eject": "react-scripts eject"
29 | },
30 | "eslintConfig": {
31 | "extends": [
32 | "react-app",
33 | "react-app/jest"
34 | ]
35 | },
36 | "browserslist": {
37 | "production": [
38 | ">0.2%",
39 | "not dead",
40 | "not op_mini all"
41 | ],
42 | "development": [
43 | "last 1 chrome version",
44 | "last 1 firefox version",
45 | "last 1 safari version"
46 | ]
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/templates/TestDetails/TestDetailsStudent.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { withStyles } from "@material-ui/core/styles";
3 | import { connect } from "react-redux";
4 | import { getAllTestStudentAction } from "../../../redux/actions/studentTestAction";
5 | import TestTableStudent from "../../molecues/TestTable/TestTableStudent";
6 |
7 | const useStyles = (theme)=> ({
8 | testDetails : {
9 | margin:'20px',
10 | display: 'inline-block',
11 | textAlign : 'center',
12 | },
13 | testTitle : {
14 | fontSize : '1.7em',
15 | textAlign : 'center',
16 | margin : '20px'
17 | }
18 | })
19 |
20 | class TestDetailsStudent extends React.Component {
21 | constructor(props){
22 | super(props);
23 | this.state = {}
24 | }
25 |
26 | render() {
27 | if(this.props.testDetails.retrived) {
28 | return()
32 | }
33 | else {
34 | this.props.getAllTestStudentAction();
35 | return (
)
36 | }
37 | }
38 | }
39 |
40 | const mapStatetoProps = state => ({
41 | user : state.user,
42 | testDetails : state.testDetails
43 | })
44 |
45 | export default withStyles(useStyles)(connect(mapStatetoProps,{
46 | getAllTestStudentAction
47 | })(TestDetailsStudent));
--------------------------------------------------------------------------------
/backend/services/adminLogin.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken')
2 |
3 | var config = require('config')
4 |
5 | var passport = require('./passportconf')
6 |
7 | var adminLogin = (req, res, next) => {
8 |
9 | req.check('username', 'Invalid username').notEmpty()
10 | req.check('password', 'Invalid password').isLength({min:4, max:20});
11 |
12 | var errors = req.validationErrors()
13 |
14 | if(errors) {
15 | res.json({
16 | success : false,
17 | message : 'Invalid inputs',
18 | errors : errors
19 | })
20 | }
21 | else {
22 | passport.authenticate('admin-login', {session : false}, (err, admin, info)=> {
23 | if(err || !admin) {
24 | res.json(info);
25 | }
26 | else {
27 | req.login({_id:admin._id}, {session : false}, (err)=>{
28 | if(err) {
29 | res.json({
30 | success : false,
31 | message : 'server error'
32 | })
33 | }
34 |
35 | var token = jwt.sign({_id: admin._id}, config.get('jwt.secret'), {expiresIn: '1d'});
36 | res.json({
37 | success : true,
38 | message : 'login successful',
39 | admin : {
40 | username : admin.username,
41 | _id : admin._id
42 | },
43 | token : token
44 | })
45 | })
46 | }
47 | })(req, res, next);
48 | }
49 | }
50 |
51 | module.exports = { adminLogin };
--------------------------------------------------------------------------------
/backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Chintan Golakiya",
3 | "bugs": {
4 | "url": "https://github.com//backend/issues"
5 | },
6 | "dependencies": {
7 | "app-root-path": "3.1.0",
8 | "bcrypt": "5.1.0",
9 | "bcryptjs": "2.4.3",
10 | "body-parser": "1.20.2",
11 | "config": "3.3.9",
12 | "cors": "2.8.5",
13 | "exceljs": "4.3.0",
14 | "express": "^4.17.1",
15 | "express-fileupload": "^1.1.5",
16 | "express-session": "^1.17.3",
17 | "express-validator": "^5.3.1",
18 | "helmet": "6.1.5",
19 | "http-errors": "2.0.0",
20 | "mongoose": "5.6.0",
21 | "morgan": "1.10.0",
22 | "multer": "1.4.5-lts.1",
23 | "nodemailer": "6.9.1",
24 | "passport": "0.6.0",
25 | "passport-jwt": "4.0.1",
26 | "passport-local": "1.0.0",
27 | "xlsx": "0.18.5"
28 | },
29 | "description": "backend logic of the project using Mongo DB, Node js",
30 | "devDependencies": {
31 | "jest": "29.5.0",
32 | "supertest": "6.3.3"
33 | },
34 | "homepage": "https://github.com//backend#readme",
35 | "jest": {
36 | "testPathIgnorePatterns": [
37 | "models",
38 | "routes",
39 | "schemas",
40 | "services"
41 | ]
42 | },
43 | "license": "MIT",
44 | "main": "index.js",
45 | "name": "examportalbackend",
46 | "repository": {
47 | "type": "git",
48 | "url": "git+https://github.com//backend.git"
49 | },
50 | "scripts": {
51 | "start": "nodemon",
52 | "test": "jest"
53 | },
54 | "version": "1.0.0"
55 | }
56 |
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/molecues/TestView/QuestionList.js:
--------------------------------------------------------------------------------
1 |
2 | import { withStyles } from "@material-ui/styles";
3 | import React from "react";
4 | import CheckCircleIcon from '@material-ui/icons/CheckCircle';
5 | import RadioButtonUncheckedIcon from '@material-ui/icons/RadioButtonUnchecked';
6 |
7 | const useStyles = (theme) => ({
8 | main : {
9 | padding : "20px",
10 | border : "1px solid",
11 | borderRadius : "20px"
12 | },
13 | li : {
14 | padding : "5px",
15 | height: "30px",
16 | display : "inline-block",
17 | border : "1px solid",
18 | width : "60px",
19 | textAlign : "left"
20 | }
21 | })
22 |
23 | class QuestionList extends React.Component {
24 | constructor(props) {
25 | super(props);
26 | this.state = {
27 | answers:this.props.answers
28 | }
29 | }
30 |
31 | render() {
32 | if(this.state.answers !== undefined){
33 | if(this.state.answers.length > 0 ) {
34 | return (
35 | {this.state.answers.map((q,index) => (
36 |
(this.props.callback(index,this.props.obj))} className={this.props.classes.li}>
37 | {q!==null ? : }
38 | {index+1}
39 |
40 | ))}
41 |
)
42 | }
43 | } else {
44 | return (No questions in test
);
45 | }
46 | }
47 | }
48 |
49 | export default withStyles(useStyles)(QuestionList);
50 |
51 |
--------------------------------------------------------------------------------
/backend/services/teacher.js:
--------------------------------------------------------------------------------
1 | const userModel = require("../models/user")
2 |
3 |
4 |
5 | var getAllTeacher = (req, res, next) => {
6 | userModel.find({usertype:"TEACHER"}, (err, users)=>{
7 | if(err) {
8 | res.status(500).json({
9 | success:false,
10 | message : 'Internal server error'
11 | })
12 | } else {
13 | var teachers = []
14 | users.forEach((teacher)=>{
15 | teachers.push({
16 | "id" : teacher._id,
17 | "name" : teacher.username,
18 | "status" : teacher.status
19 | })
20 | })
21 | res.json({
22 | success : true,
23 | teachers
24 | })
25 | }
26 | })
27 | }
28 |
29 | var getTeacherStatusCount = (req,res,next) => {
30 | userModel.aggregate(
31 | [
32 | {$match:{usertype:"TEACHER"}},
33 | {$group: {_id:"$status",count:{$sum:1}} }
34 | ]
35 | )
36 | .then((result)=>{
37 | var trueCount = 0
38 | var falseCount = 0
39 | result.forEach((x)=>{
40 | if(x._id == true) {
41 | trueCount = x.count
42 | }
43 | if(x._id == false) {
44 | falseCount = x.count
45 | }
46 | })
47 | res.json({
48 | success:true,
49 | active : trueCount,
50 | blocked : falseCount
51 | })
52 | })
53 | .catch((err)=>{
54 | console.log(err)
55 | res.status(500).json({
56 | success:false,
57 | message:'Internal server error'
58 | })
59 | })
60 | }
61 |
62 | module.exports = {
63 | getTeacherStatusCount,
64 | getAllTeacher
65 | }
--------------------------------------------------------------------------------
/user-portal-frontend/src/helper/Apis.js:
--------------------------------------------------------------------------------
1 | const environment = process.env.NODE_ENV;
2 | let base_local_url = 'http://localhost:3001';
3 | let base_backend_url = 'http://localhost:5000';
4 | if(environment==='docker') {
5 | base_local_url = 'http://user-frontend-app:3001';
6 | base_backend_url = 'http://backend:5000';
7 | }
8 |
9 | const apis = {
10 | BASE_LOCAL_URL:base_local_url,
11 | BASE : base_backend_url,
12 | LOGIN : "/api/v1/login",
13 | GET_USER_DETAILS: "/api/v1/user/details",
14 | STUDENT_REGISTER: "/api/v1/public/register",
15 | GET_ALL_SUBJECT: "/api/v1/user/getAllSubjects",
16 | ADD_QUESTION : '/api/v1/user/addQuestion',
17 | SEARCH_QUESTION : '/api/v1/user/searchQuestion',
18 | CHANGE_QUESTION_STATUS : '/api/v1/user/changeQuestionStatus',
19 | GET_QUESTION_BY_ID:'/api/v1/user/getQuestionAnswer',
20 | GET_ANSWER : '/api/v1/user/getAnswer',
21 | UPDATE_QUESTION : '/api/v1/user/updateQuestion',
22 | CREATE_QUESTION : '/api/v1/user/createTest',
23 | GET_ALL_TEST : '/api/v1/user/getAllTest',
24 | GET_ALL_TEST_STUDNET:'/api/v1/user/getAllTestStudent',
25 | STUDENT_TEST_REGISTER : '/api/v1/user/testRegistration',
26 | GET_UPCOMING_TESTS_STUDENT : '/api/v1/user/getUpcomingTests',
27 | GET_TEST_DETAILS_BY_ID : '/api/v1/user/getTestById',
28 | START_TEST : '/api/v1/user/startTest',
29 | GET_QUESTIONS_TAKETEST : '/api/v1/user/getQuenStarttime',
30 | SAVE_ANSWER : '/api/v1/user/saveAnswer',
31 | END_TEST : '/api/v1/user/endTest',
32 | GET_ALL_COMPLETED_TEST_STUDENT : '/api/v1/user/getAllCompletedTest',
33 | GET_TEST_RESULT_STUDENT:'/api/v1/user/getResultMainDetailsByTestId',
34 | GET_RESULT_QUESTIONS_STUDENTS : '/api/v1/user/getQuestionAnswerByIds'
35 | }
36 |
37 | export default apis;
--------------------------------------------------------------------------------
/backend/test/public.test.js:
--------------------------------------------------------------------------------
1 | const app = require("../app")
2 | const request = require("supertest");
3 | const mongoose = require('mongoose');
4 | const userModel = require('../models/user');
5 |
6 | test('Student Register',async () => {
7 | await userModel.findOneAndRemove({'email':'testemail@ep.com'});
8 | await request(app).post('/api/v1/public/register').send({
9 | 'username':'testuser',
10 | 'email':'testemail@ep.com',
11 | 'password':'randomvalid'
12 | }).expect(200)
13 | .then(res=>(
14 | expect(res.body).toEqual({
15 | success : true,
16 | message : 'Profile created successfully!'
17 | })))
18 |
19 | });
20 |
21 | test('Already added email in Studnet Register', async ()=> {
22 | await request(app).post('/api/v1/public/register').send({
23 | 'username':'testuser',
24 | 'email':'testemail@ep.com',
25 | 'password':'randomvalid'
26 | }).expect(400)
27 | .then(res=>(
28 | expect(res.body).toEqual({
29 | success : false,
30 | message : 'This email is already exists!'
31 | })))
32 | mongoose.connection.close();
33 | })
34 |
35 | test('small password in student register', async ()=> {
36 | await request(app).post('/api/v1/public/register').send({
37 | 'username':'testuser',
38 | 'email':'testemail@ep.com',
39 | 'password':'ran'
40 | }).expect(400)
41 | .then(res=>{
42 |
43 | expect(res.body).toEqual(expect.objectContaining({
44 | success : false,
45 | message : 'Invalid inputs'
46 | }))
47 | expect(res.body.errors).toHaveLength(1);
48 | expect(res.body.errors[0]).toEqual(expect.objectContaining({
49 | param : 'password',
50 | msg : 'Invalid Password'
51 | }))
52 | })
53 | mongoose.connection.close();
54 | })
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/templates/TestDetails/TestDetails.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { withStyles } from "@material-ui/core/styles";
3 | import { connect } from "react-redux";
4 | import { getAllTestAction } from "../../../redux/actions/teacherTestAction";
5 | import TestTable from "../../molecues/TestTable/TestTable";
6 | import { Button } from "@material-ui/core";
7 | import { goBackToAllTest } from "../../../redux/actions/teacherTestAction";
8 | import ViewTest from "../CreateTestForm/ViewTest";
9 |
10 | const useStyles = (theme)=> ({
11 | testDetails : {
12 | margin:'20px',
13 | display: 'inline-block',
14 | textAlign : 'center',
15 | },
16 | testTitle : {
17 | fontSize : '1.7em',
18 | textAlign : 'center',
19 | margin : '20px'
20 | }
21 | })
22 |
23 | class TestDetails extends React.Component {
24 | constructor(props){
25 | super(props);
26 | this.state = {
27 | }
28 | }
29 |
30 | render() {
31 | if(this.props.testDetails.searched === true) {
32 | return(
33 |
34 | (this.props.goBackToAllTest())}>Back
35 |
)
36 | }
37 | if(this.props.testDetails.retrived === false) {
38 | this.props.getAllTestAction();
39 | return (
)
40 | } else {
41 | return()
45 | }
46 | }
47 | }
48 |
49 | const mapStatetoProps = state => ({
50 | user : state.user,
51 | testDetails : state.testDetails
52 | })
53 |
54 | export default withStyles(useStyles)(connect(mapStatetoProps,{
55 | getAllTestAction,
56 | goBackToAllTest
57 | })(TestDetails));
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/templates/TestDetails/UpcomingStudentTestsDetails.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { withStyles } from "@material-ui/core/styles";
3 | import { connect } from "react-redux";
4 | import { getUpcomingTestsStudentAction } from "../../../redux/actions/studentTestAction";
5 | import UpcomingTestTableStudent from "../../molecues/TestTable/UpcomingTestTableStudent";
6 | import TakeTestStudent from "../../molecues/TestView/TakeTestStudent";
7 |
8 | const useStyles = (theme)=> ({
9 | testDetails : {
10 | margin:'20px',
11 | display: 'inline-block',
12 | textAlign : 'center',
13 | },
14 | testTitle : {
15 | fontSize : '1.7em',
16 | textAlign : 'center',
17 | margin : '20px'
18 | }
19 | })
20 |
21 | class UpcomingStudentTestsDetails extends React.Component {
22 | constructor(props){
23 | super(props);
24 | this.state = {}
25 | }
26 |
27 | render() {
28 | if(this.props.testDetails.upcomingTestRetrived === true) {
29 | return(
30 |
Upcoming Tests
31 |
32 |
)
33 | } else if (this.props.testDetails.viewTestRetrived){
34 | return()
38 | }
39 | else {
40 |
41 | this.props.getUpcomingTestsStudentAction();
42 | return (
)
43 | }
44 | }
45 | }
46 |
47 | const mapStatetoProps = state => ({
48 | user : state.user,
49 | testDetails : state.testDetails
50 | })
51 |
52 | export default withStyles(useStyles)(connect(mapStatetoProps,{
53 | getUpcomingTestsStudentAction
54 | })(UpcomingStudentTestsDetails));
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/templates/TestDetails/CompletedTestsDetailsStudent.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { withStyles } from "@material-ui/core/styles";
3 | import { connect } from "react-redux";
4 | import { getCompletedTestsStudentAction } from "../../../redux/actions/studentTestAction";
5 | import CompletedTestTableStudent from "../../molecues/TestTable/CompletedTestTableStudent";
6 | import TestResultStudent from "../../molecues/ResultView/TestResultStudent";
7 |
8 | const useStyles = (theme)=> ({
9 | testDetails : {
10 | margin:'20px',
11 | display: 'inline-block',
12 | textAlign : 'center',
13 | },
14 | testTitle : {
15 | fontSize : '1.7em',
16 | textAlign : 'center',
17 | margin : '20px'
18 | }
19 | })
20 |
21 | class CompletedTestsDetailsStudent extends React.Component {
22 | constructor(props){
23 | super(props);
24 | this.state = {}
25 | }
26 |
27 | render() {
28 | if(this.props.testDetails.completedTestRetrived === true) {
29 | return(
30 |
Completed Tests
31 |
32 |
)
33 | } else if (this.props.testDetails.viewTestResult === true){
34 | return(
35 |
Test Result
36 |
37 |
)
38 | }
39 | else {
40 |
41 | this.props.getCompletedTestsStudentAction();
42 | return (
)
43 | }
44 | }
45 | }
46 |
47 | const mapStatetoProps = state => ({
48 | user : state.user,
49 | testDetails : state.testDetails
50 | })
51 |
52 | export default withStyles(useStyles)(connect(mapStatetoProps,{
53 | getCompletedTestsStudentAction
54 | })(CompletedTestsDetailsStudent));
--------------------------------------------------------------------------------
/frontend/src/redux/actions/loginAction.js:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 | import apis from "../../services/Apis";
3 | import { ActionTypes } from "../constants/action-types";
4 | import Auth from "../../services/Auth";
5 | import Alert from "../../services/alert";
6 | import { Navigate } from "react-router-dom";
7 |
8 | export const loginUser = (details) => {
9 | return async(dispatch, getState) => {
10 | const response = await axios.post(apis.BASE+ apis.LOGIN,details);
11 | if(response.data.success) {
12 | Auth.storeToken(response.data.token);
13 | dispatch( {
14 | type: ActionTypes.LOGIN,
15 | payload : {
16 | isLoggedIn : true,
17 | userDetails : response.data.admin
18 | }
19 | })
20 | } else {
21 | Auth.deleteToken();
22 | return Alert('error','invalid data',response.data.message);
23 | }
24 | }
25 | }
26 |
27 | export const getAdminDetails = () => {
28 | return async(dispatch) => {
29 | axios.get(apis.BASE+apis.GET_ADMIN_DETAILS, {
30 | headers:{
31 | 'Authorization':`Bearer ${Auth.retriveToken()}`
32 | }
33 | }).then(response => {
34 |
35 | if(response.data.success) {
36 | dispatch({
37 | type:ActionTypes.LOGIN,
38 | payload: {
39 | isLoggedIn : true,
40 | userDetails : response.data.user
41 | }
42 | })
43 | } else {
44 | Auth.deleteToken();
45 | return ( );
46 | }
47 |
48 | }).catch(err => {
49 | console.log(err);
50 | Auth.deleteToken();
51 | return ( );
52 | })
53 |
54 | }
55 | }
56 |
57 | export const logoutUser = ()=> dispatch =>{
58 | Auth.deleteToken();
59 | dispatch({
60 | type : ActionTypes.LOGOUT,
61 | payload : 'Manual Logout'
62 | })
63 | }
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/user-portal-frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/frontend/src/components/Dashboard/studentTable/studentTable.js:
--------------------------------------------------------------------------------
1 | import { connect } from "react-redux"
2 | import React from "react";
3 | import {getStudentDetails, StudentToggleStatus} from "../../../redux/actions/studentDetails";
4 | import './studentTable.css'
5 |
6 |
7 | class StudentTable extends React.Component {
8 | constructor(props) {
9 | super(props)
10 | this.state = {}
11 | }
12 |
13 | handleStatusChange(status, id) {
14 | this.props.StudentToggleStatus(status,id,this.props.getStudentDetails);
15 | }
16 |
17 | buttonTextBasedOnStatus(status) {
18 | if(status === true) {
19 | return("block");
20 | } else {
21 | return("unblock");
22 | }
23 | }
24 |
25 | render(){
26 | if(this.props.students.retrived===false) {
27 | this.props.getStudentDetails();
28 | return (Collecting data
);
29 | }
30 |
31 | return (
32 |
33 |
Students
34 |
35 |
36 |
37 | Name
38 | Status
39 | Action
40 |
41 |
42 |
43 | {this.props.students.list.map((val,key)=>{
44 | return (
45 |
46 | {val.name}
47 | {val.status.toString()}
48 | (this.handleStatusChange(val.status,val.id))}>{this.buttonTextBasedOnStatus(val.status)}
49 |
50 | )
51 | })}
52 |
53 |
54 |
)
55 | }
56 | }
57 |
58 | const mapStateToProps = state => ({
59 | students : state.students
60 | });
61 |
62 | export default connect(mapStateToProps,{
63 | getStudentDetails,
64 | StudentToggleStatus
65 | })(StudentTable);
--------------------------------------------------------------------------------
/frontend/src/components/Dashboard/teacherTable/teacherTable.js:
--------------------------------------------------------------------------------
1 | import { connect } from "react-redux"
2 | import React from "react";
3 | import {getTeacherDetails, TeacherToggleStatus} from "../../../redux/actions/teacherDetails";
4 | import './teacherTable.css'
5 |
6 |
7 | class TeacherTable extends React.Component {
8 | constructor(props) {
9 | super(props)
10 | this.state = {}
11 | }
12 |
13 | handleStatusChange(status, id) {
14 | this.props.TeacherToggleStatus(status,id,this.props.getTeacherDetails);
15 | }
16 |
17 | buttonTextBasedOnStatus(status) {
18 | if(status === true) {
19 | return("block");
20 | } else {
21 | return("unblock");
22 | }
23 | }
24 |
25 | render(){
26 | if(this.props.teachers.retrived===false) {
27 | this.props.getTeacherDetails();
28 | return (Collecting data
);
29 | }
30 |
31 | return (
32 |
33 |
Teachers
34 |
35 |
36 |
37 | Name
38 | Status
39 | Action
40 |
41 |
42 |
43 | {this.props.teachers.list.map((val,key)=>{
44 | return (
45 |
46 | {val.name}
47 | {val.status.toString()}
48 | (this.handleStatusChange(val.status,val.id))}>{this.buttonTextBasedOnStatus(val.status)}
49 |
50 | )
51 | })}
52 |
53 |
54 |
)
55 | }
56 | }
57 |
58 | const mapStateToProps = state => ({
59 | teachers : state.teachers
60 | });
61 |
62 | export default connect(mapStateToProps,{
63 | getTeacherDetails,
64 | TeacherToggleStatus
65 | })(TeacherTable);
--------------------------------------------------------------------------------
/backend/services/login.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken');
2 |
3 | var config = require('config');
4 |
5 | var passport = require('./passportconf');
6 |
7 | var userLogin = (req, res, next) => {
8 |
9 | req.check('email','Invalid email address').isEmail().notEmpty();
10 | req.check('password','Invalid password').isLength({min:4, max:20});
11 |
12 | var errors = req.validationErrors();
13 |
14 | if(errors) {
15 | res.json({
16 | success : false,
17 | message : 'Invalid inputs',
18 | errors : errors
19 | })
20 | }
21 | else {
22 | passport.authenticate('login', {session: false}, (err, user, info)=>{
23 | if(err || !user) {
24 | res.json(info);
25 | }
26 | else {
27 | req.login({_id:user._id}, {session : false}, (err)=>{
28 | if(err) {
29 | res.json({
30 | success : false,
31 | message : 'server error'
32 | });
33 | }
34 |
35 | var token = jwt.sign({_id:user._id},config.get('jwt.secret'),{expiresIn:'1d'});
36 | res.json({
37 | success: true,
38 | message : 'login successful',
39 | user : {
40 | username : user.username,
41 | type : user.usertype,
42 | _id : user._id,
43 | email : user.email
44 | },
45 | token : token
46 | })
47 | })
48 | }
49 | })(req,res,next);
50 | }
51 | }
52 |
53 | var userDetails = (req,res,next) => {
54 | if(req.user) {
55 | res.json({
56 | success:true,
57 | user : {
58 | username : req.user.username,
59 | type : req.user.usertype,
60 | _id : req.user._id,
61 | email : req.user.email
62 | }
63 | });
64 | }
65 | else {
66 | res.json({
67 | success: false,
68 | user: {}
69 | });
70 | }
71 | }
72 |
73 | module.exports = { userLogin, userDetails };
--------------------------------------------------------------------------------
/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '1'
2 |
3 | services:
4 | backend:
5 | container_name: backend-app
6 | image: backend-app:1.0
7 | environment:
8 | - NODE_ENV=docker
9 | build:
10 | context: backend
11 | dockerfile: Dockerfile
12 | ports:
13 | - 5000:5000
14 | links:
15 | - mongodb
16 |
17 |
18 | mongodb:
19 | image: mongo
20 | ports:
21 | - 27018:27017
22 | environment:
23 | - MONGO_INITDB_ROOT_USERNAME=admin
24 | - MONGO_INITDB_ROOT_PASSWORD=password
25 | volumes:
26 | - mongo-data:/data/db
27 | healthcheck:
28 | test: echo 'db.runCommand("ping").ok' | mongo mongo:27017/test --quiet
29 | interval: 10s
30 | timeout: 10s
31 | retries: 5
32 | start_period: 40s
33 |
34 | mongo-express:
35 | image: mongo-express
36 | restart: always
37 | ports:
38 | - 8080:8081
39 | environment:
40 | - ME_CONFIG_MONGODB_ADMINUSERNAME=admin
41 | - ME_CONFIG_MONGODB_ADMINPASSWORD=password
42 | - ME_CONFIG_MONGODB_SERVER=mongodb
43 | depends_on:
44 | - "mongodb"
45 |
46 |
47 | frontend:
48 | container_name: frontend-app
49 | image: frontend-app:1.0
50 | environment:
51 | - NODE_ENV=docker
52 | - REACT_APP_API_BASE_URL=http://backend:5000
53 |
54 | build:
55 | context: frontend
56 | dockerfile: Dockerfile
57 | ports:
58 | - 3100:3000
59 | depends_on:
60 | - backend
61 |
62 | user-frontend:
63 | container_name: user-frontend-app
64 | image: user-frontend-app:1.0
65 | environment:
66 | - NODE_ENV=docker
67 | - REACT_APP_API_BASE_URL=http://backend:5000
68 | build:
69 | context: user-portal-frontend
70 | dockerfile: Dockerfile
71 |
72 | ports:
73 |
74 | - 3200:3000
75 |
76 |
77 | depends_on:
78 | - backend
79 | - frontend
80 |
81 | volumes:
82 | mongo-data:
83 | driver: local
--------------------------------------------------------------------------------
/frontend/src/components/Dashboard/subjectTable/subjectTable.js:
--------------------------------------------------------------------------------
1 | import { connect } from "react-redux"
2 | import React from "react";
3 | import {getSubjectDetails, SubjectToggleStatus} from "../../../redux/actions/subjectDetails";
4 | import './subjectTable.css'
5 |
6 |
7 | class SubjectTable extends React.Component {
8 | constructor(props) {
9 | super(props)
10 | this.state = {}
11 | }
12 |
13 | handleStatusChange(status, id) {
14 | this.props.SubjectToggleStatus(status,id,this.props.getSubjectDetails);
15 | }
16 |
17 | buttonTextBasedOnStatus(status) {
18 | if(status === true) {
19 | return("block");
20 | } else {
21 | return("unblock");
22 | }
23 | }
24 |
25 | render(){
26 | if(this.props.subjects.retrived===false) {
27 | this.props.getSubjectDetails();
28 | return (Collecting data
);
29 | }
30 |
31 | return (
32 |
33 |
Subjects
34 |
35 |
36 |
37 | Name
38 | Status
39 | Action
40 |
41 |
42 |
43 | {this.props.subjects.list.map((val,key)=>{
44 | return (
45 |
46 | {val.subject}
47 | {val.status.toString()}
48 |
49 | (this.handleStatusChange(val.status,val.id))}>{this.buttonTextBasedOnStatus(val.status)}
50 |
51 | )
52 | })}
53 |
54 |
55 |
)
56 | }
57 | }
58 |
59 | const mapStateToProps = state => ({
60 | subjects : state.subjects
61 | });
62 |
63 | export default connect(mapStateToProps,{
64 | getSubjectDetails,
65 | SubjectToggleStatus
66 | })(SubjectTable);
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/pages/studentRegisterPage/studentRegisterPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import AlertBox from '../../atoms/Alertbox/AlertBox';
3 | import StudentRegisterForm from '../../templates/studentRegisterForm/studentRegisterForm';
4 | import { Button } from '@material-ui/core';
5 | import { AppBar, Toolbar, Typography, withStyles } from '@material-ui/core';
6 | import { Navigate } from 'react-router-dom';
7 |
8 | const useStyles = (theme) => ({
9 | addHeight : theme.mixins.toolbar,
10 | title : {
11 | flexGrow : 1
12 | },
13 | main : {
14 | textAlign : 'center',
15 | paddingTop : '5%'
16 | }
17 | })
18 |
19 | class StudentRegisterPage extends React.Component {
20 | constructor(props) {
21 | super(props);
22 | this.state = {
23 | gotoHome: false
24 | }
25 | }
26 |
27 | onHomeClick() {
28 | this.setState({
29 | ...this.state,
30 | gotoHome : true
31 | })
32 | }
33 |
34 | render() {
35 | if(this.state.gotoHome) {
36 | return ( )
37 | }
38 | return(
39 |
40 |
44 |
45 |
46 | Student Register
47 |
48 |
49 | (this.onHomeClick())}>Home
50 |
51 |
52 |
53 |
54 |
58 |
59 | )
60 | }
61 | }
62 |
63 |
64 | export default withStyles(useStyles)(StudentRegisterPage);
65 |
66 |
--------------------------------------------------------------------------------
/user-portal-frontend/src/redux/actions/loginAction.js:
--------------------------------------------------------------------------------
1 | import apis from "../../helper/Apis";
2 | import { ActionTypes } from "../action-types";
3 | import Auth from "../../helper/Auth";
4 | import axios from "axios";
5 | import { Navigate } from "react-router-dom";
6 |
7 | export const loginRequestAction = (details) => {
8 | return async(dispatch) => {
9 | const response = await axios.post(apis.BASE+ apis.LOGIN,details);
10 | if(response.data.success) {
11 | Auth.storeToken(response.data.token);
12 | dispatch( {
13 | type: ActionTypes.LOGIN,
14 | payload : {
15 | isLoggedIn : true,
16 | userDetails : response.data.user
17 | }
18 | })
19 | } else {
20 | Auth.deleteToken();
21 | dispatch({
22 | type:ActionTypes.SET_ALERT,
23 | payload : {
24 | isAlert : true,
25 | title : "Login Failed",
26 | type : "error",
27 | message : response.data.message
28 | }
29 | })
30 | }
31 | }
32 | }
33 |
34 |
35 | export const logoutUser = ()=> dispatch =>{
36 | Auth.deleteToken();
37 | dispatch({
38 | type : ActionTypes.LOGOUT,
39 | payload : 'Manual Logout'
40 | })
41 | return ( )
42 | }
43 |
44 | export const getUserDetails = () => {
45 | return async(dispatch) => {
46 | axios.get(apis.BASE+apis.GET_USER_DETAILS, {
47 | headers:{
48 | 'Authorization':`Bearer ${Auth.retriveToken()}`
49 | }
50 | }).then(response => {
51 |
52 | if(response.data.success) {
53 | dispatch({
54 | type:ActionTypes.LOGIN,
55 | payload: {
56 | isLoggedIn : true,
57 | userDetails : response.data.user
58 | }
59 | })
60 | } else {
61 | Auth.deleteToken();
62 | return ( );
63 | }
64 |
65 | }).catch(err=> {
66 | Auth.deleteToken();
67 | return ( );
68 | })
69 |
70 | }
71 | }
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/molecues/TestView/Timer.js:
--------------------------------------------------------------------------------
1 | import { withStyles } from "@material-ui/styles";
2 | import React from "react";
3 | import { connect } from "react-redux";
4 | import { saveAnswerAction } from "../../../redux/actions/takeTestAction";
5 | import { endTestAction } from "../../../redux/actions/takeTestAction";
6 | const useStyles = (theme)=>({
7 |
8 | })
9 |
10 | var x = null;
11 |
12 | class Timer extends React.Component {
13 | constructor(props) {
14 | super(props);
15 | console.log(props.time);
16 | this.state = {
17 | h : parseInt(props.time/3600000),
18 | m : parseInt(((props.time/1000)%3600)/60),
19 | s : parseInt((props.time/1000)%60),
20 | startCountDown : false
21 | }
22 | }
23 |
24 |
25 | countdown() {
26 | if(x !== null) {
27 | clearInterval(x);
28 | }
29 | x = setInterval(()=>{
30 | var h = this.state.h;
31 | var m = this.state.m;
32 | var s = this.state.s - 1;
33 | if(s < 0) {
34 | this.props.saveAnswerAction();
35 | s = 59;
36 | m = m - 1;
37 | if(m < 0) {
38 | m = 59;
39 | h = h - 1;
40 | if(h < 0 ){
41 | this.props.endTestAction();
42 | this.setState({h:0,m:0,s:0,startCountDown : true});
43 | console.log("test ended");
44 | clearInterval(x);
45 | return;
46 | }
47 | }
48 | }
49 | this.setState({h:h,m:m,s:s, startCountDown: true});
50 | },1000);
51 | }
52 |
53 | render() {
54 | if(this.props.time === undefined) {
55 | console.log("time not found");
56 | return (
);
57 | }
58 | if(this.state.startCountDown===false) {
59 | this.countdown();
60 | }
61 | return ({this.state.h}:{this.state.m}:{this.state.s}
)
62 | }
63 |
64 | }
65 |
66 | const mapStatetoProps = state => ({
67 |
68 | })
69 |
70 | export default withStyles(useStyles)(connect(mapStatetoProps,{
71 | saveAnswerAction,
72 | endTestAction
73 |
74 | })(Timer));
--------------------------------------------------------------------------------
/backend/routes/user.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 |
4 | var loginService = require('../services/login');
5 | var questionService = require('../services/question');
6 | var subjectService = require('../services/subject');
7 | var testService = require('../services/test');
8 | var taketestService = require('../services/taketest');
9 | var resultService = require('../services/result');
10 |
11 | router.get('/details',loginService.userDetails);
12 | router.get('/getAllTest',testService.getAllTest);
13 | router.post('/getTestById',testService.getTestDetailsFromId);
14 |
15 | // teacher user specific routes
16 | router.post('/addQuestion',questionService.addQuestion);
17 | router.get('/getAllSubjects',subjectService.getAllActiveSubject);
18 | router.post('/searchQuestion',questionService.searchQuestion);
19 | router.post('/updateQuestion',questionService.updateQuestion);
20 | router.post('/getQuestion',questionService.getQuestionById);
21 | router.post('/changeQuestionStatus',questionService.changeQuestionStatus);
22 | router.post('/getAnswer',questionService.getAnsByQuestionId);
23 | router.post('/getQuestionAnswer',questionService.getQuestionAnswerById);
24 | router.post('/createTest',testService.createTest);
25 |
26 |
27 | // student user specific routes
28 | router.post('/testRegistration',testService.testRegistration);
29 | router.get('/getAllTestStudent',testService.getAllTestWithStudentRegisterCheck);
30 | router.get('/getUpcomingTests',testService.getUpcomingTestforStudent);
31 |
32 |
33 | router.post('/startTest',taketestService.startTestForStudent);
34 | router.post('/getQuenStarttime',taketestService.getQuestionsAndSetStartTime);
35 | router.post('/saveAnswer',taketestService.saveAnswer);
36 | router.post('/endTest',taketestService.saveAnswerandEndTest);
37 |
38 | router.get('/getAllCompletedTest',resultService.getAllCompletedTest);
39 | router.post('/getResultMainDetailsByTestId',resultService.getResultMainDetailsByTestId);
40 | router.post('/getQuestionAnswerByIds',questionService.getQuestionAnswerByIds)
41 | module.exports = router;
--------------------------------------------------------------------------------
/backend/services/register.js:
--------------------------------------------------------------------------------
1 | var userModel = require('../models/user');
2 | var tool = require('./tool');
3 |
4 | var studentRegister = (req,res,next) => {
5 |
6 | req.check('username','Invalid name').notEmpty();
7 | req.check('email','Invalid Email Address').isEmail().notEmpty();
8 | req.check('password','Invalid Password').isLength({min: 5, max: 20});
9 |
10 | var errors = req.validationErrors();
11 | if(errors) {
12 | res.status(400).json({
13 | success : false,
14 | message : 'Invalid inputs',
15 | errors : errors
16 | })
17 | }
18 | else {
19 | var username = req.body.username;
20 | var password = req.body.password;
21 | var email = req.body.email;
22 |
23 | userModel.findOne({'email':email}).then((user)=>{
24 | //user already exists
25 | if(user) {
26 | res.status(400).json({
27 | success : false,
28 | message : 'This email is already exists!'
29 | })
30 | } else {
31 | //add user to database
32 | tool.hashPassword(password)
33 | .then((hash)=> {
34 | var tempdata = new userModel({
35 | username : username,
36 | password : hash,
37 | email : email,
38 | usertype : 'STUDENT'
39 | })
40 | tempdata.save()
41 | .then(()=>{
42 | res.json({
43 | success : true,
44 | message : 'Profile created successfully!'
45 | })
46 | })
47 | .catch((err)=>{
48 | res.status(500).json({
49 | success : false,
50 | message : "Unable to register Profile"
51 | })
52 | })
53 | })
54 | .catch((err) => {
55 | res.status(500).json({
56 | success : false,
57 | message : "Unable to register Profile"
58 | })
59 | })
60 | }
61 | }).catch((err)=>{
62 | res.status(500).json({
63 | success : false,
64 | message : "Unable to register profile"
65 | })
66 | })
67 | }
68 | }
69 |
70 | module.exports = { studentRegister }
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/molecues/TestTable/TestTable.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { withStyles } from "@material-ui/styles";
3 | import { connect } from "react-redux";
4 | import { TableBody, TableCell, TableRow, Table, TableHead, TableContainer, Paper } from "@material-ui/core";
5 | import { getTestDetailsFromId } from "../../../redux/actions/teacherTestAction";
6 |
7 |
8 | const useStyles = (theme)=> ({
9 | tableBorder:{
10 | background:'#e7e7e7',
11 | padding:'15px'
12 | },
13 | tableHeader:{
14 | background:'#3f51b5',
15 | color:'white'
16 | }
17 | })
18 |
19 | class TestTable extends React.Component {
20 | constructor(props){
21 | super(props);
22 | this.state = {}
23 | }
24 |
25 | onTestClick(event,id) {
26 | this.props.getTestDetailsFromId({testid:id});
27 | }
28 |
29 | render() {
30 | return(
31 |
32 |
33 |
34 |
35 | No.
36 | Test
37 | Status
38 |
39 |
40 |
41 | {this.props.testlist.map((test,index)=>(
42 |
43 | {index+1}
44 | (this.onTestClick(event,test._id))}>{test.title}
45 | {test.status}
46 |
47 | ))}
48 |
49 |
50 |
51 |
52 |
)
53 | }
54 | }
55 |
56 | const mapStatetoProps = state => ({
57 | testlist : state.testDetails.list
58 | })
59 |
60 | export default withStyles(useStyles)(connect(mapStatetoProps,{
61 | getTestDetailsFromId
62 | })(TestTable));
63 |
--------------------------------------------------------------------------------
/backend/services/subject.js:
--------------------------------------------------------------------------------
1 | const subjectModel = require("../models/subject")
2 |
3 |
4 | var getAllSubject = (req, res, next) => {
5 | subjectModel.find({}, (err, sub)=>{
6 | if(err) {
7 | res.status(500).json({
8 | success:false,
9 | message : 'Internal server error'
10 | })
11 | } else {
12 | var subjects = []
13 | sub.forEach((subject)=>{
14 | subjects.push({
15 | "id" : subject._id,
16 | "subject" : subject.name,
17 | "status" : subject.status
18 | })
19 | })
20 | res.json({
21 | success : true,
22 | subjects : subjects
23 | })
24 | }
25 | })
26 | }
27 |
28 | var getAllActiveSubject = (req, res, next) => {
29 | subjectModel.find({status:true}, (err, sub)=>{
30 | if(err) {
31 | res.status(500).json({
32 | success:false,
33 | message : 'Internal server error'
34 | })
35 | } else {
36 | var subjects = []
37 | sub.forEach((subject)=>{
38 | subjects.push({
39 | "id" : subject._id,
40 | "subject" : subject.name,
41 | "status" : subject.status
42 | })
43 | })
44 | res.json({
45 | success : true,
46 | subjects : subjects
47 | })
48 | }
49 | })
50 | }
51 |
52 | var getStatusCount = (req,res,next) => {
53 | subjectModel.aggregate(
54 | [
55 | {$match:{}},
56 | {$group: {_id:"$status",count:{$sum:1}} }
57 | ]
58 | )
59 | .then((result)=>{
60 | var trueCount = 0
61 | var falseCount = 0
62 | result.forEach((x)=>{
63 | if(x._id == true) {
64 | trueCount = x.count
65 | }
66 | if(x._id == false) {
67 | falseCount = x.count
68 | }
69 | })
70 | res.json({
71 | success:true,
72 | active : trueCount,
73 | blocked : falseCount
74 | })
75 | })
76 | .catch((err)=>{
77 | console.log(err)
78 | res.status(500).json({
79 | success:false,
80 | message:'Internal server error'
81 | })
82 | })
83 | }
84 |
85 | module.exports = {
86 | getAllSubject,
87 | getStatusCount,
88 | getAllActiveSubject
89 | }
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/templates/QuestionDetails/questionDetails.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { withStyles } from "@material-ui/core/styles";
3 | import { connect } from "react-redux";
4 | import QuestionSearchBox from "../../atoms/SearchBox/QuestionSearchBox";
5 | import { searchQuestion } from "../../../redux/actions/questionAction";
6 | import QuestionTable from "../../molecues/QuestionsTable/QuestionTable";
7 | import ViewnUpdateQuestion from "../ViewnUpdateQuestion/ViewnUpdateQuestion";
8 | import { Button } from "@material-ui/core";
9 | import { goBacktoSearch } from "../../../redux/actions/questionAction";
10 |
11 | const useStyles = (theme)=> ({
12 | questionDetails : {
13 | margin:'20px',
14 | display: 'inline-block',
15 | textAlign : 'center',
16 | }
17 | })
18 |
19 | class QuestionDetails extends React.Component {
20 | constructor(props){
21 | super(props);
22 | this.state = {}
23 | }
24 |
25 | backHandler() {
26 | this.props.goBacktoSearch();
27 | }
28 |
29 | render(){
30 | if(this.props.questionDetails.searched === true) {
31 | return(
32 |
33 |
34 |
)
35 | } else if(this.props.questionDetails.question._id !== undefined) {
36 | return(
37 |
38 |
39 |
40 | Back
41 |
)
42 | }
43 | else {
44 | return(
45 |
46 |
)
47 | }
48 | }
49 | }
50 |
51 | const mapStatetoProps = state => ({
52 | user : state.user,
53 | questionDetails : state.questionDetails
54 | })
55 |
56 | export default withStyles(useStyles)(connect(mapStatetoProps,{
57 | searchQuestion,
58 | goBacktoSearch
59 | })(QuestionDetails));
--------------------------------------------------------------------------------
/frontend/src/components/basic/login/login.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Navigate } from "react-router-dom";
3 | import './login.css';
4 | import { connect } from 'react-redux';
5 | import { loginUser} from '../../../redux/actions/loginAction';
6 |
7 |
8 |
9 |
10 | class Login extends React.Component {
11 | constructor(props) {
12 | super(props);
13 | this.state = {
14 | username : "",
15 | password : ""
16 | }
17 | }
18 |
19 | usernameInputHandler = (event)=>{
20 | this.setState({
21 | ...this.state,
22 | username : event.target.value
23 | });
24 | }
25 |
26 | passwordInputHandler = (event) => {
27 | this.setState({
28 | ...this.state,
29 | password : event.target.value
30 | });
31 | }
32 |
33 | handleSubmit = (event) => {
34 | event.preventDefault();
35 | this.props.loginUser({username:this.state.username,password:this.state.password});
36 | }
37 |
38 | render(){
39 | if(this.props.user.isLoggedIn) {
40 | return ( );
41 | }
42 | else {
43 | return (
44 |
45 |
Admin Login
46 |
63 |
64 | );
65 | }
66 | }
67 | }
68 |
69 |
70 | const mapStateToProps = state => ({
71 | user : state.user
72 | });
73 |
74 | const mapDispatchToProps = {
75 | loginUser
76 | }
77 |
78 | export default connect(mapStateToProps,mapDispatchToProps)(Login);
--------------------------------------------------------------------------------
/frontend/src/components/Dashboard/AddSubject/AddSubject.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { connect } from "react-redux";
3 | import { Link, Navigate } from "react-router-dom";
4 | import Alert from "../../../services/alert";
5 | import Auth from "../../../services/Auth";
6 | import { getAdminDetails } from "../../../redux/actions/loginAction";
7 | import "./AddSubject.css"
8 | import axios from "axios";
9 | import apis from "../../../services/Apis";
10 |
11 | class AddSubject extends React.Component {
12 | constructor(props) {
13 | super(props)
14 | this.state = {
15 | name : ""
16 | }
17 | }
18 |
19 | nameInputHandler = (event)=>{
20 | this.setState({
21 | ...this.state,
22 | name : event.target.value
23 | });
24 | }
25 |
26 |
27 |
28 | handleSubmit= (event) => {
29 | event.preventDefault();
30 |
31 | console.log(this.state);
32 | axios.post(apis.BASE + apis.ADD_SUBJECT, {
33 | name : this.state.name
34 | },{
35 | headers:{
36 | 'Authorization':`Bearer ${Auth.retriveToken()}`
37 | }
38 | }).then(response => {
39 | console.log(response.data);
40 | if(response.data.success) {
41 | Alert('info','Success',response.data.message);
42 | } else {
43 | Alert('error','Failed',response.data.message);
44 | }
45 | })
46 | }
47 |
48 | render(){
49 | if(!Auth.retriveToken() || Auth.retriveToken()==='undefined'){
50 | return ( );
51 | } else if(!this.props.user.isLoggedIn) {
52 | this.props.getAdminDetails();
53 | return (
)
54 | }
55 | return (
56 |
57 |
68 | )
69 | }
70 | }
71 |
72 | const mapStateToProps = state => ({
73 | user:state.user
74 | });
75 |
76 | export default connect(mapStateToProps,{
77 | getAdminDetails
78 | })(AddSubject);
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/atoms/SearchBox/QuestionSearchBox.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { withStyles } from "@material-ui/core/styles";
3 | import { connect } from "react-redux";
4 | import InputBase from '@material-ui/core/InputBase';
5 | import IconButton from '@material-ui/core/IconButton';
6 | import SearchIcon from '@material-ui/icons/Search';
7 | import { setAlert } from "../../../redux/actions/alertAction";
8 |
9 | const useStyles = (theme)=> ({
10 | input: {
11 | marginLeft: theme.spacing(1),
12 | flex: 1,
13 | minWidth: 400
14 | },
15 | iconButton: {
16 | padding: 10,
17 | },
18 | searchbox:{
19 | border:'2px solid',
20 | borderColor:"#3f51b5",
21 | borderRadius:'30px',
22 | display:'inline-box',
23 | padding:'3px 8px',
24 | margin:10
25 | }
26 | })
27 |
28 | class QuestionSearchBox extends React.Component {
29 | constructor(props) {
30 | super(props);
31 | this.state = {
32 | query:''
33 | }
34 | }
35 |
36 | searchInputHandler(event) {
37 | this.setState({
38 | ...this.state,
39 | query:event.target.value
40 | })
41 | }
42 |
43 | searchButtonClick(event){
44 | if(this.state.query === '') {
45 | this.props.setAlert({
46 | isAlert:true,
47 | type:'error',
48 | title:'Error',
49 | message:'Please give input to search'
50 | })
51 | }
52 | console.log(this.state);
53 | this.props.searchCallback(this.state);
54 | }
55 |
56 |
57 | render() {
58 | return(
59 |
60 | (this.searchInputHandler(event))}
65 | value={this.state.query}
66 | />
67 | (this.searchButtonClick(event))}
71 | >
72 |
73 |
74 |
75 | )
76 | }
77 | }
78 |
79 | const mapStatetoProps = state => ({
80 | })
81 |
82 | export default withStyles(useStyles)(connect(mapStatetoProps,{
83 | setAlert
84 | })(QuestionSearchBox));
--------------------------------------------------------------------------------
/backend/app.js:
--------------------------------------------------------------------------------
1 |
2 | var createError = require('http-errors');
3 | var express = require('express');
4 | const session = require('express-session');
5 | const helmet = require('helmet')
6 | var path = require('path');
7 | var logger = require('morgan');
8 | var bodyParser = require('body-parser');
9 | const expressValidator = require('express-validator');
10 | var passport = require("./services/passportconf");
11 | var app = express();
12 | const cors = require('cors');
13 |
14 | app.use(helmet());
15 | app.use(function(req, res, next) {
16 | res.header("Access-Control-Allow-Origin", "*");
17 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, access-control-allow-origin");
18 | next();
19 | });
20 |
21 |
22 | const corsOptions = {
23 | origin: '*'
24 | }
25 | app.use(cors(corsOptions));
26 | app.use(expressValidator());
27 |
28 | //database connection
29 | require("./services/connection");
30 |
31 | //import files
32 | var publicRoutes = require("./routes/public");
33 | var login = require("./routes/login");
34 | var adminLogin = require('./routes/adminLogin');
35 | var admin = require('./routes/admin');
36 | var user = require('./routes/user');
37 |
38 |
39 | //configs
40 | app.use(express.static(path.join(__dirname, 'public')));
41 | app.use(logger('dev'));
42 | app.use(bodyParser.json());
43 | app.use(bodyParser.urlencoded({ extended: true }));
44 | app.use(session({secret:'express-session secret'}))
45 |
46 | //passport
47 | app.use(passport.initialize());
48 | app.use(passport.session());
49 |
50 | //bind routes
51 | app.use('/api/v1/public',publicRoutes);
52 | app.use('/api/v1/login',login);
53 | app.use('/api/v1/adminlogin',adminLogin);
54 | app.use('/api/v1/admin',passport.authenticate('admin-token', {session:false}),admin);
55 | app.use('/api/v1/user',passport.authenticate('user-token', {session:false}),user);
56 |
57 | app.get('*', (req,res) =>{
58 | res.sendFile(path.join(__dirname+'/public/index.html'));
59 | });
60 |
61 | //error handlings
62 | app.use(function(req, res, next) {
63 | next(createError(404,"Invalid API. Use the official documentation to get the list of valid APIS."));
64 | });
65 |
66 | app.use((err, req, res, next)=>{
67 | console.log(err);
68 | res.status(err.status).json({
69 | success : false,
70 | message : err.message
71 | });
72 | });
73 |
74 |
75 | module.exports = app;
76 |
77 |
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/molecues/TestView/TestQuestion.js:
--------------------------------------------------------------------------------
1 | import { withStyles } from "@material-ui/styles";
2 | import { connect } from "react-redux";
3 | import React from "react";
4 | import { FormControl, FormControlLabel, FormLabel, RadioGroup, Radio } from "@material-ui/core";
5 | import { selectedOptionAction } from "../../../redux/actions/takeTestAction";
6 |
7 |
8 | const useStyles = (theme) => ({
9 | quebody : {
10 | margin : "15px"
11 | },
12 | options : {
13 | margin : "15px"
14 | }
15 | })
16 |
17 | class TestQuestion extends React.Component {
18 | constructor(props) {
19 | super(props);
20 | console.log(this.props.question)
21 | this.state = {
22 | selectedOption : this.props.taketest.answersheet.answers[parseInt(this.props.question)] || null
23 | }
24 | }
25 |
26 | optionSelectHandler(event) {
27 | this.props.selectedOptionAction({
28 | index : this.props.question,
29 | ans : event.target.value
30 | })
31 | }
32 |
33 | render() {
34 | if(this.props.question!==undefined) {
35 | var que = this.props.taketest.questionid[this.props.question];
36 | var selectValue = this.props.taketest.answersheet.answers[parseInt(this.props.question)] || null
37 | return(
38 |
{que.body}
39 |
40 | Select Answer
41 | (this.optionSelectHandler(event))}>
42 | } label={que.options[0]} />
43 | } label={que.options[1]} />
44 | } label={que.options[2]} />
45 | } label={que.options[3]} />
46 |
47 |
48 |
49 |
)
50 | } else {
51 | return qustion is undefined
52 | }
53 | }
54 | }
55 |
56 | const mapStatetoProps = state => ({
57 | taketest : state.takeTestDetails
58 | })
59 |
60 | export default withStyles(useStyles)(connect(mapStatetoProps,{
61 | selectedOptionAction
62 | })(TestQuestion));
63 |
--------------------------------------------------------------------------------
/frontend/src/components/StudentRegister/RegisterForm/registerform.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Alert from "../../../services/alert";
3 | import apis from '../../../services/Apis';
4 | import axios from "axios";
5 | import { connect } from "react-redux";
6 | import './registerform.css';
7 |
8 | class RegisterForm extends React.Component {
9 | constructor(props) {
10 | super(props);
11 | this.state = {
12 | name:"",
13 | email:"",
14 | password:""
15 | };
16 | }
17 |
18 | nameInputHandler = (event)=>{
19 | this.setState({
20 | ...this.state,
21 | name : event.target.value
22 | });
23 | }
24 |
25 | emailInputHandler = (event)=>{
26 | this.setState({
27 | ...this.state,
28 | email : event.target.value
29 | });
30 | }
31 |
32 | passwordInputHandler = (event)=>{
33 | this.setState({
34 | ...this.state,
35 | password : event.target.value
36 | });
37 | }
38 |
39 | handleSubmit= async (event) => {
40 | event.preventDefault();
41 | const details = {
42 | name : this.state.name,
43 | email : this.state.email,
44 | password : this.state.password
45 | }
46 | const response = await axios.post(apis.BASE+ apis.REGISTER_USER,details);
47 | if(response.data.success) {
48 | Alert("info","Suceess",response.data.message);
49 | } else {
50 | Alert("error","Failure",response.data.message);
51 | }
52 | }
53 |
54 | render() {
55 | return (
56 |
57 |
Student Registration
58 |
75 |
76 | )
77 | }
78 | }
79 |
80 | const mapStateToProps = state => ({
81 | user : state.user
82 | });
83 |
84 | const mapDispatchToProps = {}
85 |
86 | export default connect(mapStateToProps,mapDispatchToProps)(RegisterForm);
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/molecues/TestTable/CompletedTestTableStudent.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { withStyles } from "@material-ui/styles";
3 | import { connect } from "react-redux";
4 | import { TableBody, TableCell, TableRow, Table, TableHead, TableContainer, Paper, Button } from "@material-ui/core";
5 | import { getTestResultStudent } from "../../../redux/actions/studentTestAction";
6 |
7 | const useStyles = (theme)=> ({
8 | tableBorder:{
9 | background:'#e7e7e7',
10 | padding:'15px'
11 | },
12 | tableHeader:{
13 | background:'#3f51b5',
14 | color:'white'
15 | }
16 | })
17 |
18 | class CompletedTestTableStudent extends React.Component {
19 | constructor(props){
20 | super(props);
21 | this.state = {}
22 | }
23 |
24 | onTestClick(event,id) {
25 | console.log("view result for test "+id);
26 | this.props.getTestResultStudent({testid:id});
27 | }
28 |
29 | onTestRegister(event,id) {
30 | this.props.studentTestRegister({testid:id});
31 | }
32 |
33 |
34 | render() {
35 | return(
36 |
37 |
38 |
39 |
40 | Test
41 | Status
42 | total marks
43 | View
44 |
45 |
46 |
47 | {this.props.testlist.map((test,index)=>(
48 |
49 | {test.title}
50 | {test.status}
51 | {test.maxmarks}
52 | (this.onTestClick(event,test._id))}>View
53 |
54 | ))}
55 |
56 |
57 |
58 |
59 |
)
60 | }
61 | }
62 |
63 | const mapStatetoProps = state => ({
64 | testlist : state.testDetails.list
65 | })
66 |
67 | export default withStyles(useStyles)(connect(mapStatetoProps,{
68 | getTestResultStudent
69 | })(CompletedTestTableStudent));
70 |
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/pages/loginPage/loginPage.js:
--------------------------------------------------------------------------------
1 | import { Button, withStyles } from '@material-ui/core';
2 | import React from 'react';
3 | import { connect } from 'react-redux';
4 | import { Navigate } from 'react-router-dom';
5 | import AlertBox from '../../atoms/Alertbox/AlertBox';
6 | import LoginForm from '../../templates/loginForm/loginForm';
7 | import Auth from '../../../helper/Auth';
8 | import { AppBar, Toolbar, Typography } from '@material-ui/core';
9 |
10 | const useStyles = (theme) => ({
11 | addHeight : theme.mixins.toolbar,
12 | title : {
13 | flexGrow : 1
14 | },
15 | main : {
16 | textAlign : 'center',
17 | paddingTop : '5%',
18 | margin : 'auto'
19 | }
20 | })
21 |
22 |
23 | class LoginPage extends React.Component {
24 | constructor(props) {
25 | super(props);
26 | this.state = {
27 | gotoStudentRegister: false
28 | }
29 | }
30 |
31 | onStudentRegisterClick () {
32 | this.setState({
33 | ...this.state,
34 | gotoStudentRegister : true
35 | })
36 | }
37 |
38 |
39 | render(){
40 | if(this.state.gotoStudentRegister) {
41 | return ( )
42 | }
43 | if(this.props.user.isLoggedIn) {
44 | if(this.props.user.userDetails.type === 'TEACHER')
45 | return ( );
46 | else
47 | return ( );
48 | } else if(Auth.retriveToken() && Auth.retriveToken()!=='undefined'){
49 |
50 | return ( );
51 | }
52 | else {
53 | return (
54 |
55 |
59 |
60 |
61 | Login
62 |
63 |
64 | (this.onStudentRegisterClick())}>Student Register
65 |
66 |
67 |
68 |
69 |
73 |
74 | )
75 | }
76 | }
77 | }
78 |
79 | const mapStatetoProps = state=>({
80 | user : state.user
81 | });
82 |
83 | export default withStyles(useStyles)(connect(mapStatetoProps,{})(LoginPage));
--------------------------------------------------------------------------------
/user-portal-frontend/src/redux/actions/teacherTestAction.js:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 | import apis from "../../helper/Apis"
3 | import { ActionTypes } from "../action-types";
4 | import Auth from "../../helper/Auth"
5 |
6 | export const createTestAction = (details) => {
7 | return async(dispatch)=> {
8 | const response = await axios.post(apis.BASE + apis.CREATE_QUESTION, details, {
9 | headers:{
10 | 'Authorization':`Bearer ${Auth.retriveToken()}`
11 | }
12 | })
13 | if(response.data.success) {
14 | dispatch({
15 | type:ActionTypes.SET_ALERT,
16 | payload : {
17 | isAlert : true,
18 | title : "Success",
19 | type : "success",
20 | message : response.data.message
21 | }
22 | })
23 | }
24 | else {
25 | dispatch({
26 | type:ActionTypes.SET_ALERT,
27 | payload : {
28 | isAlert : true,
29 | title : "Submit Error",
30 | type : "error",
31 | message : response.data.message
32 | }
33 | })
34 | }
35 | }
36 | }
37 |
38 | export const getAllTestAction = () => {
39 | return async(dispatch)=> {
40 | await axios.get(apis.BASE + apis.GET_ALL_TEST, {
41 | headers : {
42 | 'Authorization':`Bearer ${Auth.retriveToken()}`
43 | }
44 | }).then(response => {
45 | if(response.data.success) {
46 | dispatch({
47 | type: ActionTypes.GET_ALL_TESTS,
48 | payload : {
49 | testlist : response.data.testlist
50 | }
51 | })
52 | }
53 | })
54 | }
55 | }
56 |
57 | export const getTestDetailsFromId = (details) => {
58 | return async(dispatch) => {
59 | const response = await axios.post(apis.BASE+ apis.GET_TEST_DETAILS_BY_ID,details,{
60 | headers:{
61 | 'Authorization':`Bearer ${Auth.retriveToken()}`
62 | }
63 | })
64 | if(response.data.success) {
65 | dispatch({
66 | type:ActionTypes.GET_TEST_DETAILS_TEACHER,
67 | payload : {
68 | test : response.data.test
69 | }
70 | })
71 | } else {
72 | dispatch({
73 | type:ActionTypes.SET_ALERT,
74 | payload : {
75 | isAlert : true,
76 | title : "Could not get test details",
77 | type : "error",
78 | message : response.data.message
79 | }
80 | })
81 | }
82 | }
83 | }
84 |
85 | export const goBackToAllTest = () => {
86 | return (dispatch) => {
87 | dispatch({
88 | type: ActionTypes.Go_BACK_ALL_TEST_TEACHER,
89 | payload : ''
90 | })
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/templates/loginForm/loginForm.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import TextField from "@material-ui/core/TextField";
3 | import './loginForm.css';
4 | import Button from "@material-ui/core/Button";
5 | import { withStyles } from "@material-ui/core/styles";
6 | import { loginRequestAction } from "../../../redux/actions/loginAction";
7 | import { connect } from "react-redux";
8 |
9 | const useStyles = ()=>({
10 | inputfield : {
11 | display:'block',
12 | margin :'20px'
13 | },
14 | loginbtn : {
15 | margin : '0px 40px'
16 | }
17 | })
18 |
19 | class LoginForm extends React.Component {
20 | constructor(props) {
21 | super(props);
22 | this.state = {
23 | email : "",
24 | password : ""
25 | }
26 | }
27 |
28 | emailInputHandler = (event) => {
29 | this.setState({
30 | ...this.state,
31 | email : event.target.value
32 | });
33 | }
34 |
35 | passwordInputHandler = (event) => {
36 | this.setState({
37 | ...this.state,
38 | password : event.target.value
39 | });
40 | }
41 |
42 | handleSubmit(event) {
43 | event.preventDefault();
44 | this.props.loginRequestAction(this.state);
45 | }
46 |
47 | render() {
48 | return (
49 |
84 | )
85 | }
86 | }
87 |
88 | const mapStatetoProps = state => ({
89 | state : state.user
90 | })
91 |
92 | export default withStyles(useStyles)(connect(mapStatetoProps,{
93 | loginRequestAction
94 | })(LoginForm));
95 |
96 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional stylelint cache
58 | .stylelintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variable files
76 | .env
77 | .env.development.local
78 | .env.test.local
79 | .env.production.local
80 | .env.local
81 |
82 | # parcel-bundler cache (https://parceljs.org/)
83 | .cache
84 | .parcel-cache
85 |
86 | # Next.js build output
87 | .next
88 | out
89 |
90 | # Nuxt.js build / generate output
91 | .nuxt
92 | dist
93 |
94 | # Gatsby files
95 | .cache/
96 | # Comment in the public line in if your project uses Gatsby and not Next.js
97 | # https://nextjs.org/blog/next-9-1#public-directory-support
98 | # public
99 |
100 | # vuepress build output
101 | .vuepress/dist
102 |
103 | # vuepress v2.x temp and cache directory
104 | .temp
105 | .cache
106 |
107 | # Serverless directories
108 | .serverless/
109 |
110 | # FuseBox cache
111 | .fusebox/
112 |
113 | # DynamoDB Local files
114 | .dynamodb/
115 |
116 | # TernJS port file
117 | .tern-port
118 |
119 | # Stores VSCode versions used for testing VSCode extensions
120 | .vscode-test
121 |
122 | # yarn v2
123 | .yarn/cache
124 | .yarn/unplugged
125 | .yarn/build-state.yml
126 | .yarn/install-state.gz
127 | .pnp.*
128 |
129 | database.xlsx
130 | notes.docx
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/molecues/QuestionsTable/QuestionTable.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { withStyles } from "@material-ui/core/styles";
3 | import { connect } from "react-redux";
4 | import { TableBody, TableCell, TableRow, Table, TableHead, TableContainer, Paper, Button } from "@material-ui/core";
5 | import { changeQuestionStatus, searchQuestionById } from "../../../redux/actions/questionAction";
6 |
7 | const useStyles = (theme)=> ({
8 | tableBorder:{
9 | background:'#e7e7e7',
10 | padding:'15px'
11 | },
12 | tableHeader:{
13 | background:'#3f51b5',
14 | color:'white'
15 | }
16 | })
17 |
18 |
19 |
20 | class QuestionTable extends React.Component {
21 | constructor(props){
22 | super(props);
23 | this.state = {}
24 | }
25 |
26 | viewQuestion = (id) => {
27 | this.props.searchQuestionById(id);
28 | }
29 |
30 | onQuestionStatusChange = (event,id,status) => {
31 | this.props.changeQuestionStatus({id,status:(!status)});
32 | }
33 |
34 | render() {
35 |
36 | return(
37 |
38 |
39 |
40 |
41 |
42 | No.
43 | Question
44 | Status
45 | Action
46 |
47 |
48 |
49 | {this.props.questionlist.map((question,index)=>(
50 |
51 | {index+1}
52 | (this.viewQuestion(question._id))}>{question.body}
53 |
56 | {(question.status===true)?'Active':'Blocked'}
57 |
58 |
59 | (this.onQuestionStatusChange(event,question._id,question.status))}
61 | style={(question.status===false)?{background:'#00ff0088'}:{background:'#ff0000aa'}}
62 | >
63 | {(question.status===true)?'block':'unblock'}
64 |
65 |
66 |
67 | ))}
68 |
69 |
70 |
71 |
72 |
73 | )
74 | }
75 | }
76 |
77 | const mapStatetoProps = state => ({
78 | questionlist : state.questionDetails.list
79 | })
80 |
81 | export default withStyles(useStyles)(connect(mapStatetoProps,{
82 | changeQuestionStatus,
83 | searchQuestionById
84 | })(QuestionTable));
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/molecues/TestTable/UpcomingTestTableStudent.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { withStyles } from "@material-ui/styles";
3 | import { connect } from "react-redux";
4 | import { TableBody, TableCell, TableRow, Table, TableHead, TableContainer, Paper, Button } from "@material-ui/core";
5 | import { getDatePretty, getTimePretty } from "../../../helper/common";
6 | import { getTestById } from "../../../redux/actions/studentTestAction";
7 |
8 | const useStyles = (theme)=> ({
9 | tableBorder:{
10 | background:'#e7e7e7',
11 | padding:'15px'
12 | },
13 | tableHeader:{
14 | background:'#3f51b5',
15 | color:'white'
16 | }
17 | })
18 |
19 | class UpcomingTestTableStudent extends React.Component {
20 | constructor(props){
21 | super(props);
22 | this.state = {}
23 | }
24 |
25 | onTestClick(event,id) {
26 | this.props.getTestById({testid:id});
27 | }
28 |
29 | onTestRegister(event,id) {
30 | this.props.studentTestRegister({testid:id});
31 | }
32 |
33 |
34 | render() {
35 | return(
36 |
37 |
38 |
39 |
40 | Test
41 | Status
42 | total marks
43 | Duration (hours)
44 | Test start
45 | Test end
46 | Result
47 | View
48 |
49 |
50 |
51 | {this.props.testlist.map((test,index)=>(
52 |
53 | {test.title}
54 | {test.status}
55 | {test.maxmarks}
56 | {getTimePretty(test.duration)}
57 | {getDatePretty(test.startTime)}
58 | {getDatePretty(test.endTime)}
59 | {getDatePretty(test.resultTime)}
60 | (this.onTestClick(event,test._id))}>View
61 |
62 | ))}
63 |
64 |
65 |
66 |
67 |
)
68 | }
69 | }
70 |
71 | const mapStatetoProps = state => ({
72 | testlist : state.testDetails.list
73 | })
74 |
75 | export default withStyles(useStyles)(connect(mapStatetoProps,{
76 | getTestById
77 | })(UpcomingTestTableStudent));
78 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
13 |
14 | The page will reload when you make changes.\
15 | You may also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!**
35 |
36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
39 |
40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `npm run build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/user-portal-frontend/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
13 |
14 | The page will reload when you make changes.\
15 | You may also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!**
35 |
36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
39 |
40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `npm run build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/frontend/src/components/Dashboard/AddTeacher/AddTeacher.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import { connect } from "react-redux";
3 | import { Link, Navigate } from "react-router-dom";
4 | import Alert from "../../../services/alert";
5 | import Auth from "../../../services/Auth";
6 | import { getAdminDetails } from "../../../redux/actions/loginAction";
7 | import "./AddTeacher.css"
8 | import axios from "axios";
9 | import apis from "../../../services/Apis";
10 |
11 | class AddTeacher extends React.Component {
12 | constructor(props) {
13 | super(props)
14 | this.state = {
15 | name : "",
16 | email : "",
17 | password : "",
18 | confirmpassword : ""
19 | }
20 | }
21 |
22 | nameInputHandler = (event)=>{
23 | this.setState({
24 | ...this.state,
25 | name : event.target.value
26 | });
27 | }
28 |
29 | emailInputHandler = (event)=> {
30 | this.setState({
31 | ...this.state,
32 | email : event.target.value
33 | })
34 | }
35 |
36 | passwordInputHandler = (event)=> {
37 | this.setState({
38 | ...this.state,
39 | password : event.target.value
40 | })
41 | }
42 |
43 | confirmInputHandler = (event)=> {
44 | this.setState({
45 | ...this.state,
46 | confirmpassword : event.target.value
47 | })
48 | }
49 |
50 | handleSubmit= (event) => {
51 | event.preventDefault();
52 | if(this.state.confirmpassword !== this.state.password) {
53 | Alert('error','Invalid Input','Confirm Password does not match')
54 | }
55 | console.log(this.state);
56 | axios.post(apis.BASE + apis.ADD_TEACHER, {
57 | username : this.state.name,
58 | email : this.state.email,
59 | password : this.state.password
60 | },{
61 | headers:{
62 | 'Authorization':`Bearer ${Auth.retriveToken()}`
63 | }
64 | }).then(response => {
65 | console.log(response.data);
66 | if(response.data.success) {
67 | Alert('info','Success',response.data.message);
68 | } else {
69 | Alert('error','Failed',response.data.message);
70 | }
71 | })
72 | }
73 |
74 | render(){
75 | if(!Auth.retriveToken() || Auth.retriveToken()==='undefined'){
76 | return ( );
77 | } else if(!this.props.user.isLoggedIn) {
78 | this.props.getAdminDetails();
79 | return (
)
80 | }
81 | return (
82 |
83 |
105 | )
106 | }
107 | }
108 |
109 | const mapStateToProps = state => ({
110 | user:state.user
111 | });
112 |
113 | export default connect(mapStateToProps,{
114 | getAdminDetails
115 | })(AddTeacher);
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/molecues/TestTable/TestTableStudent.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { withStyles } from "@material-ui/styles";
3 | import { connect } from "react-redux";
4 | import { TableBody, TableCell, TableRow, Table, TableHead, TableContainer, Paper, Button } from "@material-ui/core";
5 | import { studentTestRegister } from "../../../redux/actions/studentTestAction";
6 | import { getDatePretty, getTimePretty } from "../../../helper/common";
7 |
8 |
9 | const useStyles = (theme)=> ({
10 | tableBorder:{
11 | background:'#e7e7e7',
12 | padding:'15px'
13 | },
14 | tableHeader:{
15 | background:'#3f51b5',
16 | color:'white'
17 | }
18 | })
19 |
20 | class TestTableStudent extends React.Component {
21 | constructor(props){
22 | super(props);
23 | this.state = {}
24 | }
25 |
26 | onTestClick(event,id) {
27 | console.log(id);
28 | }
29 |
30 | onTestRegister(event,id) {
31 | this.props.studentTestRegister({testid:id});
32 | }
33 |
34 |
35 | render() {
36 | return(
37 |
38 |
39 |
40 |
41 | Test
42 | Status
43 | total marks
44 | Duration (hours)
45 | Registration start
46 | Registration end
47 | Test start
48 | Test end
49 | Result
50 | Register
51 |
52 |
53 |
54 | {this.props.testlist.map((test,index)=>(
55 |
56 | {test.title}
57 | {test.status}
58 | {test.maxmarks}
59 | {getTimePretty(test.duration)}
60 | {getDatePretty(test.regStartTime)}
61 | {getDatePretty(test.regEndTime)}
62 | {getDatePretty(test.startTime)}
63 | {getDatePretty(test.endTime)}
64 | {getDatePretty(test.resultTime)}
65 | {test.isRegistered===false?(test.status==='REGISTRATION_STARTED'? ((this.onTestRegister(event,test._id))}>
67 | Register
68 | ) : 'not Registered'):'Registered'}
69 |
70 | ))}
71 |
72 |
73 |
74 |
75 |
)
76 | }
77 | }
78 |
79 | const mapStatetoProps = state => ({
80 | testlist : state.testDetails.list
81 | })
82 |
83 | export default withStyles(useStyles)(connect(mapStatetoProps,{
84 | studentTestRegister
85 | })(TestTableStudent));
86 |
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/molecues/ResultView/TestResultStudent.js:
--------------------------------------------------------------------------------
1 | import { withStyles } from "@material-ui/styles";
2 | import React from "react";
3 | import { connect } from "react-redux";
4 | import { Button } from "@material-ui/core";
5 | import { getCompletedTestsStudentAction } from "../../../redux/actions/studentTestAction";
6 | import { TableCell, TableContainer, Table, TableBody, TableRow, Paper } from "@material-ui/core";
7 | import TestResultViewQuestions from "./TestResultViewQuestions";
8 | import { setAlert } from "../../../redux/actions/alertAction";
9 |
10 | const useStyles = (theme)=> ({
11 | tableBorder:{
12 | background:'#e7e7e7',
13 | padding:'15px'
14 | },
15 | tableHeader:{
16 | background:'#3f51b5',
17 | color:'white'
18 | }
19 |
20 | })
21 |
22 | class TestResultStudent extends React.Component {
23 | constructor(props){
24 | super(props);
25 | this.state = {
26 | toggleViewQue : false
27 | }
28 | }
29 |
30 | onViewQuestions(event, result){
31 | if(result.status !== 'RESULT_DECLARED') {
32 | this.props.setAlert({
33 | type : 'info',
34 | title:'No Result',
35 | message : 'test result is not declared'
36 | })
37 | return;
38 | }
39 | this.setState({
40 | ...this.state,
41 | toggleViewQue : !this.state.toggleViewQue
42 | })
43 | }
44 |
45 | goBack() {
46 | this.props.getCompletedTestsStudentAction();
47 | }
48 |
49 | render() {
50 | var test = this.props.test
51 | return (
52 |
53 |
54 |
55 |
56 | Title
57 | {test.title}
58 |
59 |
60 | Student Name
61 | {this.props.user.username}
62 |
63 |
64 | Status
65 | {test.status}
66 |
67 |
68 | Subjects
69 | {test.subjects}
70 |
71 |
72 | Total Marks
73 | {test.maxmarks}
74 |
75 |
76 | Obtained Marks
77 | {test.status === 'RESULT_DECLARED' ? test.score : "Result not declared"}
78 |
79 |
80 | Questions
81 | (this.onViewQuestions(event,test))}>
83 | View
84 |
85 |
86 |
87 |
88 | (this.goBack(event))}>Back
89 |
90 |
91 |
92 |
93 |
94 | { this.state.toggleViewQue === true ?
: ""}
95 |
)
96 | }
97 | }
98 |
99 |
100 | const mapStatetoProps = state => ({
101 | test : state.testDetails.test,
102 | user : state.user.userDetails
103 | })
104 |
105 | export default withStyles(useStyles)(connect(mapStatetoProps,{
106 | getCompletedTestsStudentAction,
107 | setAlert
108 | })(TestResultStudent));
109 |
--------------------------------------------------------------------------------
/frontend/src/components/basic/login/login.css:
--------------------------------------------------------------------------------
1 | html {
2 | height: 100%;
3 | }
4 |
5 | body {
6 | margin: 0;
7 | padding: 0;
8 | font-family: "Arial", sans-serif;
9 | background: linear-gradient(#141e30, #243b55);
10 | }
11 |
12 | .login-box {
13 | position: absolute;
14 | top: 50%;
15 | left: 50%;
16 | width: 400px;
17 | padding: 40px;
18 | transform: translate(-50%, -50%);
19 | background: rgba(0, 0, 0, 0.5);
20 | box-sizing: border-box;
21 | box-shadow: 0 15px 25px rgba(0, 0, 0, 0.6);
22 | border-radius: 10px;
23 | }
24 |
25 | .login-box{
26 | margin: 0 0 30px;
27 | padding: 30px;
28 | color: #f1eee6;
29 | text-align: center;
30 | }
31 |
32 | .login-box-title {
33 | margin-top:30px;
34 | color: #07cfda;
35 | }
36 |
37 | .login-box .user-box {
38 | position: relative;
39 | }
40 |
41 | .login-box .user-box .input-field {
42 | width: 100%;
43 | padding: 10px 0;
44 | font-size: 16px;
45 | color: #f1eee6;
46 | margin-bottom: 30px;
47 | border: none;
48 | border-bottom: 1px solid #f1eee6;
49 | outline: none;
50 | background: transparent;
51 | }
52 |
53 | .login-box .user-box label {
54 | position: absolute;
55 | top: 0;
56 | left: 0;
57 | padding: 10px 0;
58 | font-size: 16px;
59 | color: #f1eee6;
60 | pointer-events: none;
61 | transition: 0.5s;
62 | }
63 |
64 | .login-box .user-box .input-field:focus ~ label,
65 | .login-box .user-box .input-field:valid ~ label {
66 | top: -20px;
67 | left: 0px;
68 | color: #07cfda;
69 | font-size: 12px;
70 | }
71 |
72 | .login-box form .button {
73 | position: relative;
74 | display: inline-block;
75 | padding: 10px 20px;
76 | color: #07cfda;
77 | font-size: 16px;
78 | text-decoration: none;
79 | text-transform: uppercase;
80 | overflow: hidden;
81 | transition: 0.5s;
82 | margin-top: 40px;
83 | letter-spacing: 4px;
84 | }
85 |
86 | .login-box form .button:hover {
87 | background: #07cfdae0;
88 | color: #f1eee6;
89 | border-radius: 5px;
90 | cursor: pointer;
91 | }
92 |
93 | .button {
94 | background-color: transparent;
95 | border: 0;
96 | }
97 |
98 | .login-box .button span {
99 | position: absolute;
100 | display: block;
101 | }
102 |
103 | .login-box .button span:nth-child(1) {
104 | top: 0;
105 | left: -100%;
106 | width: 100%;
107 | height: 2px;
108 | background: linear-gradient(90deg, transparent, #07cfda);
109 | animation: btn-anim1 1s linear infinite;
110 | animation-delay: 0s;
111 | }
112 |
113 | /* top line */
114 | @keyframes btn-anim1 {
115 | 0% {
116 | left: -100%;
117 | }
118 | 50%,
119 | 100% {
120 | left: 100%;
121 | }
122 | }
123 |
124 | .login-box span:nth-child(2) {
125 | top: -100%;
126 | right: 0;
127 | width: 2px;
128 | height: 100%;
129 | background: linear-gradient(180deg, transparent, #07cfda);
130 | animation: btn-anim2 1s linear infinite;
131 | animation-delay: 0.25s;
132 | }
133 |
134 | /* right line */
135 | @keyframes btn-anim2 {
136 | 0% {
137 | top: -100%;
138 | }
139 | 50%,
140 | 100% {
141 | top: 100%;
142 | }
143 | }
144 |
145 | .login-box span:nth-child(3) {
146 | bottom: 0;
147 | right: -100%;
148 | width: 100%;
149 | height: 2px;
150 | background: linear-gradient(270deg, transparent, #07cfda);
151 | animation: btn-anim3 1s linear infinite;
152 | animation-delay: 0.75s;
153 | }
154 |
155 | /* bottom line */
156 | @keyframes btn-anim3 {
157 | 0% {
158 | right: -100%;
159 | }
160 | 50%,
161 | 100% {
162 | right: 100%;
163 | }
164 | }
165 |
166 | .login-box span:nth-child(4) {
167 | bottom: -100%;
168 | left: 0;
169 | width: 2px;
170 | height: 100%;
171 | background: linear-gradient(360deg, transparent, #07cfda);
172 | animation: btn-anim4 1s linear infinite;
173 | animation-delay: 0.5s;
174 | }
175 |
176 | /* left line */
177 | @keyframes btn-anim4 {
178 | 0% {
179 | bottom: -100%;
180 | }
181 | 50%,
182 | 100% {
183 | bottom: 100%;
184 | }
185 | }
186 |
--------------------------------------------------------------------------------
/user-portal-frontend/src/redux/reducers/test.js:
--------------------------------------------------------------------------------
1 | import { ActionTypes } from "../action-types"
2 |
3 |
4 | const initialState = {
5 | list : [],
6 | searched : false,
7 | retrived : false,
8 | upcomingTestRetrived : false,
9 | viewTestRetrived : false,
10 | completedTestRetrived : false,
11 | viewTestResult : false,
12 | test : {}
13 |
14 | }
15 |
16 | export const TestReducer = (state=initialState, {type,payload})=> {
17 | switch(type) {
18 | case ActionTypes.GET_ALL_TESTS:
19 | return {
20 | ...state,
21 | list : payload.testlist,
22 | retrived : true,
23 | upcomingTestRetrived : false,
24 | searched : false,
25 | viewTestRetrived : false,
26 | completedTestRetrived : false,
27 | viewTestResult : false
28 | }
29 | case ActionTypes.CHANGE_TEST_REGISTER:
30 | var newlist = state.list.map((q)=>(q._id===payload.id ?
31 | {
32 | ...q,
33 | isRegistered : true
34 | } :q ));
35 | return {
36 | ...state,
37 | list : newlist,
38 | retrived : true,
39 | upcomingTestRetrived : false,
40 | searched : false,
41 | viewTestRetrived : false,
42 | completedTestRetrived : false,
43 | viewTestResult : false
44 | }
45 | case ActionTypes.GET_UPCOMING_TESTS_STUDENT:
46 | return {
47 | ...state,
48 | list : payload.testlist,
49 | retrived : false,
50 | upcomingTestRetrived : true,
51 | searched : false,
52 | viewTestRetrived : false,
53 | completedTestRetrived : false,
54 | viewTestResult : false
55 | }
56 | case ActionTypes.VIEW_TEST_DETAILS:
57 | return {
58 | ...state,
59 | test : payload.test,
60 | retrived : false,
61 | upcomingTestRetrived : false,
62 | searched : false,
63 | viewTestRetrived : true,
64 | completedTestRetrived : false,
65 | viewTestResult : false
66 | }
67 | case ActionTypes.GET_ALL_COMPLETED_TEST_STUDENT:
68 | return {
69 | ...state,
70 | list : payload.testlist,
71 | retrived : false,
72 | upcomingTestRetrived : false,
73 | searched : false,
74 | viewTestRetrived : false,
75 | completedTestRetrived : true,
76 | viewTestResult : false
77 | }
78 |
79 | case ActionTypes.GET_TEST_RESULT_STUDENT:
80 | return {
81 | ...state,
82 | test : payload.test,
83 | retrived : false,
84 | upcomingTestRetrived : false,
85 | searched : false,
86 | viewTestRetrived : false,
87 | completedTestRetrived : false,
88 | viewTestResult : true
89 | }
90 | case ActionTypes.GET_RESULT_QUESTIONS_STUDENTS:
91 |
92 | var newtest = {
93 | ...state.test,
94 | resultQuestion : payload.questions
95 | }
96 | return {
97 | ...state,
98 | test : newtest,
99 | retrived : false,
100 | upcomingTestRetrived : false,
101 | searched : false,
102 | viewTestRetrived : false,
103 | completedTestRetrived : false,
104 | viewTestResult : true
105 | }
106 | case ActionTypes.GET_TEST_DETAILS_TEACHER:
107 | return {
108 | ...state,
109 | test : payload.test,
110 | retrived : false,
111 | upcomingTestRetrived : false,
112 | searched : true,
113 | viewTestRetrived : false,
114 | completedTestRetrived : false,
115 | viewTestResult : false
116 | }
117 | case ActionTypes.Go_BACK_ALL_TEST_TEACHER:
118 | return {
119 | ...state,
120 | retrived : true,
121 | upcomingTestRetrived : false,
122 | searched : false,
123 | viewTestRetrived : false,
124 | completedTestRetrived : false,
125 | viewTestResult : false
126 | }
127 | case ActionTypes.LOGOUT:
128 | return initialState;
129 |
130 |
131 | default:
132 | return state;
133 | }
134 | }
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/molecues/TestView/TakeTestStudent.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { withStyles } from "@material-ui/styles";
3 | import { connect } from "react-redux";
4 | import { getUpcomingTestsStudentAction } from "../../../redux/actions/studentTestAction";
5 | import { Button } from "@material-ui/core";
6 | import { TableBody, TableCell, TableRow, Table, TableContainer, Paper } from "@material-ui/core";
7 | import { getDatePretty, getTimePretty } from "../../../helper/common";
8 | import { setAlert } from "../../../redux/actions/alertAction";
9 | import { startTestAction } from "../../../redux/actions/takeTestAction";
10 | import { Navigate } from "react-router-dom";
11 |
12 | const useStyles = (theme)=> ({
13 | tableBorder:{
14 | background:'#e7e7e7',
15 | padding:'15px'
16 | },
17 | tableHeader:{
18 | background:'#3f51b5',
19 | color:'white'
20 | }
21 | })
22 |
23 | class TakeTestStudent extends React.Component {
24 | constructor(props){
25 | super(props);
26 | this.state = {}
27 | }
28 |
29 | goBack() {
30 | this.props.getUpcomingTestsStudentAction();
31 | }
32 |
33 | onStartTest(event,test) {
34 | if(test.status==='TEST_STARTED'){
35 | console.log("start test "+test._id);
36 | this.props.startTestAction({testid:test._id},test);
37 |
38 | } else {
39 | this.props.setAlert({
40 | isAlert : true,
41 | type : "info",
42 | title : "Not Started"
43 | })
44 | }
45 |
46 | }
47 |
48 | render() {
49 | if(this.props.isTestStarted) {
50 | console.log('test started');
51 | return ( );
52 | }
53 | var test = this.props.test;
54 | return(
55 |
56 |
57 |
58 |
59 | Title
60 | {test.title}
61 |
62 |
63 | Status
64 | {test.status}
65 |
66 |
67 | Total Marks
68 | {test.maxmarks}
69 |
70 |
71 | Duration
72 | {getTimePretty(test.duration)}
73 |
74 |
75 | Test Start Time
76 | {getDatePretty(test.startTime)}
77 |
78 |
79 | Test End Time
80 | {getDatePretty(test.endTime)}
81 |
82 |
83 | Result Time
84 | {getDatePretty(test.resultTime)}
85 |
86 |
87 | Start Test
88 | (this.onStartTest(event,test))}>
90 | Start Test
91 |
92 |
93 |
94 |
95 | (this.goBack(event))}>Back
96 |
97 |
98 |
99 |
100 |
101 |
102 |
)
103 | }
104 | }
105 |
106 | const mapStatetoProps = state => ({
107 | test : state.testDetails.test,
108 | isTestStarted : state.takeTestDetails.isRetrived
109 | })
110 |
111 | export default withStyles(useStyles)(connect(mapStatetoProps,{
112 | getUpcomingTestsStudentAction,
113 | setAlert,
114 | startTestAction
115 | })(TakeTestStudent))
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/pages/TakeTest/TestPage.js:
--------------------------------------------------------------------------------
1 | import { Button, withStyles } from "@material-ui/core";
2 | import React from "react";
3 | import { connect } from "react-redux";
4 | import { Navigate } from "react-router-dom";
5 | import { AppBar, Toolbar, Typography } from "@material-ui/core";
6 | import Timer from "../../molecues/TestView/Timer";
7 | import QuestionList from "../../molecues/TestView/QuestionList";
8 | import TestQuestion from "../../molecues/TestView/TestQuestion";
9 | import AlertBox from '../../atoms/Alertbox/AlertBox';
10 | import { endTestAction } from "../../../redux/actions/takeTestAction";
11 |
12 |
13 | const useStyles = (theme) => ({
14 | addHeight : theme.mixins.toolbar,
15 | title : {
16 | flexGrow : 1
17 | },
18 | flexdiv : {
19 | display:"flex"
20 | },
21 | quelistdiv : {
22 | width : "18%",
23 | margin : "50px 10px"
24 | },
25 | questiondiv : {
26 | width : "75%",
27 | marginLeft : "50px",
28 | marginTop : "50px"
29 | },
30 | endtestbtn : {
31 | marginLeft : "20px"
32 | },
33 | btns : {
34 | margin : "10px"
35 | }
36 | })
37 |
38 | class TestPage extends React.Component {
39 | constructor(props) {
40 | super(props);
41 | this.state = {
42 | curIndex:0
43 | }
44 | }
45 |
46 | setCurIndex(x, obj) {
47 | console.log("set index");
48 | console.log(obj);
49 | obj.setState({
50 | ...obj.state,
51 | curIndex:x
52 | })
53 | }
54 |
55 | goToPrev() {
56 | if(this.state.curIndex > 0) {
57 | this.setState({
58 | ...this.state,
59 | curIndex: this.state.curIndex-1
60 | })
61 | }
62 | }
63 |
64 | goToNext() {
65 | if(this.state.curIndex + 1 < this.props.taketest.answersheet.answers.length){
66 | this.setState({
67 | ...this.state,
68 | curIndex : this.state.curIndex+1
69 | })
70 | }
71 | }
72 |
73 | endtest() {
74 | this.props.endTestAction();
75 | }
76 |
77 | render() {
78 | if(this.props.taketest.isRetrived === false) {
79 | return( );
80 | }
81 | var timerTime = this.props.taketest.test.duration*1000 - (Date.now()-Date.parse(this.props.taketest.answersheet.startTime));
82 | return(
83 |
84 |
88 |
89 |
90 | {this.props.taketest.test.title}
91 |
92 |
93 | Time Remaining
94 |
95 |
96 |
97 |
98 |
99 | (this.endtest())}>End Test
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 | (this.goToPrev())} className={this.props.classes.btns}>Prev
114 | (this.goToNext())} className={this.props.classes.btns}>Next
115 |
116 |
117 |
118 |
119 |
120 |
)
121 | }
122 | }
123 |
124 |
125 | const mapStatetoProps = state => ({
126 | user : state.user,
127 | taketest : state.takeTestDetails
128 | })
129 | export default withStyles(useStyles)(connect(mapStatetoProps,{
130 | endTestAction
131 | })(TestPage)) ;
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/pages/studentHomepage/studentHomepage.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { connect } from "react-redux";
3 | import LogoutButton from "../../atoms/LogoutButton/LogoutButton";
4 | import Auth from "../../../helper/Auth";
5 | import { Navigate } from "react-router-dom";
6 | import { getUserDetails } from "../../../redux/actions/loginAction";
7 | import { Drawer, Typography, withStyles, AppBar, Toolbar, List, ListItem, ListItemText } from "@material-ui/core";
8 | import AlertBox from '../../atoms/Alertbox/AlertBox';
9 | import TestDetailsStudent from "../../templates/TestDetails/TestDetailsStudent";
10 | import UpcomingStudentTestsDetails from "../../templates/TestDetails/UpcomingStudentTestsDetails";
11 | import CompletedTestsDetailsStudent from "../../templates/TestDetails/CompletedTestsDetailsStudent";
12 |
13 | const drawerWidth = 200
14 | const appbarHeight = 64
15 |
16 | const useStyles = (theme)=>({
17 | drawer : {
18 | width : drawerWidth,
19 | height : `calc(100% - ${appbarHeight}px)`,
20 | top : appbarHeight
21 | },
22 | drawerPaper : {
23 | width : drawerWidth,
24 | height : `calc(100% - ${appbarHeight}px)`,
25 | top : appbarHeight
26 | },
27 | flex : {
28 | display : 'flex'
29 | },
30 | content : {
31 | margin:'auto'
32 | },
33 | addHeight : theme.mixins.toolbar,
34 | title : {
35 | flexGrow : 1
36 | },
37 | appbar : {
38 | height : appbarHeight
39 | }
40 | })
41 |
42 | class StudentHomepage extends React.Component{
43 | constructor(props) {
44 | super(props);
45 | this.state = {
46 | content:(Welcome to Exam portal
),
47 | menuList:[{
48 | title:'Home',
49 | content:(Welcome to Exam portal
)
50 | },{
51 | title : 'View All tests',
52 | content:
53 | },{
54 | title : 'Upcoming Tests',
55 | content:
56 | },{
57 | title : 'Completed Tests',
58 | content :
59 | }]
60 | }
61 | }
62 |
63 | onMenuItemClick(content) {
64 | this.setState({
65 | ...this.state,
66 | content: content
67 | })
68 | }
69 |
70 | render(){
71 | if(!Auth.retriveToken() || Auth.retriveToken()==='undefined'){
72 | return ( );
73 | } else if(!this.props.user.isLoggedIn) {
74 | this.props.getUserDetails();
75 | return (
);
76 | } else if(this.props.user.userDetails.type !== 'STUDENT') {
77 | return ( );
78 | }
79 | return(
80 |
81 |
82 |
86 |
87 |
88 | Student Homepage
89 |
90 |
91 | welcome, {this.props.user.userDetails.username} !!
92 |
93 |
94 |
95 |
96 |
97 |
98 |
104 |
105 | {this.state.menuList.map((item,index)=>(
106 | (this.onMenuItemClick(item.content))}>
107 |
108 |
109 | ))}
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 | {this.state.content}
119 |
120 |
121 |
122 |
123 | )
124 | }
125 | }
126 |
127 | const mapStatetoProps = state => ({
128 | user:state.user
129 | })
130 |
131 | export default withStyles(useStyles)(connect(mapStatetoProps,{
132 | getUserDetails
133 | })(StudentHomepage));
--------------------------------------------------------------------------------
/backend/services/result.js:
--------------------------------------------------------------------------------
1 | const answersheetModel = require("../models/answersheet");
2 | const testModel = require("../models/test");
3 | const subjectModel = require("../models/subject");
4 | const testService = require("./test");
5 |
6 | const getAllCompletedTest = (req,res,next) => {
7 | var creator = req.user || null;
8 | if(creator == null || req.user.usertype != 'STUDENT') {
9 | res.status(401).json({
10 | success : false,
11 | message : "Permissions not granted!"
12 | })
13 | }
14 |
15 | answersheetModel.find({student:creator._id, completed:true},{test:1})
16 | .then(result => {
17 | var testids = result.map(x => (x.test))
18 | testModel.find({_id:{$in:testids}}).sort({resultTime:-1})
19 | .then(tests => {
20 | for(var i in tests) {
21 | var correctStatus = testService.getTestStatus(tests[i]);
22 | if(correctStatus !== tests[i].status) {
23 | testService.updateStatus(tests[i],correctStatus);
24 | tests[i].status = correctStatus;
25 | }
26 | }
27 |
28 | res.json({
29 | success : true,
30 | completedtestlist : tests.map(t => ({
31 | _id: t._id,
32 | title:t.title,
33 | status : t.status,
34 | maxmarks : t.maxmarks,
35 | subjects : t.subjects
36 | }))
37 | })
38 | })
39 | .catch(err => {
40 | console.log(err);
41 | res.json({
42 | success : false,
43 | message : 'Internal server error in test'
44 | })
45 | })
46 | }).catch(err => {
47 | console.log(err);
48 | res.json({
49 | success : false,
50 | message : 'Internal server error in answersheet'
51 | })
52 | })
53 | }
54 |
55 | const getResultMainDetailsByTestId = (req, res, next) => {
56 | var creator = req.user || null;
57 | if(creator == null || req.user.usertype != 'STUDENT') {
58 | res.status(401).json({
59 | success : false,
60 | message : "Permissions not granted!"
61 | })
62 | }
63 |
64 | req.check('testid','Test id not found').notEmpty();
65 |
66 | var errors = req.validationErrors()
67 | if(errors) {
68 | console.log(errors);
69 | res.json({
70 | success:false,
71 | message: 'Invalid inputs',
72 | errors : errors
73 | })
74 | return;
75 | }
76 |
77 | answersheetModel.find({student:creator._id, test:req.body.testid, completed:true})
78 | .then(answersheets => {
79 | if(answersheets[0]) {
80 | testModel.findById({_id: req.body.testid})
81 | .then(test => {
82 | if(test) {
83 | var correctStatus = testService.getTestStatus(test);
84 | if(correctStatus !== test.status) {
85 | testService.updateStatus(test,correctStatus);
86 | test.status = correctStatus;
87 | }
88 | subjectModel.find({_id:{$in:test.subjects}},{name:1})
89 | .then(subjects=> {
90 | subs = subjects.map(sub=>(sub.name))
91 | res.json({
92 | success : true,
93 | result : {
94 | title : test.title,
95 | status : test.status,
96 | maxmarks : test.maxmarks,
97 | subjects : subs,
98 | score : answersheets[0].score,
99 | questions : test.questions,
100 | answers : answersheets[0].answers
101 | }
102 | })
103 | }).catch(err=> {
104 | console.log(err);
105 | res.json({
106 | success : false,
107 | message : 'Internal server error'
108 | })
109 | })
110 | } else {
111 | res.json({
112 | success : false,
113 | message : 'Answer sheet not found'
114 | })
115 | }
116 | }).catch(err=> {
117 | console.log(err);
118 | res.json({
119 | success : false,
120 | message : 'Internal server error'
121 | })
122 | })
123 | } else {
124 | res.json({
125 | success : false,
126 | message : 'Answer sheet not found'
127 | })
128 | }
129 | })
130 | .catch(err=> {
131 | console.log(err);
132 | res.json({
133 | success : false,
134 | message : 'Internal server error'
135 | })
136 | return;
137 | })
138 |
139 | }
140 |
141 | module.exports = {
142 | getAllCompletedTest,
143 | getResultMainDetailsByTestId
144 | }
--------------------------------------------------------------------------------
/frontend/src/components/Dashboard/Main/dashboradMain.js:
--------------------------------------------------------------------------------
1 | import { withStyles} from "@material-ui/core/styles";
2 | import React from "react";
3 | import { connect } from "react-redux";
4 | import { Link, Navigate } from "react-router-dom";
5 | import { logoutUser, getAdminDetails } from "../../../redux/actions/loginAction";
6 | import { getDashboardCount } from "../../../redux/actions/dashboardDetails";
7 | import Auth from "../../../services/Auth";
8 | import { HomepageHeader } from "../../basic/header/header";
9 | import logoImg from '../../basic/Homepage/main.jpg'
10 | import { MainCard } from "../Card/card";
11 | import TeacherImg from '../teacher.png';
12 | import StudentImg from '../student.jfif';
13 | import SubjectImg from '../subject.jfif';
14 | import TeacherTable from "../teacherTable/teacherTable";
15 | import SubjectTable from "../subjectTable/subjectTable";
16 | import StudentTable from "../studentTable/studentTable";
17 |
18 | const useStyles = (theme)=>({
19 | logout_btn : {
20 | marginLeft : '80%'
21 | },
22 | headerMargin : {
23 | marginTop : 80
24 | },
25 | inlineblock : {
26 | display : 'inline-block'
27 | },
28 | linkbtn : {
29 | color:'black'
30 | }
31 | })
32 |
33 | class DashboardMain extends React.Component {
34 | constructor(props) {
35 | super(props)
36 | this.state = {}
37 | this.expand = "none"
38 | }
39 |
40 | logout(obj) {
41 | obj.props.logoutUser();
42 | obj.forceUpdate();
43 | }
44 |
45 | handleTableExapand(type) {
46 | console.log("handle table")
47 | if(type === this.expand) {
48 | this.expand = "none"
49 | } else {
50 | this.expand = type
51 | }
52 | this.forceUpdate();
53 | }
54 |
55 | render(){
56 | console.log(this.props.user);
57 | if(!Auth.retriveToken() || Auth.retriveToken()==='undefined'){
58 | return ( );
59 | } else if(!this.props.user.isLoggedIn) {
60 | this.props.getAdminDetails();
61 | return (
);
62 | } else {
63 | if(!this.props.dashboardDetails.retrived){
64 | this.props.getDashboardCount();
65 | }
66 | let x;
67 | if(this.expand === "Teacher") {
68 | x = ;
69 | } else if (this.expand === "Student") {
70 | x = ;
71 | } else if (this.expand === "Subject") {
72 | x = ;
73 | }
74 | return (
75 |
76 |
77 |
78 | (this.logout(this))} className={this.props.classes.logout_btn} >Logout
79 |
80 |
81 |
82 | Add Teacher
83 |
84 | (this.handleTableExapand("Teacher"))}>Show
85 |
86 |
87 | (this.handleTableExapand("Student"))}>Show
88 |
89 |
90 | Add Subject
91 |
92 | (this.handleTableExapand("Subject"))}>Show
93 |
94 |
95 |
96 | {x}
97 |
98 | );
99 |
100 | }
101 |
102 | }
103 | }
104 |
105 | const mapStateToProps = state => ({
106 | user:state.user,
107 | dashboardDetails:state.dashboardDetails
108 | });
109 |
110 | export default withStyles(useStyles)(connect(mapStateToProps,{
111 | logoutUser,
112 | getAdminDetails,
113 | getDashboardCount,
114 | })(DashboardMain));
115 |
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/pages/teacherHomepage/teacherHomepage.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { connect } from "react-redux";
3 | import LogoutButton from "../../atoms/LogoutButton/LogoutButton";
4 | import Auth from "../../../helper/Auth";
5 | import { Navigate } from "react-router-dom";
6 | import { getUserDetails} from "../../../redux/actions/loginAction";
7 | import AddQuestionForm from "../../templates/AddQuestionForm/AddQuestionForm";
8 | import AlertBox from '../../atoms/Alertbox/AlertBox';
9 | import { Drawer, Typography, withStyles, AppBar, Toolbar, List, ListItem, ListItemText } from "@material-ui/core";
10 | import QuestionDetails from "../../templates/QuestionDetails/questionDetails";
11 | import CreateTestForm from "../../templates/CreateTestForm/CreateTestForm";
12 | import TestDetails from "../../templates/TestDetails/TestDetails";
13 |
14 | const drawerWidth = 200
15 | const appbarHeight = 64
16 |
17 | const useStyles = (theme)=>({
18 | drawer : {
19 | width : drawerWidth,
20 | height : `calc(100% - ${appbarHeight}px)`,
21 | top : appbarHeight
22 | },
23 | drawerPaper : {
24 | width : drawerWidth,
25 | height : `calc(100% - ${appbarHeight}px)`,
26 | top : appbarHeight
27 | },
28 | flex : {
29 | display : 'flex'
30 | },
31 | content : {
32 | margin:'auto'
33 | },
34 | addHeight : theme.mixins.toolbar,
35 | title : {
36 | flexGrow : 1
37 | },
38 | appbar : {
39 | height : appbarHeight
40 | }
41 | })
42 |
43 | class TeacherHomepage extends React.Component{
44 | constructor(props) {
45 | super(props);
46 | this.state = {
47 | content:(Welcome to Exam portal
), // home - welcome message addquestion - add question form
48 | menuList : [{
49 | title:'Home',
50 | content:(Welcome to Exam portal
)
51 | },{
52 | title: 'Add Question',
53 | content:
54 | },{
55 | title: 'Questions',
56 | content:
57 | },{
58 | title: 'Create Test',
59 | content:
60 | },{
61 | title : 'View Tests',
62 | content :
63 | }]
64 | }
65 | }
66 |
67 | onMenuItemClick(content) {
68 | this.setState({
69 | ...this.state,
70 | content: content
71 | })
72 | }
73 |
74 | render(){
75 | if(!Auth.retriveToken() || Auth.retriveToken()==='undefined'){
76 | return ( );
77 | } else if(!this.props.user.isLoggedIn) {
78 | this.props.getUserDetails();
79 | return (
);
80 | } else if(this.props.user.userDetails.type !== 'TEACHER') {
81 | return ( );
82 | }
83 | return(
84 |
85 |
86 |
90 |
91 |
92 | Teacher Homepage
93 |
94 |
95 | welcome, {this.props.user.userDetails.username} !!
96 |
97 |
98 |
99 |
100 |
101 |
102 |
108 |
109 | {this.state.menuList.map((item,index)=>(
110 | (this.onMenuItemClick(item.content))}>
111 |
112 |
113 | ))}
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | {this.state.content}
123 |
124 |
125 |
126 |
127 |
128 |
129 | )
130 | }
131 | }
132 |
133 | const mapStatetoProps = state => ({
134 | user:state.user
135 | })
136 |
137 | export default withStyles(useStyles) (connect(mapStatetoProps,{
138 | getUserDetails
139 | })(TeacherHomepage));
--------------------------------------------------------------------------------
/user-portal-frontend/src/redux/actions/takeTestAction.js:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 | import apis from "../../helper/Apis"
3 | import Auth from "../../helper/Auth"
4 | import { ActionTypes } from "../action-types"
5 | import { Navigate } from "react-router-dom";
6 | import store from "../store";
7 |
8 | export const startTestAction = (details, test) => {
9 |
10 | return async(dispatch)=> {
11 | const response = await axios.post(apis.BASE + apis.START_TEST, details, {
12 | headers : {
13 | 'Authorization' : `Bearer ${Auth.retriveToken()}`
14 | }
15 | }).catch(err => {
16 | console.log(err);
17 | return;
18 | })
19 | if(response.data.success) {
20 | console.log(response.data);
21 | var addStartTime = (response.data.answersheet.startTime === undefined)
22 | const queResponse = await axios.post(apis.BASE + apis.GET_QUESTIONS_TAKETEST,
23 | {
24 | answersheetid:response.data.answersheet._id,
25 | addStartTime:addStartTime,
26 | questionid:response.data.questions
27 | }, {
28 | headers : {
29 | 'Authorization' : `Bearer ${Auth.retriveToken()}`
30 | }
31 | }).catch(err => {
32 | console.log(err);
33 | return;
34 | })
35 | if(queResponse.data.success) {
36 | if(response.data.answersheet.answers.length > 0) {
37 | dispatch({
38 | type: ActionTypes.START_TEST,
39 | payload : {
40 | test : test,
41 | answersheet : {...response.data.answersheet,startTime:queResponse.data.startTime},
42 | questions : queResponse.data.questions
43 | }
44 | })
45 | }
46 | else {
47 | var emptyAns = new Array(response.data.questions.length).fill(null);
48 | dispatch({
49 | type: ActionTypes.START_TEST,
50 | payload : {
51 | test : test,
52 | answersheet : {...response.data.answersheet,startTime:queResponse.data.startTime, answers:emptyAns},
53 | questions : queResponse.data.questions
54 | }
55 | })
56 | }
57 | } else {
58 | dispatch({
59 | type:ActionTypes.SET_ALERT,
60 | payload : {
61 | isAlert : true,
62 | title : "Start Test Error",
63 | type : "error",
64 | message : queResponse.data.message
65 | }
66 | })
67 | }
68 | }
69 | else {
70 | dispatch({
71 | type:ActionTypes.SET_ALERT,
72 | payload : {
73 | isAlert : true,
74 | title : "Start Test Error",
75 | type : "error",
76 | message : response.data.message
77 | }
78 | })
79 | }
80 | }
81 | }
82 |
83 | export const selectedOptionAction = (details) => {
84 | return async(dispatch)=>{
85 | dispatch({
86 | type:ActionTypes.SELECT_ANSWER,
87 | payload : details
88 | })
89 | }
90 | }
91 |
92 | export const saveAnswerAction = () => {
93 | return async(dispatch)=> {
94 | console.log(store.getState());
95 | var answersheet = store.getState().takeTestDetails.answersheet;
96 | console.log(answersheet.answers);
97 | axios.post(apis.BASE + apis.SAVE_ANSWER,{answersheetid:answersheet._id,answers: answersheet.answers},{
98 | headers : {
99 | 'Authorization' : `Bearer ${Auth.retriveToken()}`
100 | }
101 | }).then(response=> {
102 | console.log(response);
103 | if(response.data.success) {
104 | if(response.data.testDone) {
105 | dispatch({
106 | type : ActionTypes.END_TEST
107 | })
108 | }
109 | console.log("saved answer");
110 | } else {
111 | console.log(response.data);
112 | }
113 | }).catch(err => {
114 | console.log(err);
115 | return;
116 | })
117 | }
118 | }
119 |
120 | export const endTestAction = () => {
121 | return async(dispatch)=> {
122 | console.log(store.getState());
123 | var answersheet = store.getState().takeTestDetails.answersheet;
124 | console.log(answersheet.answers);
125 | axios.post(apis.BASE + apis.END_TEST,{answersheetid:answersheet._id,answers: answersheet.answers},{
126 | headers : {
127 | 'Authorization' : `Bearer ${Auth.retriveToken()}`
128 | }
129 | }).then(response=> {
130 | console.log(response);
131 | if(response.data.success) {
132 | dispatch({
133 | type : ActionTypes.END_TEST
134 | })
135 | return ( )
136 | }
137 | }).catch(err => {
138 | console.log(err);
139 | return;
140 | })
141 | }
142 | }
--------------------------------------------------------------------------------
/user-portal-frontend/src/components/templates/studentRegisterForm/studentRegisterForm.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import TextField from "@material-ui/core/TextField";
3 | import Button from "@material-ui/core/Button";
4 | import { withStyles } from "@material-ui/core/styles";
5 | import { registerStudentAction } from "../../../redux/actions/registerStudentAction";
6 | import { connect } from "react-redux";
7 | import { setAlert } from "../../../redux/actions/alertAction";
8 |
9 | const useStyles = ()=>({
10 | inputfield : {
11 | display:'block',
12 | margin :'20px'
13 | },
14 | btn : {
15 | margin : '0px 40px'
16 | },
17 | formClass : {
18 | margin:'20px',
19 | display: 'inline-block',
20 | textAlign : 'center',
21 | border : '1px solid black',
22 | borderRadius: '10px',
23 | padding : '20px'
24 | },
25 |
26 | formTitle:{
27 | fontSize: '1.7em'
28 | }
29 | })
30 |
31 | class StudentRegisterForm extends React.Component {
32 | constructor(props) {
33 | super(props);
34 | this.state = {
35 | username : "",
36 | email : "",
37 | password : "",
38 | confirmPassword : ""
39 | }
40 | }
41 |
42 | usernameInputHandler = (event) => {
43 | this.setState({
44 | ...this.state,
45 | username : event.target.value
46 | });
47 | }
48 |
49 | emailInputHandler = (event) => {
50 | this.setState({
51 | ...this.state,
52 | email : event.target.value
53 | });
54 | }
55 |
56 | passwordInputHandler = (event) => {
57 | this.setState({
58 | ...this.state,
59 | password : event.target.value
60 | });
61 | }
62 |
63 | confirmpasswordInputHandler = (event) => {
64 | this.setState({
65 | ...this.state,
66 | confirmPassword : event.target.value
67 | });
68 | }
69 |
70 | handleSubmit(event) {
71 | event.preventDefault();
72 | if(this.state.confirmPassword !== this.state.password) {
73 | this.props.setAlert({
74 | isAlert:false,
75 | type:"error",
76 | title:'Invalid Input',
77 | message : 'Confirm Password does not match',
78 | })
79 | return;
80 | }
81 | this.props.registerStudentAction({
82 | username : this.state.username,
83 | email : this.state.email,
84 | password : this.state.password
85 | });
86 | }
87 |
88 | render() {
89 | return (
90 |
149 | )
150 | }
151 | }
152 |
153 | const mapStatetoProps = state => ({
154 |
155 | })
156 |
157 | export default withStyles(useStyles)(connect(mapStatetoProps,{
158 | registerStudentAction,
159 | setAlert
160 | })(StudentRegisterForm));
--------------------------------------------------------------------------------
/backend/services/passportconf.js:
--------------------------------------------------------------------------------
1 | var passport = require('passport');
2 | var LocalStrategy = require('passport-local').Strategy;
3 | var JwtStrategy = require('passport-jwt').Strategy;
4 | var ExtractJwt = require('passport-jwt').ExtractJwt;
5 |
6 | const bcrypt = require('bcrypt');
7 | const saltRounds = 10;
8 |
9 | var config = require('config');
10 |
11 | var userModel = require('../models/user');
12 | var adminModel = require('../models/admin');
13 |
14 | var localStrategyOption = {
15 | usernameField: 'email',
16 | passwordField : 'password',
17 | passReqToCallback : true
18 | }
19 |
20 | function localStrategyVerify(req,email, password, done){
21 | userModel.findOne({'email':email}, (err, user)=>{
22 | // database server error
23 | if(err) {
24 | return done(err, false, {
25 | success : false,
26 | message : 'server error'
27 | });
28 | }
29 |
30 | // user not found
31 | if(!user) {
32 | return done(null, false, {
33 | success : false,
34 | message : 'email is not registered'
35 | })
36 | } else if (user.status == false) {
37 | return done(null, false, {
38 | success : false,
39 | message : 'your account is blocked'
40 | })
41 | }
42 | else {
43 | //check for password
44 | bcrypt.compare(password, user.password)
45 | .then( (result) => {
46 | if(result) {
47 | return done(null, user, {
48 | success : true,
49 | message : 'logged in successfully'
50 | });
51 | } else {
52 | return done(null, false, {
53 | success : false,
54 | message : 'invalid password'
55 | });
56 | }
57 | })
58 | }
59 |
60 | })
61 | }
62 |
63 | var localStrategy = new LocalStrategy(localStrategyOption, localStrategyVerify);
64 |
65 | passport.use('login',localStrategy);
66 |
67 |
68 | var jwt_options = {
69 | jwtFromRequest : ExtractJwt.fromAuthHeaderAsBearerToken(),
70 | secretOrKey : config.jwt.secret
71 | }
72 |
73 | function jwtStrategyVerify(jwt_payload, done) {
74 | userModel.findById(jwt_payload._id, (err, user)=> {
75 | // database server error
76 | if(err) {
77 | return done(err, false, {
78 | success : false,
79 | message : 'server error'
80 | });
81 | }
82 | if (user) {
83 | return done(null, user,{
84 | success: true,
85 | message: "Successful"
86 | });
87 | }
88 | else {
89 | return done(null, false,{
90 | success: false,
91 | message: "Authorization Failed"
92 | });
93 | }
94 | });
95 | }
96 |
97 | var jwtStrategy = new JwtStrategy(jwt_options, jwtStrategyVerify);
98 |
99 | passport.use('user-token',jwtStrategy);
100 |
101 | var localStrategyOptionAdmin = {
102 | usernameField : 'username',
103 | passwordField : 'password',
104 | passReqToCallback : true
105 | }
106 |
107 | function localStrategyVerifyAdmin(req, username, password, done) {
108 | adminModel.findOne({'username':username}, (err, admin)=> {
109 | // database server error
110 | if(err) {
111 | return done(err, false, {
112 | success : false,
113 | message : 'server error'
114 | })
115 | }
116 |
117 | //admin not found
118 | if(!admin) {
119 | return done(null, false, {
120 | success : false,
121 | message : 'user not found'
122 | })
123 | } else {
124 | //check of password
125 | bcrypt.compare(password, admin.password)
126 | .then((result)=>{
127 | if(result) {
128 | return done(null, admin, {
129 | success : true,
130 | message : 'logged in successfully'
131 | })
132 | }
133 | else {
134 | return done(null, false, {
135 | success : false,
136 | message : 'invalid password'
137 | })
138 | }
139 | })
140 | }
141 | })
142 | }
143 |
144 | var localStrategyAdmin = new LocalStrategy(localStrategyOptionAdmin, localStrategyVerifyAdmin);
145 |
146 | passport.use('admin-login',localStrategyAdmin);
147 |
148 | function jwtStrategyVeriryAdmin(jwt_payload, done) {
149 | adminModel.findById(jwt_payload._id, (err, admin)=>{
150 | //database server error
151 | if(err) {
152 | return done(err, false, {
153 | success : false,
154 | message : 'server error'
155 | })
156 | }
157 |
158 | if (admin) {
159 | return done(null, admin, {
160 | success : true,
161 | message : 'successful'
162 | })
163 | } else {
164 | return done(null, false, {
165 | success : false,
166 | message : 'Authorization failed'
167 | })
168 | }
169 | })
170 | }
171 |
172 | var jwtStrategyAdmin = new JwtStrategy(jwt_options, jwtStrategyVeriryAdmin);
173 |
174 | passport.use('admin-token', jwtStrategyAdmin);
175 |
176 | module.exports = passport;
--------------------------------------------------------------------------------
/user-portal-frontend/src/redux/actions/studentTestAction.js:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 | import apis from "../../helper/Apis"
3 | import Auth from "../../helper/Auth"
4 | import { ActionTypes } from "../action-types"
5 |
6 |
7 | export const getAllTestStudentAction = () => {
8 | return async(dispatch) => {
9 | const response = await axios.get(apis.BASE + apis.GET_ALL_TEST_STUDNET,{
10 | headers:{
11 | 'Authorization':`Bearer ${Auth.retriveToken()}`
12 | }
13 | });
14 | console.log(response.data);
15 | if(response.data.success) {
16 |
17 | dispatch({
18 | type : ActionTypes.GET_ALL_TESTS,
19 | payload : {
20 | testlist : response.data.testlist
21 | }
22 | })
23 | }
24 | }
25 | }
26 |
27 | export const getUpcomingTestsStudentAction = () => {
28 | return async(dispatch) => {
29 | const response = await axios.get(apis.BASE + apis.GET_UPCOMING_TESTS_STUDENT,{
30 | headers:{
31 | 'Authorization':`Bearer ${Auth.retriveToken()}`
32 | }
33 | });
34 | if(response.data.success) {
35 |
36 | dispatch({
37 | type : ActionTypes.GET_UPCOMING_TESTS_STUDENT,
38 | payload : {
39 | testlist : response.data.upcomingtestlist
40 | }
41 | })
42 | }
43 | }
44 | }
45 |
46 | export const getCompletedTestsStudentAction = () => {
47 | return async(dispatch) => {
48 | const response = await axios.get(apis.BASE + apis.GET_ALL_COMPLETED_TEST_STUDENT,{
49 | headers:{
50 | 'Authorization':`Bearer ${Auth.retriveToken()}`
51 | }
52 | });
53 | if(response.data.success) {
54 | dispatch({
55 | type : ActionTypes.GET_ALL_COMPLETED_TEST_STUDENT,
56 | payload : {
57 | testlist : response.data.completedtestlist
58 | }
59 | })
60 | }
61 | }
62 | }
63 |
64 | export const studentTestRegister = (details) => {
65 | return async(dispatch)=>{
66 | axios.post(apis.BASE + apis.STUDENT_TEST_REGISTER,details, {
67 | headers : {
68 | 'Authorization':`Bearer ${Auth.retriveToken()}`
69 | }
70 | }).then(response => {
71 | if(response.data.success) {
72 | dispatch({
73 | type : ActionTypes.CHANGE_TEST_REGISTER,
74 | payload : {
75 | id: details.testid
76 | }
77 | })
78 | } else {
79 | console.error(response.data);
80 | dispatch({
81 | type:ActionTypes.SET_ALERT,
82 | payload : {
83 | isAlert:true,
84 | type : 'error',
85 | title : 'error in change status',
86 | message : response.data.message
87 | }
88 | })
89 | }
90 | })
91 | }
92 | }
93 |
94 | export const getTestById = (details) => {
95 | return async(dispatch) => {
96 | const response = await axios.post(apis.BASE+ apis.GET_TEST_DETAILS_BY_ID,details,{
97 | headers:{
98 | 'Authorization':`Bearer ${Auth.retriveToken()}`
99 | }
100 | })
101 | if(response.data.success) {
102 | dispatch({
103 | type:ActionTypes.VIEW_TEST_DETAILS,
104 | payload : {
105 | test : response.data.test
106 | }
107 | })
108 | } else {
109 | dispatch({
110 | type:ActionTypes.SET_ALERT,
111 | payload : {
112 | isAlert : true,
113 | title : "Could not get test details",
114 | type : "error",
115 | message : response.data.message
116 | }
117 | })
118 | }
119 | }
120 | }
121 |
122 | export const getTestResultStudent = (details) => {
123 |
124 | return async(dispatch)=> {
125 | var response = await axios.post(apis.BASE + apis.GET_TEST_RESULT_STUDENT,details,{
126 | headers:{
127 | 'Authorization':`Bearer ${Auth.retriveToken()}`
128 | }
129 | })
130 | if(response.data.success){
131 | dispatch({
132 | type : ActionTypes.GET_TEST_RESULT_STUDENT,
133 | payload : {
134 | test : response.data.result
135 | }
136 | })
137 | } else {
138 | dispatch({
139 | type : ActionTypes.SET_ALERT,
140 | payload : {
141 | isAlert : true,
142 | type : 'error',
143 | title : 'Error',
144 | message : response.data.message
145 | }
146 | })
147 | }
148 |
149 | }
150 | }
151 |
152 |
153 | export const getQuestionAnswerActionStudent = (details) => {
154 | return async(dispatch) => {
155 | var response = await axios.post(apis.BASE + apis.GET_RESULT_QUESTIONS_STUDENTS, details, {
156 | headers:{
157 | 'Authorization':`Bearer ${Auth.retriveToken()}`
158 | }
159 | })
160 | console.log(response.data);
161 | if(response.data.success) {
162 | dispatch({
163 | type : ActionTypes.GET_RESULT_QUESTIONS_STUDENTS,
164 | payload : {
165 | questions : response.data.questions
166 | }
167 | })
168 | } else {
169 | dispatch({
170 | type : ActionTypes.SET_ALERT,
171 | payload : {
172 | isAlert : true,
173 | type : 'error',
174 | title : 'Error',
175 | message : response.data.message
176 | }
177 | })
178 | }
179 | }
180 | }
--------------------------------------------------------------------------------