Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
H
Hire-Guru
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vigneswaran Shanmugam
Hire-Guru
Commits
d2648061
Commit
d2648061
authored
Apr 15, 2026
by
AravindR-K
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fixed quiz genreation bugs + editing bugs
parent
1d54e11a
Changes
14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
885 additions
and
759 deletions
+885
-759
Backend/.env
Backend/.env
+1
-1
Backend/models/Quiz.js
Backend/models/Quiz.js
+3
-3
Backend/package.json
Backend/package.json
+1
-1
Backend/routes/admin.js
Backend/routes/admin.js
+532
-488
Backend/routes/auth.js
Backend/routes/auth.js
+1
-1
Backend/server.js
Backend/server.js
+77
-77
Frontend/src/app/interceptors/auth.interceptor.ts
Frontend/src/app/interceptors/auth.interceptor.ts
+1
-1
Frontend/src/app/pages/admin/edit-quiz/edit-quiz.ts
Frontend/src/app/pages/admin/edit-quiz/edit-quiz.ts
+42
-23
Frontend/src/app/pages/admin/generate-quiz/generate-quiz.html
...tend/src/app/pages/admin/generate-quiz/generate-quiz.html
+64
-36
Frontend/src/app/pages/admin/generate-quiz/generate-quiz.ts
Frontend/src/app/pages/admin/generate-quiz/generate-quiz.ts
+25
-5
Frontend/src/app/pages/admin/user-history/user-history.css
Frontend/src/app/pages/admin/user-history/user-history.css
+1
-1
Frontend/src/app/services/auth.service.ts
Frontend/src/app/services/auth.service.ts
+8
-8
Frontend/src/app/services/quiz.service.ts
Frontend/src/app/services/quiz.service.ts
+114
-114
Frontend/src/app/services/theme.service.ts
Frontend/src/app/services/theme.service.ts
+15
-0
No files found.
Backend/.env
View file @
d2648061
...
@@ -2,4 +2,4 @@ PORT=5000
...
@@ -2,4 +2,4 @@ PORT=5000
#MONGODB_URI=mongodb://localhost:27017/quiz_app
#MONGODB_URI=mongodb://localhost:27017/quiz_app
MONGODB_URI=mongodb://127.0.0.1:27017/quiz_app
MONGODB_URI=mongodb://127.0.0.1:27017/quiz_app
JWT_SECRET=quiz_app_super_secret_key_2026
JWT_SECRET=quiz_app_super_secret_key_2026
JWT_EXPIRES_IN=
7
d
JWT_EXPIRES_IN=
1
d
Backend/models/Quiz.js
View file @
d2648061
...
@@ -43,10 +43,10 @@ const quizSchema = new mongoose.Schema({
...
@@ -43,10 +43,10 @@ const quizSchema = new mongoose.Schema({
}],
}],
difficulty
:
{
difficulty
:
{
type
:
String
,
type
:
String
,
enum
:
[
'
Beginner
'
,
'
Intermediate
'
,
'
Advance
d
'
],
enum
:
[
'
easy
'
,
'
medium
'
,
'
har
d
'
],
default
:
'
Intermediate
'
default
:
'
medium
'
},
},
topic
:
{
topic
:
{
type
:
String
,
type
:
String
,
trim
:
true
trim
:
true
},
},
...
...
Backend/package.json
View file @
d2648061
...
@@ -4,7 +4,7 @@
...
@@ -4,7 +4,7 @@
"main"
:
"server.js"
,
"main"
:
"server.js"
,
"scripts"
:
{
"scripts"
:
{
"start"
:
"node server.js"
,
"start"
:
"node server.js"
,
"dev"
:
"nodemon server.js"
,
"dev"
:
"nodemon
--ignore uploads/
server.js"
,
"seed"
:
"node seedAdmin.js"
,
"seed"
:
"node seedAdmin.js"
,
"test"
:
"echo
\"
Error: no test specified
\"
&& exit 1"
"test"
:
"echo
\"
Error: no test specified
\"
&& exit 1"
},
},
...
...
Backend/routes/admin.js
View file @
d2648061
const
express
=
require
(
'
express
'
);
const
express
=
require
(
'
express
'
);
const
xlsx
=
require
(
'
xlsx
'
);
const
xlsx
=
require
(
'
xlsx
'
);
const
fs
=
require
(
'
fs
'
);
const
fs
=
require
(
'
fs
'
);
const
User
=
require
(
'
../models/User
'
);
const
User
=
require
(
'
../models/User
'
);
const
Quiz
=
require
(
'
../models/Quiz
'
);
const
Quiz
=
require
(
'
../models/Quiz
'
);
const
Question
=
require
(
'
../models/Question
'
);
const
Question
=
require
(
'
../models/Question
'
);
const
Submission
=
require
(
'
../models/Submission
'
);
const
Submission
=
require
(
'
../models/Submission
'
);
const
{
protect
,
authorize
}
=
require
(
'
../middleware/auth
'
);
const
{
protect
,
authorize
}
=
require
(
'
../middleware/auth
'
);
const
upload
=
require
(
'
../middleware/upload
'
);
const
upload
=
require
(
'
../middleware/upload
'
);
const
router
=
express
.
Router
();
const
router
=
express
.
Router
();
// All admin routes require authentication + admin role
// All admin routes require authentication + admin role
router
.
use
(
protect
,
authorize
(
'
admin
'
));
router
.
use
(
protect
,
authorize
(
'
admin
'
));
// ============ USER MANAGEMENT ============
// ============ USER MANAGEMENT ============
// @route GET /api/admin/users
// @route GET /api/admin/users
// @desc Get all candidates and HR users
// @desc Get all candidates and HR users
// @access Admin
// @access Admin
router
.
get
(
'
/users
'
,
async
(
req
,
res
)
=>
{
router
.
get
(
'
/users
'
,
async
(
req
,
res
)
=>
{
try
{
try
{
const
{
role
}
=
req
.
query
;
const
{
role
}
=
req
.
query
;
const
filter
=
role
?
{
role
}
:
{
role
:
{
$in
:
[
'
candidate
'
,
'
hr
'
]
}
};
const
filter
=
role
?
{
role
}
:
{
role
:
{
$in
:
[
'
candidate
'
,
'
hr
'
]
}
};
const
users
=
await
User
.
find
(
filter
)
const
users
=
await
User
.
find
(
filter
)
.
select
(
'
-password
'
)
.
select
(
'
-password
'
)
.
sort
({
createdAt
:
-
1
});
.
sort
({
createdAt
:
-
1
});
res
.
json
({
users
});
res
.
json
({
users
});
}
catch
(
error
)
{
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
}
});
});
// @route GET /api/admin/users/logged-in
// @desc Get all currently logged-in users
// @access Admin
router
.
get
(
'
/users/logged-in
'
,
async
(
req
,
res
)
=>
{
try
{
const
users
=
await
User
.
find
({
role
:
{
$in
:
[
'
candidate
'
,
'
hr
'
]
},
isLoggedIn
:
true
})
.
select
(
'
-password
'
)
.
sort
({
createdAt
:
-
1
});
res
.
json
({
users
});
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
});
// @route POST /api/admin/users/create-hr
// @route GET /api/admin/users/logged-in
// @desc Create an HR user
// @desc Get all currently logged-in users
// @access Admin
// @access Admin
router
.
post
(
'
/users/create-hr
'
,
async
(
req
,
res
)
=>
{
router
.
get
(
'
/users/logged-in
'
,
async
(
req
,
res
)
=>
{
try
{
try
{
const
{
name
,
email
,
password
}
=
req
.
body
;
const
users
=
await
User
.
find
({
role
:
{
$in
:
[
'
candidate
'
,
'
hr
'
]
},
isLoggedIn
:
true
})
.
select
(
'
-password
'
)
.
sort
({
createdAt
:
-
1
});
res
.
json
({
users
});
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
});
// @route POST /api/admin/users/create-hr
// @desc Create an HR user
// @access Admin
router
.
post
(
'
/users/create-hr
'
,
async
(
req
,
res
)
=>
{
try
{
const
{
name
,
email
,
password
}
=
req
.
body
;
if
(
!
name
||
!
email
||
!
password
)
{
return
res
.
status
(
400
).
json
({
message
:
'
Please provide name, email, and password
'
});
}
if
(
!
name
||
!
email
||
!
password
)
{
const
existingUser
=
await
User
.
findOne
({
email
});
return
res
.
status
(
400
).
json
({
message
:
'
Please provide name, email, and password
'
});
if
(
existingUser
)
{
return
res
.
status
(
400
).
json
({
message
:
'
User with this email already exists
'
});
}
const
user
=
await
User
.
create
({
name
,
email
,
password
,
role
:
'
hr
'
});
res
.
status
(
201
).
json
({
message
:
'
HR user created successfully
'
,
user
:
{
id
:
user
.
_id
,
name
:
user
.
name
,
email
:
user
.
email
,
role
:
user
.
role
}
});
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
}
});
const
existingUser
=
await
User
.
findOne
({
email
});
// @route DELETE /api/admin/users/:userId
if
(
existingUser
)
{
// @desc Delete a user
return
res
.
status
(
400
).
json
({
message
:
'
User with this email already exists
'
});
// @access Admin
router
.
delete
(
'
/users/:userId
'
,
async
(
req
,
res
)
=>
{
try
{
const
{
userId
}
=
req
.
params
;
const
user
=
await
User
.
findById
(
userId
);
if
(
!
user
)
return
res
.
status
(
404
).
json
({
message
:
'
User not found
'
});
if
(
user
.
role
===
'
admin
'
)
return
res
.
status
(
403
).
json
({
message
:
'
Cannot delete admin users
'
});
await
Submission
.
deleteMany
({
studentId
:
userId
});
await
User
.
findByIdAndDelete
(
userId
);
res
.
json
({
message
:
'
User deleted successfully
'
});
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
}
});
const
user
=
await
User
.
create
({
name
,
email
,
password
,
role
:
'
hr
'
});
// @route GET /api/admin/users/:userId/history
// @desc Get a user's test history
// @access Admin
router
.
get
(
'
/users/:userId/history
'
,
async
(
req
,
res
)
=>
{
try
{
const
{
userId
}
=
req
.
params
;
res
.
status
(
201
).
json
({
const
user
=
await
User
.
findById
(
userId
).
select
(
'
-password
'
);
message
:
'
HR user created successfully
'
,
if
(
!
user
)
{
user
:
{
return
res
.
status
(
404
).
json
({
message
:
'
User not found
'
});
id
:
user
.
_id
,
name
:
user
.
name
,
email
:
user
.
email
,
role
:
user
.
role
}
}
});
}
catch
(
error
)
{
const
submissions
=
await
Submission
.
find
({
studentId
:
userId
})
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
.
populate
(
'
quizId
'
,
'
title timer totalQuestions category
'
)
}
.
sort
({
submittedAt
:
-
1
});
});
res
.
json
({
user
,
submissions
});
// @route DELETE /api/admin/users/:userId
}
catch
(
error
)
{
// @desc Delete a user
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
// @access Admin
router
.
delete
(
'
/users/:userId
'
,
async
(
req
,
res
)
=>
{
try
{
const
{
userId
}
=
req
.
params
;
const
user
=
await
User
.
findById
(
userId
);
if
(
!
user
)
return
res
.
status
(
404
).
json
({
message
:
'
User not found
'
});
if
(
user
.
role
===
'
admin
'
)
return
res
.
status
(
403
).
json
({
message
:
'
Cannot delete admin users
'
});
await
Submission
.
deleteMany
({
studentId
:
userId
});
await
User
.
findByIdAndDelete
(
userId
);
res
.
json
({
message
:
'
User deleted successfully
'
});
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
});
// @route GET /api/admin/users/:userId/history
// @desc Get a user's test history
// @access Admin
router
.
get
(
'
/users/:userId/history
'
,
async
(
req
,
res
)
=>
{
try
{
const
{
userId
}
=
req
.
params
;
const
user
=
await
User
.
findById
(
userId
).
select
(
'
-password
'
);
if
(
!
user
)
{
return
res
.
status
(
404
).
json
({
message
:
'
User not found
'
});
}
}
});
const
submissions
=
await
Submission
.
find
({
studentId
:
userId
})
// @route GET /api/admin/submissions/:submissionId
.
populate
(
'
quizId
'
,
'
title timer totalQuestions category
'
)
// @desc Get detailed submission - answers, correct answers, scores
.
sort
({
submittedAt
:
-
1
});
// @access Admin
router
.
get
(
'
/submissions/:submissionId
'
,
async
(
req
,
res
)
=>
{
try
{
const
{
submissionId
}
=
req
.
params
;
res
.
json
({
user
,
submissions
});
const
submission
=
await
Submission
.
findById
(
submissionId
)
}
catch
(
error
)
{
.
populate
(
'
studentId
'
,
'
name email
'
)
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
.
populate
(
'
quizId
'
,
'
title timer totalQuestions
'
);
}
});
// @route GET /api/admin/submissions/:submissionId
if
(
!
submission
)
{
// @desc Get detailed submission - answers, correct answers, scores
return
res
.
status
(
404
).
json
({
message
:
'
Submission not found
'
});
// @access Admin
}
router
.
get
(
'
/submissions/:submissionId
'
,
async
(
req
,
res
)
=>
{
try
{
const
{
submissionId
}
=
req
.
params
;
const
submission
=
await
Submission
.
findById
(
submissionId
)
// Get all questions for this quiz
.
populate
(
'
studentId
'
,
'
name email
'
)
const
questions
=
await
Question
.
find
({
quizId
:
submission
.
quizId
.
_id
});
.
populate
(
'
quizId
'
,
'
title timer totalQuestions
'
);
// Build detailed result with question, student answer, correct answer
const
detailedAnswers
=
questions
.
map
(
q
=>
{
const
studentAnswer
=
submission
.
answers
.
find
(
a
=>
a
.
questionId
.
toString
()
===
q
.
_id
.
toString
()
);
return
{
questionId
:
q
.
_id
,
question
:
q
.
question
,
options
:
q
.
options
,
type
:
q
.
type
,
correctAnswers
:
q
.
correctAnswers
,
studentAnswers
:
studentAnswer
?
studentAnswer
.
selectedAnswers
:
[],
isCorrect
:
(
studentAnswer
&&
studentAnswer
.
selectedAnswers
)
?
checkAnswersMatch
(
studentAnswer
.
selectedAnswers
,
q
.
correctAnswers
)
:
false
};
});
if
(
!
submission
)
{
res
.
json
({
return
res
.
status
(
404
).
json
({
message
:
'
Submission not found
'
});
submission
:
{
id
:
submission
.
_id
,
student
:
submission
.
studentId
,
quiz
:
submission
.
quizId
,
score
:
submission
.
score
,
totalMarks
:
submission
.
totalMarks
,
percentage
:
submission
.
percentage
,
timeTaken
:
submission
.
timeTaken
,
submittedAt
:
submission
.
submittedAt
},
detailedAnswers
});
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
}
});
// Get all questions for this quiz
// ============ QUIZ MANAGEMENT ============
const
questions
=
await
Question
.
find
({
quizId
:
submission
.
quizId
.
_id
});
// Build detailed result with question, student answer, correct answer
const
detailedAnswers
=
questions
.
map
(
q
=>
{
const
studentAnswer
=
submission
.
answers
.
find
(
a
=>
a
.
questionId
.
toString
()
===
q
.
_id
.
toString
()
);
return
{
// @route POST /api/admin/quiz/create
questionId
:
q
.
_id
,
// @desc Create quiz with Excel upload
question
:
q
.
question
,
// @access Admin
options
:
q
.
options
,
router
.
post
(
'
/quiz/create
'
,
upload
.
single
(
'
questionsFile
'
),
async
(
req
,
res
)
=>
{
type
:
q
.
type
,
try
{
correctAnswers
:
q
.
correctAnswers
,
const
{
title
,
timer
,
category
,
difficulty
,
topic
,
assignToAll
,
assignees
,
assignedGroups
}
=
req
.
body
;
studentAnswers
:
studentAnswer
?
studentAnswer
.
selectedAnswers
:
[],
isCorrect
:
(
studentAnswer
&&
studentAnswer
.
selectedAnswers
)
?
checkAnswersMatch
(
studentAnswer
.
selectedAnswers
,
q
.
correctAnswers
)
:
false
};
});
res
.
json
({
if
(
!
title
||
!
timer
)
{
submission
:
{
return
res
.
status
(
400
).
json
({
message
:
'
Please provide quiz title and timer
'
});
id
:
submission
.
_id
,
}
student
:
submission
.
studentId
,
quiz
:
submission
.
quizId
,
score
:
submission
.
score
,
totalMarks
:
submission
.
totalMarks
,
percentage
:
submission
.
percentage
,
timeTaken
:
submission
.
timeTaken
,
submittedAt
:
submission
.
submittedAt
},
detailedAnswers
});
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
});
// ============ QUIZ MANAGEMENT ============
if
(
!
req
.
file
)
{
return
res
.
status
(
400
).
json
({
message
:
'
Please upload an Excel file with questions
'
});
}
console
.
log
(
"
FILE:
"
,
req
.
file
);
console
.
log
(
"
BODY:
"
,
req
.
body
);
// Parse Excel file
const
workbook
=
xlsx
.
readFile
(
req
.
file
.
path
);
const
sheetName
=
workbook
.
SheetNames
[
0
];
const
sheet
=
workbook
.
Sheets
[
sheetName
];
const
data
=
xlsx
.
utils
.
sheet_to_json
(
sheet
);
if
(
data
.
length
===
0
)
{
fs
.
unlinkSync
(
req
.
file
.
path
);
return
res
.
status
(
400
).
json
({
message
:
'
Excel file is empty
'
});
}
// @route POST /api/admin/quiz/create
// Parse assignees
// @desc Create quiz with Excel upload
let
parsedAssignees
=
[];
// @access Admin
if
(
assignees
)
{
router
.
post
(
'
/quiz/create
'
,
upload
.
single
(
'
questionsFile
'
),
async
(
req
,
res
)
=>
{
try
{
parsedAssignees
=
JSON
.
parse
(
assignees
);
}
catch
(
e
)
{
parsedAssignees
=
[];
}
try
{
}
const
{
title
,
timer
,
category
,
difficulty
,
topic
,
assignToAll
,
assignees
,
assignedGroups
}
=
req
.
body
;
if
(
!
title
||
!
timer
)
{
let
parsedGroups
=
[];
return
res
.
status
(
400
).
json
({
message
:
'
Please provide quiz title and timer
'
});
if
(
assignedGroups
)
{
}
try
{
parsedGroups
=
JSON
.
parse
(
assignedGroups
);
}
catch
(
e
)
{
parsedGroups
=
[];
}
}
if
(
!
req
.
file
)
{
// Create quiz
return
res
.
status
(
400
).
json
({
message
:
'
Please upload an Excel file with questions
'
});
const
quiz
=
await
Quiz
.
create
({
}
title
,
timer
:
parseInt
(
timer
),
totalQuestions
:
data
.
length
,
createdBy
:
req
.
user
.
_id
,
category
:
category
||
'
General
'
,
difficulty
:
difficulty
||
'
Intermediate
'
,
topic
:
topic
||
''
,
assignToAll
:
assignToAll
===
'
true
'
||
assignToAll
===
true
,
assignees
:
parsedAssignees
,
assignedGroups
:
parsedGroups
});
// Parse Excel file
// Parse and create questions
const
workbook
=
xlsx
.
readFile
(
req
.
file
.
path
);
const
questions
=
parseExcelQuestions
(
data
,
quiz
.
_id
);
const
sheetName
=
workbook
.
SheetNames
[
0
];
console
.
log
(
"
PARSED QUESTIONS:
"
,
questions
);
const
sheet
=
workbook
.
Sheets
[
sheetName
];
await
Question
.
insertMany
(
questions
);
const
data
=
xlsx
.
utils
.
sheet_to_json
(
sheet
);
if
(
data
.
length
===
0
)
{
// Clean up uploaded file
fs
.
unlinkSync
(
req
.
file
.
path
);
fs
.
unlinkSync
(
req
.
file
.
path
);
return
res
.
status
(
400
).
json
({
message
:
'
Excel file is empty
'
});
}
// Parse assignees
res
.
status
(
201
).
json
({
let
parsedAssignees
=
[];
message
:
'
Quiz created successfully
'
,
if
(
assignees
)
{
quiz
:
{
try
{
parsedAssignees
=
JSON
.
parse
(
assignees
);
}
catch
(
e
)
{
parsedAssignees
=
[];
}
id
:
quiz
.
_id
,
title
:
quiz
.
title
,
timer
:
quiz
.
timer
,
totalQuestions
:
quiz
.
totalQuestions
,
category
:
quiz
.
category
}
});
}
catch
(
error
)
{
if
(
req
.
file
&&
fs
.
existsSync
(
req
.
file
.
path
))
{
fs
.
unlinkSync
(
req
.
file
.
path
);
}
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
}
});
let
parsedGroups
=
[];
// @route POST /api/admin/quiz/create-manual
if
(
assignedGroups
)
{
// @desc Create quiz manually (for AI-generated quizzes or manual entry)
try
{
parsedGroups
=
JSON
.
parse
(
assignedGroups
);
}
catch
(
e
)
{
parsedGroups
=
[];
}
// @access Admin
}
router
.
post
(
'
/quiz/create-manual
'
,
async
(
req
,
res
)
=>
{
try
{
const
{
title
,
timer
,
category
,
difficulty
,
topic
,
assignToAll
,
assignees
,
assignedGroups
,
questions
,
generatedByAI
}
=
req
.
body
;
// Create quiz
if
(
!
title
||
!
timer
)
{
const
quiz
=
await
Quiz
.
create
({
return
res
.
status
(
400
).
json
({
message
:
'
Please provide quiz title and timer
'
});
title
,
}
timer
:
parseInt
(
timer
),
totalQuestions
:
data
.
length
,
createdBy
:
req
.
user
.
_id
,
category
:
category
||
'
General
'
,
difficulty
:
difficulty
||
'
Intermediate
'
,
topic
:
topic
||
''
,
assignToAll
:
assignToAll
===
'
true
'
||
assignToAll
===
true
,
assignees
:
parsedAssignees
,
assignedGroups
:
parsedGroups
});
// Parse and create questions
if
(
!
questions
||
questions
.
length
===
0
)
{
const
questions
=
parseExcelQuestions
(
data
,
quiz
.
_id
);
return
res
.
status
(
400
).
json
({
message
:
'
Please provide at least one question
'
});
await
Question
.
insertMany
(
questions
);
// Clean up uploaded file
fs
.
unlinkSync
(
req
.
file
.
path
);
res
.
status
(
201
).
json
({
message
:
'
Quiz created successfully
'
,
quiz
:
{
id
:
quiz
.
_id
,
title
:
quiz
.
title
,
timer
:
quiz
.
timer
,
totalQuestions
:
quiz
.
totalQuestions
,
category
:
quiz
.
category
}
}
});
}
catch
(
error
)
{
// Create quiz
if
(
req
.
file
&&
fs
.
existsSync
(
req
.
file
.
path
))
{
const
quiz
=
await
Quiz
.
create
({
fs
.
unlinkSync
(
req
.
file
.
path
);
title
,
timer
:
parseInt
(
timer
),
totalQuestions
:
questions
.
length
,
createdBy
:
req
.
user
.
_id
,
category
:
category
||
'
General
'
,
difficulty
:
difficulty
||
'
Intermediate
'
,
topic
:
topic
||
''
,
assignToAll
:
assignToAll
||
false
,
assignees
:
assignees
||
[],
assignedGroups
:
assignedGroups
||
[],
generatedByAI
:
generatedByAI
||
false
});
// Create questions
const
questionDocs
=
questions
.
map
(
q
=>
({
quizId
:
quiz
.
_id
,
question
:
q
.
question
,
options
:
q
.
options
,
correctAnswers
:
q
.
correctAnswers
,
type
:
q
.
correctAnswers
.
length
>
1
?
'
mcq
'
:
'
single
'
}));
await
Question
.
insertMany
(
questionDocs
);
res
.
status
(
201
).
json
({
message
:
'
Quiz created successfully
'
,
quiz
:
{
id
:
quiz
.
_id
,
title
:
quiz
.
title
,
timer
:
quiz
.
timer
,
totalQuestions
:
quiz
.
totalQuestions
,
category
:
quiz
.
category
}
});
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
}
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
});
}
});
// @route POST /api/admin/quiz/create-manual
// @route GET /api/admin/quizzes
// @desc Create quiz manually (for AI-generated quizzes or manual entry)
// @desc Get all quizzes with attempt count
// @access Admin
// @access Admin
router
.
post
(
'
/quiz/create-manual
'
,
async
(
req
,
res
)
=>
{
router
.
get
(
'
/quizzes
'
,
async
(
req
,
res
)
=>
{
try
{
try
{
const
{
title
,
timer
,
category
,
difficulty
,
topic
,
assignToAll
,
assignees
,
assignedGroups
,
questions
,
generatedByAI
}
=
req
.
body
;
const
quizzes
=
await
Quiz
.
find
()
.
populate
(
'
createdBy
'
,
'
name email
'
)
.
sort
({
createdAt
:
-
1
});
// Get attempt counts for each quiz
const
quizzesWithAttempts
=
await
Promise
.
all
(
quizzes
.
map
(
async
(
quiz
)
=>
{
const
attemptCount
=
await
Submission
.
countDocuments
({
quizId
:
quiz
.
_id
});
return
{
...
quiz
.
toObject
(),
attemptCount
};
})
);
if
(
!
title
||
!
timer
)
{
res
.
json
({
quizzes
:
quizzesWithAttempts
});
return
res
.
status
(
400
).
json
({
message
:
'
Please provide quiz title and timer
'
});
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
}
});
// @route GET /api/admin/quiz/:quizId
// @desc Get quiz with its questions for editing
// @access Admin
router
.
get
(
'
/quiz/:quizId
'
,
async
(
req
,
res
)
=>
{
try
{
const
{
quizId
}
=
req
.
params
;
const
quiz
=
await
Quiz
.
findById
(
quizId
)
.
populate
(
'
createdBy
'
,
'
name email
'
)
.
populate
(
'
assignees
'
,
'
name email
'
);
if
(
!
quiz
)
{
return
res
.
status
(
404
).
json
({
message
:
'
Quiz not found
'
});
}
if
(
!
questions
||
questions
.
length
===
0
)
{
const
questions
=
await
Question
.
find
({
quizId
});
return
res
.
status
(
400
).
json
({
message
:
'
Please provide at least one question
'
});
const
attemptCount
=
await
Submission
.
countDocuments
({
quizId
});
res
.
json
({
quiz
,
questions
,
attemptCount
});
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
}
});
// Create quiz
// @route PUT /api/admin/quiz/:quizId
const
quiz
=
await
Quiz
.
create
({
// @desc Edit quiz (only if no attempts have been made)
title
,
// @access Admin
timer
:
parseInt
(
timer
),
router
.
put
(
'
/quiz/:quizId
'
,
async
(
req
,
res
)
=>
{
totalQuestions
:
questions
.
length
,
try
{
createdBy
:
req
.
user
.
_id
,
const
{
quizId
}
=
req
.
params
;
category
:
category
||
'
General
'
,
difficulty
:
difficulty
||
'
Intermediate
'
,
// Check if any attempts exist
topic
:
topic
||
''
,
const
attemptCount
=
await
Submission
.
countDocuments
({
quizId
});
assignToAll
:
assignToAll
||
false
,
if
(
attemptCount
>
0
)
{
assignees
:
assignees
||
[],
return
res
.
status
(
403
).
json
({
assignedGroups
:
assignedGroups
||
[],
message
:
'
This quiz cannot be edited because it has already been attempted by users.
'
,
generatedByAI
:
generatedByAI
||
false
attemptCount
});
});
}
// Create questions
const
{
title
,
timer
,
category
,
difficulty
,
topic
,
assignToAll
,
assignees
,
assignedGroups
,
questions
}
=
req
.
body
;
const
questionDocs
=
questions
.
map
(
q
=>
({
quizId
:
quiz
.
_id
,
// Update quiz metadata
question
:
q
.
question
,
const
updateData
=
{};
options
:
q
.
options
,
if
(
title
)
updateData
.
title
=
title
;
correctAnswers
:
q
.
correctAnswers
,
if
(
timer
)
updateData
.
timer
=
parseInt
(
timer
);
type
:
q
.
correctAnswers
.
length
>
1
?
'
mcq
'
:
'
single
'
if
(
category
)
updateData
.
category
=
category
;
}));
if
(
difficulty
)
updateData
.
difficulty
=
difficulty
;
if
(
topic
!==
undefined
)
updateData
.
topic
=
topic
;
await
Question
.
insertMany
(
questionDocs
);
if
(
assignToAll
!==
undefined
)
updateData
.
assignToAll
=
assignToAll
;
if
(
assignees
)
updateData
.
assignees
=
assignees
;
res
.
status
(
201
).
json
({
if
(
assignedGroups
)
updateData
.
assignedGroups
=
assignedGroups
;
message
:
'
Quiz created successfully
'
,
quiz
:
{
// If questions are provided, replace them
id
:
quiz
.
_id
,
if
(
questions
&&
questions
.
length
>
0
)
{
title
:
quiz
.
title
,
await
Question
.
deleteMany
({
quizId
});
timer
:
quiz
.
timer
,
totalQuestions
:
quiz
.
totalQuestions
,
const
questionDocs
=
questions
.
map
(
q
=>
({
category
:
quiz
.
category
quizId
,
question
:
q
.
question
,
options
:
q
.
options
,
correctAnswers
:
q
.
correctAnswers
,
type
:
q
.
correctAnswers
.
length
>
1
?
'
mcq
'
:
'
single
'
}));
await
Question
.
insertMany
(
questions
);
updateData
.
totalQuestions
=
questions
.
length
;
}
}
});
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
});
// @route GET /api/admin/quizzes
// @desc Get all quizzes with attempt count
// @access Admin
router
.
get
(
'
/quizzes
'
,
async
(
req
,
res
)
=>
{
try
{
const
quizzes
=
await
Quiz
.
find
()
.
populate
(
'
createdBy
'
,
'
name email
'
)
.
sort
({
createdAt
:
-
1
});
// Get attempt counts for each quiz
const
quizzesWithAttempts
=
await
Promise
.
all
(
quizzes
.
map
(
async
(
quiz
)
=>
{
const
attemptCount
=
await
Submission
.
countDocuments
({
quizId
:
quiz
.
_id
});
return
{
...
quiz
.
toObject
(),
attemptCount
};
})
);
res
.
json
({
quizzes
:
quizzesWithAttempts
});
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
});
// @route GET /api/admin/quiz/:quizId
// @desc Get quiz with its questions for editing
// @access Admin
router
.
get
(
'
/quiz/:quizId
'
,
async
(
req
,
res
)
=>
{
try
{
const
{
quizId
}
=
req
.
params
;
const
quiz
=
await
Quiz
.
findById
(
quizId
)
.
populate
(
'
createdBy
'
,
'
name email
'
)
.
populate
(
'
assignees
'
,
'
name email
'
);
if
(
!
quiz
)
{
return
res
.
status
(
404
).
json
({
message
:
'
Quiz not found
'
});
}
const
questions
=
await
Question
.
find
({
quizId
});
const
quiz
=
await
Quiz
.
findByIdAndUpdate
(
quizId
,
updateData
,
{
new
:
true
});
const
attemptCount
=
await
Submission
.
countDocuments
({
quizId
});
res
.
json
({
quiz
,
questions
,
attemptCount
});
res
.
json
({
message
:
'
Quiz updated successfully
'
,
quiz
});
}
catch
(
error
)
{
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
}
});
});
// @route PUT /api/admin/quiz/:quizId
// @route PUT /api/admin/quiz/:quizId/assign
// @desc Edit quiz (only if no attempts have been made)
// @desc Assign quiz to users/groups
// @access Admin
// @access Admin
router
.
put
(
'
/quiz/:quizId
'
,
async
(
req
,
res
)
=>
{
router
.
put
(
'
/quiz/:quizId/assign
'
,
async
(
req
,
res
)
=>
{
try
{
try
{
const
{
quizId
}
=
req
.
params
;
const
{
quizId
}
=
req
.
params
;
const
{
assignToAll
,
assignees
,
assignedGroups
}
=
req
.
body
;
// Check if any attempts exist
const
attemptCount
=
await
Submission
.
countDocuments
({
quizId
});
const
quiz
=
await
Quiz
.
findByIdAndUpdate
(
quizId
,
{
if
(
attemptCount
>
0
)
{
assignToAll
:
assignToAll
||
false
,
return
res
.
status
(
403
).
json
({
assignees
:
assignees
||
[],
message
:
'
This quiz cannot be edited because it has already been attempted by users.
'
,
assignedGroups
:
assignedGroups
||
[]
attemptCount
},
{
new
:
true
});
});
if
(
!
quiz
)
return
res
.
status
(
404
).
json
({
message
:
'
Quiz not found
'
});
res
.
json
({
message
:
'
Quiz assignment updated
'
,
quiz
});
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
}
});
// @route DELETE /api/admin/quiz/:quizId
// @desc Delete a quiz (only if no attempts have been made)
// @access Admin
router
.
delete
(
'
/quiz/:quizId
'
,
async
(
req
,
res
)
=>
{
try
{
const
{
quizId
}
=
req
.
params
;
// Check if any attempts exist
const
attemptCount
=
await
Submission
.
countDocuments
({
quizId
});
if
(
attemptCount
>
0
)
{
return
res
.
status
(
403
).
json
({
message
:
'
This quiz cannot be deleted because it has already been attempted by users.
'
,
attemptCount
});
}
const
{
title
,
timer
,
category
,
difficulty
,
topic
,
assignToAll
,
assignees
,
assignedGroups
,
questions
}
=
req
.
body
;
// Update quiz metadata
const
updateData
=
{};
if
(
title
)
updateData
.
title
=
title
;
if
(
timer
)
updateData
.
timer
=
parseInt
(
timer
);
if
(
category
)
updateData
.
category
=
category
;
if
(
difficulty
)
updateData
.
difficulty
=
difficulty
;
if
(
topic
!==
undefined
)
updateData
.
topic
=
topic
;
if
(
assignToAll
!==
undefined
)
updateData
.
assignToAll
=
assignToAll
;
if
(
assignees
)
updateData
.
assignees
=
assignees
;
if
(
assignedGroups
)
updateData
.
assignedGroups
=
assignedGroups
;
// If questions are provided, replace them
if
(
questions
&&
questions
.
length
>
0
)
{
await
Question
.
deleteMany
({
quizId
});
await
Question
.
deleteMany
({
quizId
});
await
Quiz
.
findByIdAndDelete
(
quizId
);
const
questionDocs
=
questions
.
map
(
q
=>
({
res
.
json
({
message
:
'
Quiz deleted successfully
'
});
quizId
,
}
catch
(
error
)
{
question
:
q
.
question
,
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
options
:
q
.
options
,
}
correctAnswers
:
q
.
correctAnswers
,
});
type
:
q
.
correctAnswers
.
length
>
1
?
'
mcq
'
:
'
single
'
}));
await
Question
.
insertMany
(
questionDocs
);
// @route GET /api/admin/categories
updateData
.
totalQuestions
=
questions
.
length
;
// @desc Get all unique quiz categories
// @access Admin
router
.
get
(
'
/categories
'
,
async
(
req
,
res
)
=>
{
try
{
const
categories
=
await
Quiz
.
distinct
(
'
category
'
);
res
.
json
({
categories
});
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
}
});
const
quiz
=
await
Quiz
.
findByIdAndUpdate
(
quizId
,
updateData
,
{
new
:
true
});
// @route GET /api/admin/groups
// @desc Get all unique user groups
// @access Admin
router
.
get
(
'
/groups
'
,
async
(
req
,
res
)
=>
{
try
{
const
groups
=
await
User
.
distinct
(
'
group
'
);
res
.
json
({
groups
});
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
});
res
.
json
({
message
:
'
Quiz updated successfully
'
,
quiz
});
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
// @route POST /api/admin/groups
}
// @desc Create a new group
});
// @access Admin
router
.
post
(
'
/groups
'
,
async
(
req
,
res
)
=>
{
// @route PUT /api/admin/quiz/:quizId/assign
try
{
// @desc Assign quiz to users/groups
const
{
name
}
=
req
.
body
;
// @access Admin
router
.
put
(
'
/quiz/:quizId/assign
'
,
async
(
req
,
res
)
=>
{
if
(
!
name
||
!
name
.
trim
())
{
try
{
return
res
.
status
(
400
).
json
({
message
:
'
Group name is required
'
});
const
{
quizId
}
=
req
.
params
;
}
const
{
assignToAll
,
assignees
,
assignedGroups
}
=
req
.
body
;
// Check if already exists
const
quiz
=
await
Quiz
.
findByIdAndUpdate
(
quizId
,
{
const
existing
=
await
User
.
findOne
({
group
:
name
.
trim
()
});
assignToAll
:
assignToAll
||
false
,
assignees
:
assignees
||
[],
if
(
existing
)
{
assignedGroups
:
assignedGroups
||
[]
return
res
.
status
(
400
).
json
({
message
:
'
Group already exists
'
});
},
{
new
:
true
});
}
if
(
!
quiz
)
return
res
.
status
(
404
).
json
({
message
:
'
Quiz not found
'
});
// ⚠️ Since you're using group as string,
// we don't store separately — just return success
res
.
json
({
message
:
'
Quiz assignment updated
'
,
quiz
});
res
.
status
(
201
).
json
({
message
:
'
Group created successfully
'
,
group
:
name
.
trim
()
});
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
catch
(
error
)
{
}
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
});
}
});
// @route DELETE /api/admin/quiz/:quizId
// @desc Delete a quiz (only if no attempts have been made)
// @route GET /api/admin/stats
// @access Admin
// @desc Get dashboard statistics
router
.
delete
(
'
/quiz/:quizId
'
,
async
(
req
,
res
)
=>
{
// @access Admin
try
{
router
.
get
(
'
/stats
'
,
async
(
req
,
res
)
=>
{
const
{
quizId
}
=
req
.
params
;
try
{
const
totalUsers
=
await
User
.
countDocuments
({
role
:
'
candidate
'
});
// Check if any attempts exist
const
totalHR
=
await
User
.
countDocuments
({
role
:
'
hr
'
});
const
attemptCount
=
await
Submission
.
countDocuments
({
quizId
});
const
totalQuizzes
=
await
Quiz
.
countDocuments
();
if
(
attemptCount
>
0
)
{
const
totalSubmissions
=
await
Submission
.
countDocuments
();
return
res
.
status
(
403
).
json
({
const
onlineUsers
=
await
User
.
countDocuments
({
isLoggedIn
:
true
,
role
:
{
$in
:
[
'
candidate
'
,
'
hr
'
]
}
});
message
:
'
This quiz cannot be deleted because it has already been attempted by users.
'
,
attemptCount
// Recent submissions
const
recentSubmissions
=
await
Submission
.
find
()
.
populate
(
'
studentId
'
,
'
name email
'
)
.
populate
(
'
quizId
'
,
'
title
'
)
.
sort
({
submittedAt
:
-
1
})
.
limit
(
5
);
res
.
json
({
stats
:
{
totalUsers
,
totalHR
,
totalQuizzes
,
totalSubmissions
,
onlineUsers
},
recentSubmissions
});
});
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
}
});
await
Question
.
deleteMany
({
quizId
});
// ============ HELPER FUNCTIONS ============
await
Quiz
.
findByIdAndDelete
(
quizId
);
res
.
json
({
message
:
'
Quiz deleted successfully
'
});
function
parseExcelQuestions
(
data
,
quizId
)
{
}
catch
(
error
)
{
return
data
.
map
(
row
=>
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
const
question
=
row
[
'
Question
'
]
||
row
[
'
question
'
]
||
''
;
}
const
option1
=
row
[
'
Option1
'
]
||
row
[
'
option1
'
]
||
row
[
'
Option 1
'
]
||
''
;
});
const
option2
=
row
[
'
Option2
'
]
||
row
[
'
option2
'
]
||
row
[
'
Option 2
'
]
||
''
;
const
option3
=
row
[
'
Option3
'
]
||
row
[
'
option3
'
]
||
row
[
'
Option 3
'
]
||
''
;
// @route GET /api/admin/categories
const
option4
=
row
[
'
Option4
'
]
||
row
[
'
option4
'
]
||
row
[
'
Option 4
'
]
||
''
;
// @desc Get all unique quiz categories
const
correct
=
row
[
'
Correct
'
]
||
row
[
'
correct
'
]
||
row
[
'
Answer
'
]
||
row
[
'
answer
'
]
||
''
;
// @access Admin
router
.
get
(
'
/categories
'
,
async
(
req
,
res
)
=>
{
const
options
=
[
try
{
const
categories
=
await
Quiz
.
distinct
(
'
category
'
);
res
.
json
({
categories
});
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
});
// @route GET /api/admin/groups
// @desc Get all unique user groups
// @access Admin
router
.
get
(
'
/groups
'
,
async
(
req
,
res
)
=>
{
try
{
const
groups
=
await
User
.
distinct
(
'
group
'
);
res
.
json
({
groups
});
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
});
// @route GET /api/admin/stats
// @desc Get dashboard statistics
// @access Admin
router
.
get
(
'
/stats
'
,
async
(
req
,
res
)
=>
{
try
{
const
totalUsers
=
await
User
.
countDocuments
({
role
:
'
candidate
'
});
const
totalHR
=
await
User
.
countDocuments
({
role
:
'
hr
'
});
const
totalQuizzes
=
await
Quiz
.
countDocuments
();
const
totalSubmissions
=
await
Submission
.
countDocuments
();
const
onlineUsers
=
await
User
.
countDocuments
({
isLoggedIn
:
true
,
role
:
{
$in
:
[
'
candidate
'
,
'
hr
'
]
}
});
// Recent submissions
const
recentSubmissions
=
await
Submission
.
find
()
.
populate
(
'
studentId
'
,
'
name email
'
)
.
populate
(
'
quizId
'
,
'
title
'
)
.
sort
({
submittedAt
:
-
1
})
.
limit
(
5
);
res
.
json
({
stats
:
{
totalUsers
,
totalHR
,
totalQuizzes
,
totalSubmissions
,
onlineUsers
},
recentSubmissions
});
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
});
// ============ HELPER FUNCTIONS ============
function
parseExcelQuestions
(
data
,
quizId
)
{
return
data
.
map
(
row
=>
{
const
question
=
row
[
'
Question
'
]
||
row
[
'
question
'
]
||
''
;
const
option1
=
row
[
'
Option1
'
]
||
row
[
'
option1
'
]
||
row
[
'
Option 1
'
]
||
''
;
const
option2
=
row
[
'
Option2
'
]
||
row
[
'
option2
'
]
||
row
[
'
Option 2
'
]
||
''
;
const
option3
=
row
[
'
Option3
'
]
||
row
[
'
option3
'
]
||
row
[
'
Option 3
'
]
||
''
;
const
option4
=
row
[
'
Option4
'
]
||
row
[
'
option4
'
]
||
row
[
'
Option 4
'
]
||
''
;
const
correct
=
row
[
'
Correct
'
]
||
row
[
'
correct
'
]
||
row
[
'
Answer
'
]
||
row
[
'
answer
'
]
||
''
;
const
correctStr
=
correct
.
toString
().
trim
();
const
correctAnswers
=
correctStr
.
includes
(
'
,
'
)
?
correctStr
.
split
(
'
,
'
).
map
(
a
=>
a
.
trim
())
:
[
correctStr
];
const
type
=
correctAnswers
.
length
>
1
?
'
mcq
'
:
'
single
'
;
return
{
quizId
,
question
:
question
.
toString
().
trim
(),
options
:
[
option1
.
toString
().
trim
(),
option1
.
toString
().
trim
(),
option2
.
toString
().
trim
(),
option2
.
toString
().
trim
(),
option3
.
toString
().
trim
(),
option3
.
toString
().
trim
(),
option4
.
toString
().
trim
()
option4
.
toString
().
trim
()
],
];
correctAnswers
,
type
const
correctStr
=
correct
.
toString
().
trim
();
};
const
correctArr
=
correctStr
.
split
(
'
,
'
).
map
(
s
=>
s
.
trim
());
});
}
// 🔥 convert text → index
const
correctAnswers
=
options
.
map
((
opt
,
index
)
=>
correctArr
.
includes
(
opt
)
?
index
.
toString
()
:
null
)
.
filter
(
val
=>
val
!==
null
);
if
(
correctAnswers
.
length
===
0
)
{
throw
new
Error
(
`Correct answer "
${
correctStr
}
" not matching options`
);
}
const
type
=
correctAnswers
.
length
>
1
?
'
mcq
'
:
'
single
'
;
return
{
quizId
,
question
:
question
.
toString
().
trim
(),
options
:
[
option1
.
toString
().
trim
(),
option2
.
toString
().
trim
(),
option3
.
toString
().
trim
(),
option4
.
toString
().
trim
()
],
correctAnswers
,
type
};
});
}
function
checkAnswersMatch
(
arr1
,
arr2
)
{
function
checkAnswersMatch
(
arr1
,
arr2
)
{
if
(
!
arr1
||
!
arr2
||
arr1
.
length
!==
arr2
.
length
)
return
false
;
if
(
!
arr1
||
!
arr2
||
arr1
.
length
!==
arr2
.
length
)
return
false
;
const
a
=
arr1
.
map
(
x
=>
x
.
toString
().
trim
().
toLowerCase
()).
sort
().
join
(
'
||
'
);
const
a
=
arr1
.
map
(
x
=>
x
.
toString
().
trim
().
toLowerCase
()).
sort
().
join
(
'
||
'
);
const
b
=
arr2
.
map
(
x
=>
x
.
toString
().
trim
().
toLowerCase
()).
sort
().
join
(
'
||
'
);
const
b
=
arr2
.
map
(
x
=>
x
.
toString
().
trim
().
toLowerCase
()).
sort
().
join
(
'
||
'
);
return
a
===
b
;
return
a
===
b
;
}
}
module
.
exports
=
router
;
module
.
exports
=
router
;
Backend/routes/auth.js
View file @
d2648061
...
@@ -7,7 +7,7 @@ const router = express.Router();
...
@@ -7,7 +7,7 @@ const router = express.Router();
// Generate JWT Token
// Generate JWT Token
const
generateToken
=
(
id
)
=>
{
const
generateToken
=
(
id
)
=>
{
return
jwt
.
sign
({
id
},
process
.
env
.
JWT_SECRET
,
{
return
jwt
.
sign
({
id
},
process
.
env
.
JWT_SECRET
,
{
expiresIn
:
process
.
env
.
JWT_EXPIRES_IN
||
'
7
d
'
expiresIn
:
process
.
env
.
JWT_EXPIRES_IN
||
'
1
d
'
});
});
};
};
...
...
Backend/server.js
View file @
d2648061
const
express
=
require
(
'
express
'
);
const
express
=
require
(
'
express
'
);
const
cors
=
require
(
'
cors
'
);
const
cors
=
require
(
'
cors
'
);
const
cookieParser
=
require
(
'
cookie-parser
'
);
const
cookieParser
=
require
(
'
cookie-parser
'
);
const
dotenv
=
require
(
'
dotenv
'
);
const
dotenv
=
require
(
'
dotenv
'
);
const
connectDB
=
require
(
'
./config/db
'
);
const
connectDB
=
require
(
'
./config/db
'
);
// Load env vars
// Load env vars
dotenv
.
config
();
dotenv
.
config
();
// Connect to database
// Connect to database
connectDB
();
connectDB
();
const
app
=
express
();
const
app
=
express
();
// Middleware
// Middleware
const
allowedOrigins
=
[
const
allowedOrigins
=
[
'
http://localhost:4200
'
,
'
http://localhost:4200
'
,
'
http://localhost:3000
'
,
'
http://localhost:3000
'
,
process
.
env
.
FRONTEND_URL
process
.
env
.
FRONTEND_URL
].
filter
(
Boolean
);
].
filter
(
Boolean
);
app
.
use
(
cors
({
app
.
use
(
cors
({
origin
:
function
(
origin
,
callback
)
{
origin
:
function
(
origin
,
callback
)
{
// Allow requests with no origin (mobile apps, curl, Postman, server-to-server)
// Allow requests with no origin (mobile apps, curl, Postman, server-to-server)
if
(
!
origin
)
return
callback
(
null
,
true
);
if
(
!
origin
)
return
callback
(
null
,
true
);
// Allow any vercel.app previews and production deployments
// Allow any vercel.app previews and production deployments
if
(
origin
.
endsWith
(
'
.vercel.app
'
)
||
origin
.
includes
(
'
vercel.app
'
))
{
if
(
origin
.
endsWith
(
'
.vercel.app
'
)
||
origin
.
includes
(
'
vercel.app
'
))
{
return
callback
(
null
,
true
);
return
callback
(
null
,
true
);
}
// Allow explicitly listed origins
if
(
allowedOrigins
.
includes
(
origin
))
{
return
callback
(
null
,
true
);
}
// Block everything else
console
.
warn
(
`CORS blocked origin:
${
origin
}
`
);
return
callback
(
new
Error
(
`CORS policy: origin
${
origin
}
not allowed`
),
false
);
},
credentials
:
true
,
methods
:
[
'
GET
'
,
'
POST
'
,
'
PUT
'
,
'
DELETE
'
,
'
OPTIONS
'
],
allowedHeaders
:
[
'
Content-Type
'
,
'
Authorization
'
]
}));
app
.
use
(
express
.
json
());
app
.
use
(
express
.
urlencoded
({
extended
:
true
}));
app
.
use
(
cookieParser
());
// Routes
app
.
use
(
'
/api/auth
'
,
require
(
'
./routes/auth
'
));
app
.
use
(
'
/api/admin
'
,
require
(
'
./routes/admin
'
));
app
.
use
(
'
/api/hr
'
,
require
(
'
./routes/hr
'
));
app
.
use
(
'
/api/candidate
'
,
require
(
'
./routes/candidate
'
));
// Keep backward compatibility for old student endpoints
app
.
use
(
'
/api/student
'
,
require
(
'
./routes/candidate
'
));
// Health check
app
.
get
(
'
/api/health
'
,
(
req
,
res
)
=>
{
res
.
json
({
status
:
'
OK
'
,
message
:
'
QuizMaster Pro API is running
'
});
});
// Error handling middleware
app
.
use
((
err
,
req
,
res
,
next
)
=>
{
console
.
error
(
err
.
stack
);
if
(
err
.
name
===
'
MulterError
'
)
{
if
(
err
.
code
===
'
LIMIT_FILE_SIZE
'
)
{
return
res
.
status
(
400
).
json
({
message
:
'
File size exceeds 5MB limit
'
});
}
return
res
.
status
(
400
).
json
({
message
:
err
.
message
});
}
}
// Allow explicitly listed origins
res
.
status
(
500
).
json
({
message
:
'
Something went wrong!
'
,
error
:
err
.
message
});
if
(
allowedOrigins
.
includes
(
origin
))
{
});
return
callback
(
null
,
true
);
}
// Block everything else
console
.
warn
(
`CORS blocked origin:
${
origin
}
`
);
return
callback
(
new
Error
(
`CORS policy: origin
${
origin
}
not allowed`
),
false
);
},
credentials
:
true
,
methods
:
[
'
GET
'
,
'
POST
'
,
'
PUT
'
,
'
DELETE
'
,
'
OPTIONS
'
],
allowedHeaders
:
[
'
Content-Type
'
,
'
Authorization
'
]
}));
app
.
use
(
express
.
json
());
app
.
use
(
express
.
urlencoded
({
extended
:
true
}));
app
.
use
(
cookieParser
());
// Routes
app
.
use
(
'
/api/auth
'
,
require
(
'
./routes/auth
'
));
app
.
use
(
'
/api/admin
'
,
require
(
'
./routes/admin
'
));
app
.
use
(
'
/api/hr
'
,
require
(
'
./routes/hr
'
));
app
.
use
(
'
/api/candidate
'
,
require
(
'
./routes/candidate
'
));
// Keep backward compatibility for old student endpoints
app
.
use
(
'
/api/student
'
,
require
(
'
./routes/candidate
'
));
// Health check
app
.
get
(
'
/api/health
'
,
(
req
,
res
)
=>
{
res
.
json
({
status
:
'
OK
'
,
message
:
'
QuizMaster Pro API is running
'
});
});
// Error handling middleware
app
.
use
((
err
,
req
,
res
,
next
)
=>
{
console
.
error
(
err
.
stack
);
if
(
err
.
name
===
'
MulterError
'
)
{
if
(
err
.
code
===
'
LIMIT_FILE_SIZE
'
)
{
return
res
.
status
(
400
).
json
({
message
:
'
File size exceeds 5MB limit
'
});
}
return
res
.
status
(
400
).
json
({
message
:
err
.
message
});
}
res
.
status
(
500
).
json
({
message
:
'
Something went wrong!
'
,
error
:
err
.
message
});
});
const
PORT
=
process
.
env
.
PORT
||
5000
;
const
PORT
=
process
.
env
.
PORT
||
5000
;
app
.
listen
(
PORT
,
()
=>
{
app
.
listen
(
PORT
,
()
=>
{
console
.
log
(
`Server running on port
${
PORT
}
`
);
console
.
log
(
`Server running on port
${
PORT
}
`
);
});
});
Frontend/src/app/interceptors/auth.interceptor.ts
View file @
d2648061
import
{
HttpInterceptorFn
}
from
'
@angular/common/http
'
;
import
{
HttpInterceptorFn
}
from
'
@angular/common/http
'
;
export
const
authInterceptor
:
HttpInterceptorFn
=
(
req
,
next
)
=>
{
export
const
authInterceptor
:
HttpInterceptorFn
=
(
req
,
next
)
=>
{
const
token
=
localStorage
.
getItem
(
'
token
'
);
const
token
=
sessionStorage
.
getItem
(
'
token
'
);
if
(
token
)
{
if
(
token
)
{
const
cloned
=
req
.
clone
({
const
cloned
=
req
.
clone
({
...
...
Frontend/src/app/pages/admin/edit-quiz/edit-quiz.ts
View file @
d2648061
...
@@ -56,34 +56,53 @@ export class EditQuizComponent implements OnInit {
...
@@ -56,34 +56,53 @@ export class EditQuizComponent implements OnInit {
}
}
});
});
}
}
onSave
():
void
{
if
(
!
this
.
title
.
trim
())
{
this
.
error
.
set
(
'
Title is required
'
);
return
;
}
this
.
saving
.
set
(
true
);
this
.
error
.
set
(
''
);
onSave
():
void
{
// 🔥 Format questions correctly
if
(
!
this
.
title
.
trim
())
{
this
.
error
.
set
(
'
Title is required
'
);
return
;
}
const
formattedQuestions
=
this
.
questions
().
map
(
q
=>
{
this
.
saving
.
set
(
true
);
const
options
=
q
.
options
;
this
.
error
.
set
(
''
);
const
data
=
{
const
correctIndex
=
options
.
findIndex
(
title
:
this
.
title
,
(
opt
:
string
)
=>
opt
==
q
.
correctAnswer
timer
:
this
.
timer
,
);
category
:
this
.
category
,
difficulty
:
this
.
difficulty
,
return
{
topic
:
this
.
topic
,
question
:
q
.
question
,
questions
:
this
.
questions
()
options
,
correctAnswers
:
[
correctIndex
.
toString
()],
type
:
'
single
'
};
};
});
// ✅ closes map()
this
.
quizService
.
updateQuiz
(
this
.
quizId
,
data
).
subscribe
({
// 🔥 Final data
next
:
()
=>
{
const
data
=
{
this
.
saving
.
set
(
false
);
title
:
this
.
title
,
this
.
success
.
set
(
'
Quiz updated successfully!
'
);
timer
:
this
.
timer
,
setTimeout
(()
=>
this
.
router
.
navigate
([
'
/admin/quizzes
'
]),
1200
);
category
:
this
.
category
,
},
difficulty
:
this
.
difficulty
,
error
:
(
err
)
=>
{
topic
:
this
.
topic
,
this
.
saving
.
set
(
false
);
questions
:
formattedQuestions
this
.
error
.
set
(
err
.
error
?.
message
||
'
Failed to update quiz
'
);
};
// ✅ closes data object
}
});
}
this
.
quizService
.
updateQuiz
(
this
.
quizId
,
data
).
subscribe
({
next
:
()
=>
{
this
.
saving
.
set
(
false
);
this
.
success
.
set
(
'
Quiz updated successfully!
'
);
setTimeout
(()
=>
this
.
router
.
navigate
([
'
/admin/quizzes
'
]),
1200
);
},
error
:
(
err
)
=>
{
this
.
saving
.
set
(
false
);
this
.
error
.
set
(
err
.
error
?.
message
||
'
Failed to update quiz
'
);
}
});
// ✅ closes subscribe
}
// ✅ closes function
updateQuestion
(
index
:
number
,
field
:
string
,
value
:
any
):
void
{
updateQuestion
(
index
:
number
,
field
:
string
,
value
:
any
):
void
{
const
q
=
[...
this
.
questions
()];
const
q
=
[...
this
.
questions
()];
q
[
index
]
=
{
...
q
[
index
],
[
field
]:
value
};
q
[
index
]
=
{
...
q
[
index
],
[
field
]:
value
};
...
...
Frontend/src/app/pages/admin/generate-quiz/generate-quiz.html
View file @
d2648061
<div
class=
"dashboard-layout"
>
<div
class=
"dashboard-layout"
>
<aside
class=
"sidebar"
>
<aside
class=
"sidebar"
>
<div
class=
"sidebar-header"
>
<div
class=
"sidebar-header"
>
<span
class=
"logo-icon"
>
📝
</span><h2>
QuizMaster
</h2><span
class=
"role-badge"
>
Admin
</span>
<span
class=
"logo-icon"
>
📝
</span>
<h2>
QuizMaster
</h2><span
class=
"role-badge"
>
Admin
</span>
</div>
</div>
<nav
class=
"sidebar-nav"
>
<nav
class=
"sidebar-nav"
>
<a
routerLink=
"/admin/dashboard"
class=
"nav-item"
><span
class=
"nav-icon"
>
🏠
</span><span>
Dashboard
</span></a>
<a
routerLink=
"/admin/dashboard"
class=
"nav-item"
><span
class=
"nav-icon"
>
🏠
</span><span>
Dashboard
</span></a>
<a
routerLink=
"/admin/users"
class=
"nav-item"
><span
class=
"nav-icon"
>
👥
</span><span>
Users
</span></a>
<a
routerLink=
"/admin/users"
class=
"nav-item"
><span
class=
"nav-icon"
>
👥
</span><span>
Users
</span></a>
<a
routerLink=
"/admin/generate-quiz"
class=
"nav-item active"
><span
class=
"nav-icon"
>
➕
</span><span>
Generate Quiz
</span></a>
<a
routerLink=
"/admin/generate-quiz"
class=
"nav-item active"
><span
class=
"nav-icon"
>
➕
</span><span>
Generate
Quiz
</span></a>
</nav>
</nav>
<div
class=
"sidebar-footer"
>
<div
class=
"sidebar-footer"
>
<div
class=
"user-info"
>
<div
class=
"user-info"
>
<div
class=
"user-avatar"
>
{{ authService.currentUser()?.name?.charAt(0) || 'A' }}
</div>
<div
class=
"user-avatar"
>
{{ authService.currentUser()?.name?.charAt(0) || 'A' }}
</div>
<div
class=
"user-details"
><span
class=
"user-name"
>
{{ authService.currentUser()?.name }}
</span><span
class=
"user-email"
>
{{ authService.currentUser()?.email }}
</span></div>
<div
class=
"user-details"
><span
class=
"user-name"
>
{{ authService.currentUser()?.name }}
</span><span
class=
"user-email"
>
{{ authService.currentUser()?.email }}
</span></div>
</div>
</div>
<button
class=
"logout-btn"
(click)=
"logout()"
><span>
🚪
</span>
Logout
</button>
<button
class=
"logout-btn"
(click)=
"logout()"
><span>
🚪
</span>
Logout
</button>
</div>
</div>
...
@@ -24,10 +27,10 @@
...
@@ -24,10 +27,10 @@
</div>
</div>
@if (success()) {
@if (success()) {
<div
class=
"alert alert-success"
>
✅ {{ success() }}
</div>
<div
class=
"alert alert-success"
>
✅ {{ success() }}
</div>
}
}
@if (error()) {
@if (error()) {
<div
class=
"alert alert-error"
>
⚠️ {{ error() }}
</div>
<div
class=
"alert alert-error"
>
⚠️ {{ error() }}
</div>
}
}
<div
class=
"create-section"
>
<div
class=
"create-section"
>
...
@@ -35,7 +38,8 @@
...
@@ -35,7 +38,8 @@
<div
class=
"form-row"
>
<div
class=
"form-row"
>
<div
class=
"form-group"
>
<div
class=
"form-group"
>
<label
for=
"title"
>
Quiz Name
</label>
<label
for=
"title"
>
Quiz Name
</label>
<input
type=
"text"
id=
"title"
[(ngModel)]=
"title"
name=
"title"
placeholder=
"e.g., JavaScript Fundamentals"
/>
<input
type=
"text"
id=
"title"
[(ngModel)]=
"title"
name=
"title"
placeholder=
"e.g., JavaScript Fundamentals"
/>
</div>
</div>
<div
class=
"form-group"
>
<div
class=
"form-group"
>
<label
for=
"timer"
>
Timer (minutes)
</label>
<label
for=
"timer"
>
Timer (minutes)
</label>
...
@@ -48,53 +52,77 @@
...
@@ -48,53 +52,77 @@
<div
class=
"file-upload"
(click)=
"fileInput.click()"
>
<div
class=
"file-upload"
(click)=
"fileInput.click()"
>
<input
#fileInput
type=
"file"
accept=
".xlsx,.xls"
(change)=
"onFileSelected($event)"
hidden
/>
<input
#fileInput
type=
"file"
accept=
".xlsx,.xls"
(change)=
"onFileSelected($event)"
hidden
/>
@if (fileName()) {
@if (fileName()) {
<span
class=
"file-icon"
>
📄
</span>
<span
class=
"file-icon"
>
📄
</span>
<span
class=
"file-name"
>
{{ fileName() }}
</span>
<span
class=
"file-name"
>
{{ fileName() }}
</span>
<span
class=
"file-change"
>
Change
</span>
<span
class=
"file-change"
>
Change
</span>
} @else {
} @else {
<span
class=
"upload-icon"
>
📤
</span>
<span
class=
"upload-icon"
>
📤
</span>
<span
class=
"upload-text"
>
Click to upload Excel file
</span>
<span
class=
"upload-text"
>
Click to upload Excel file
</span>
<span
class=
"upload-hint"
>
Format: Question | Option1 | Option2 | Option3 | Option4 | Correct
</span>
<span
class=
"upload-hint"
>
Format: Question | Option1 | Option2 | Option3 | Option4 | Correct
</span>
}
}
</div>
</div>
</div>
</div>
<button
type=
"submit"
class=
"btn btn-primary"
[disabled]=
"loading()"
>
<div
class=
"form-group"
>
<label>
Assign Quiz
</label>
<div
style=
"margin-bottom: 10px;"
>
<label>
<input
type=
"checkbox"
[checked]=
"assignToAll()"
(change)=
"assignToAll.set(!assignToAll())"
/>
Assign to All Users
</label>
</div>
@if (!assignToAll()) {
<div
class=
"group-list"
>
@for (group of groups(); track group._id) {
<label
style=
"display:block; margin-bottom:6px;"
>
<input
type=
"checkbox"
[value]=
"group.name"
(change)=
"onGroupToggle(group.name, $event)"
/>
{{ group.name }}
</label>
}
</div>
}
</div>
<button
mat-raised-button
color=
"primary"
type=
"submit"
class=
"btn btn-primary"
[disabled]=
"loading()"
>
@if (loading()) {
@if (loading()) {
<span
class=
"spinner"
></span>
Creating Quiz...
<span
class=
"spinner"
></span>
Creating Quiz...
} @else {
} @else {
🚀 Create Quiz
🚀 Create Quiz
}
}
</button>
</button>
</form>
</form>
</div>
</div>
<!-- Existing Quizzes -->
<!-- Existing Quizzes -->
<h2
class=
"section-title"
>
Existing Quizzes
</h2>
<h2
class=
"section-title"
>
Existing Quizzes
</h2>
@if (loadingQuizzes()) {
@if (loadingQuizzes()) {
<div
class=
"loading-state"
><div
class=
"loader"
></div></div>
<div
class=
"loading-state"
>
<div
class=
"loader"
></div>
</div>
} @else if (quizzes().length === 0) {
} @else if (quizzes().length === 0) {
<div
class=
"empty-state"
>
<div
class=
"empty-state"
>
<span
class=
"empty-icon"
>
📋
</span>
<span
class=
"empty-icon"
>
📋
</span>
<h3>
No quizzes yet
</h3>
<h3>
No quizzes yet
</h3>
<p>
Create your first quiz above
</p>
<p>
Create your first quiz above
</p>
</div>
</div>
} @else {
} @else {
<div
class=
"quiz-grid"
>
<div
class=
"quiz-grid"
>
@for (quiz of quizzes(); track quiz._id) {
@for (quiz of quizzes(); track quiz._id) {
<div
class=
"quiz-card"
>
<div
class=
"quiz-card"
>
<div
class=
"quiz-card-header"
>
<div
class=
"quiz-card-header"
>
<h4>
{{ quiz.title }}
</h4>
<h4>
{{ quiz.title }}
</h4>
<button
class=
"delete-btn"
(click)=
"deleteQuiz(quiz._id)"
>
🗑️
</button>
<button
class=
"delete-btn"
(click)=
"deleteQuiz(quiz._id)"
>
🗑️
</button>
</div>
</div>
<div
class=
"quiz-meta"
>
<div
class=
"quiz-meta"
>
<span>
⏱️ {{ quiz.timer }} min
</span>
<span>
⏱️ {{ quiz.timer }} min
</span>
<span>
📝 {{ quiz.totalQuestions }} questions
</span>
<span>
📝 {{ quiz.totalQuestions }} questions
</span>
</div>
</div>
<span
class=
"quiz-date"
>
Created {{ quiz.createdAt | date:'mediumDate' }}
</span>
<span
class=
"quiz-date"
>
Created {{ quiz.createdAt | date:'mediumDate' }}
</span>
</div>
}
</div>
</div>
}
</div>
}
}
</main>
</main>
</div>
</div>
\ No newline at end of file
Frontend/src/app/pages/admin/generate-quiz/generate-quiz.ts
View file @
d2648061
...
@@ -4,11 +4,12 @@ import { FormsModule } from '@angular/forms';
...
@@ -4,11 +4,12 @@ import { FormsModule } from '@angular/forms';
import
{
RouterLink
}
from
'
@angular/router
'
;
import
{
RouterLink
}
from
'
@angular/router
'
;
import
{
AuthService
}
from
'
../../../services/auth.service
'
;
import
{
AuthService
}
from
'
../../../services/auth.service
'
;
import
{
QuizService
}
from
'
../../../services/quiz.service
'
;
import
{
QuizService
}
from
'
../../../services/quiz.service
'
;
import
{
MatButtonModule
}
from
'
@angular/material/button
'
;
@
Component
({
@
Component
({
selector
:
'
app-generate-quiz
'
,
selector
:
'
app-generate-quiz
'
,
standalone
:
true
,
standalone
:
true
,
imports
:
[
CommonModule
,
FormsModule
,
RouterLink
],
imports
:
[
CommonModule
,
FormsModule
,
RouterLink
,
MatButtonModule
],
templateUrl
:
'
./generate-quiz.html
'
,
templateUrl
:
'
./generate-quiz.html
'
,
styleUrl
:
'
./generate-quiz.css
'
styleUrl
:
'
./generate-quiz.css
'
})
})
...
@@ -17,7 +18,9 @@ export class GenerateQuizComponent implements OnInit {
...
@@ -17,7 +18,9 @@ export class GenerateQuizComponent implements OnInit {
timer
=
30
;
timer
=
30
;
selectedFile
:
File
|
null
=
null
;
selectedFile
:
File
|
null
=
null
;
fileName
=
signal
<
string
>
(
''
);
fileName
=
signal
<
string
>
(
''
);
groups
=
signal
<
any
[]
>
([]);
selectedGroups
=
signal
<
string
[]
>
([]);
assignToAll
=
signal
<
boolean
>
(
true
);
loading
=
signal
<
boolean
>
(
false
);
loading
=
signal
<
boolean
>
(
false
);
success
=
signal
<
string
>
(
''
);
success
=
signal
<
string
>
(
''
);
error
=
signal
<
string
>
(
''
);
error
=
signal
<
string
>
(
''
);
...
@@ -25,11 +28,18 @@ export class GenerateQuizComponent implements OnInit {
...
@@ -25,11 +28,18 @@ export class GenerateQuizComponent implements OnInit {
quizzes
=
signal
<
any
[]
>
([]);
quizzes
=
signal
<
any
[]
>
([]);
loadingQuizzes
=
signal
<
boolean
>
(
true
);
loadingQuizzes
=
signal
<
boolean
>
(
true
);
constructor
(
public
authService
:
AuthService
,
private
quizService
:
QuizService
)
{}
constructor
(
public
authService
:
AuthService
,
private
quizService
:
QuizService
)
{
}
ngOnInit
():
void
{
ngOnInit
():
void
{
console
.
log
(
"
Component loaded
"
);
this
.
loadQuizzes
();
this
.
loadQuizzes
();
this
.
quizService
.
getAdminGroups
().
subscribe
({
next
:
(
res
)
=>
{
this
.
groups
.
set
(
res
.
groups
||
[]);
},
error
:
()
=>
{
console
.
log
(
"
Failed to load groups
"
);
}
});
}
}
loadQuizzes
():
void
{
loadQuizzes
():
void
{
...
@@ -72,7 +82,8 @@ export class GenerateQuizComponent implements OnInit {
...
@@ -72,7 +82,8 @@ export class GenerateQuizComponent implements OnInit {
formData
.
append
(
'
title
'
,
this
.
title
);
formData
.
append
(
'
title
'
,
this
.
title
);
formData
.
append
(
'
timer
'
,
this
.
timer
.
toString
());
formData
.
append
(
'
timer
'
,
this
.
timer
.
toString
());
formData
.
append
(
'
questionsFile
'
,
this
.
selectedFile
);
formData
.
append
(
'
questionsFile
'
,
this
.
selectedFile
);
formData
.
append
(
'
assignToAll
'
,
this
.
assignToAll
().
toString
());
formData
.
append
(
'
assignedGroups
'
,
JSON
.
stringify
(
this
.
selectedGroups
()));
this
.
quizService
.
createQuiz
(
formData
).
subscribe
({
this
.
quizService
.
createQuiz
(
formData
).
subscribe
({
next
:
(
res
)
=>
{
next
:
(
res
)
=>
{
this
.
loading
.
set
(
false
);
this
.
loading
.
set
(
false
);
...
@@ -98,6 +109,15 @@ export class GenerateQuizComponent implements OnInit {
...
@@ -98,6 +109,15 @@ export class GenerateQuizComponent implements OnInit {
});
});
}
}
}
}
onGroupToggle
(
groupName
:
string
,
event
:
any
):
void
{
const
selected
=
this
.
selectedGroups
();
if
(
event
.
target
.
checked
)
{
this
.
selectedGroups
.
set
([...
selected
,
groupName
]);
}
else
{
this
.
selectedGroups
.
set
(
selected
.
filter
(
g
=>
g
!==
groupName
));
}
}
logout
():
void
{
logout
():
void
{
this
.
authService
.
logout
();
this
.
authService
.
logout
();
...
...
Frontend/src/app/pages/admin/user-history/user-history.css
View file @
d2648061
...
@@ -148,7 +148,7 @@
...
@@ -148,7 +148,7 @@
.main-content
{
.main-content
{
flex
:
1
;
flex
:
1
;
margin-left
:
26
0px
;
margin-left
:
0px
;
padding
:
24px
32px
;
padding
:
24px
32px
;
}
}
...
...
Frontend/src/app/services/auth.service.ts
View file @
d2648061
...
@@ -31,8 +31,8 @@ export class AuthService {
...
@@ -31,8 +31,8 @@ export class AuthService {
}
}
private
loadUserFromStorage
():
void
{
private
loadUserFromStorage
():
void
{
const
token
=
local
Storage
.
getItem
(
'
token
'
);
const
token
=
session
Storage
.
getItem
(
'
token
'
);
const
user
=
localStorage
.
getItem
(
'
user
'
);
const
user
=
sessionStorage
.
getItem
(
'
user
'
);
if
(
token
&&
user
)
{
if
(
token
&&
user
)
{
this
.
currentUser
.
set
(
JSON
.
parse
(
user
));
this
.
currentUser
.
set
(
JSON
.
parse
(
user
));
this
.
isLoggedIn
.
set
(
true
);
this
.
isLoggedIn
.
set
(
true
);
...
@@ -49,7 +49,7 @@ export class AuthService {
...
@@ -49,7 +49,7 @@ export class AuthService {
}
}
logout
():
void
{
logout
():
void
{
const
token
=
localStorage
.
getItem
(
'
token
'
);
const
token
=
sessionStorage
.
getItem
(
'
token
'
);
if
(
token
)
{
if
(
token
)
{
this
.
http
.
post
(
`
${
this
.
apiUrl
}
/logout`
,
{}).
subscribe
({
this
.
http
.
post
(
`
${
this
.
apiUrl
}
/logout`
,
{}).
subscribe
({
complete
:
()
=>
this
.
clearAuth
(),
complete
:
()
=>
this
.
clearAuth
(),
...
@@ -62,23 +62,23 @@ export class AuthService {
...
@@ -62,23 +62,23 @@ export class AuthService {
private
handleAuth
(
res
:
AuthResponse
):
void
{
private
handleAuth
(
res
:
AuthResponse
):
void
{
if
(
res
.
token
)
{
if
(
res
.
token
)
{
local
Storage
.
setItem
(
'
token
'
,
res
.
token
);
session
Storage
.
setItem
(
'
token
'
,
res
.
token
);
}
}
localStorage
.
setItem
(
'
user
'
,
JSON
.
stringify
(
res
.
user
));
sessionStorage
.
setItem
(
'
user
'
,
JSON
.
stringify
(
res
.
user
));
this
.
currentUser
.
set
(
res
.
user
);
this
.
currentUser
.
set
(
res
.
user
);
this
.
isLoggedIn
.
set
(
true
);
this
.
isLoggedIn
.
set
(
true
);
}
}
private
clearAuth
():
void
{
private
clearAuth
():
void
{
local
Storage
.
removeItem
(
'
token
'
);
session
Storage
.
removeItem
(
'
token
'
);
local
Storage
.
removeItem
(
'
user
'
);
session
Storage
.
removeItem
(
'
user
'
);
this
.
currentUser
.
set
(
null
);
this
.
currentUser
.
set
(
null
);
this
.
isLoggedIn
.
set
(
false
);
this
.
isLoggedIn
.
set
(
false
);
this
.
router
.
navigate
([
'
/login
'
]);
this
.
router
.
navigate
([
'
/login
'
]);
}
}
getToken
():
string
|
null
{
getToken
():
string
|
null
{
return
localStorage
.
getItem
(
'
token
'
);
return
sessionStorage
.
getItem
(
'
token
'
);
}
}
getUserRole
():
string
|
null
{
getUserRole
():
string
|
null
{
...
...
Frontend/src/app/services/quiz.service.ts
View file @
d2648061
import
{
Injectable
}
from
'
@angular/core
'
;
import
{
Injectable
}
from
'
@angular/core
'
;
import
{
HttpClient
}
from
'
@angular/common/http
'
;
import
{
HttpClient
}
from
'
@angular/common/http
'
;
import
{
Observable
}
from
'
rxjs
'
;
import
{
Observable
}
from
'
rxjs
'
;
@
Injectable
({
@
Injectable
({
providedIn
:
'
root
'
providedIn
:
'
root
'
})
})
export
class
QuizService
{
export
class
QuizService
{
private
adminUrl
=
'
http://localhost:5000/api/admin
'
;
private
adminUrl
=
'
http://localhost:5000/api/admin
'
;
private
hrUrl
=
'
http://localhost:5000/api/hr
'
;
private
hrUrl
=
'
http://localhost:5000/api/hr
'
;
private
candidateUrl
=
'
http://localhost:5000/api/candidate
'
;
private
candidateUrl
=
'
http://localhost:5000/api/candidate
'
;
constructor
(
private
http
:
HttpClient
)
{}
constructor
(
private
http
:
HttpClient
)
{}
// ========== ADMIN ENDPOINTS ==========
// ========== ADMIN ENDPOINTS ==========
getAdminStats
():
Observable
<
any
>
{
getAdminStats
():
Observable
<
any
>
{
return
this
.
http
.
get
(
`
${
this
.
adminUrl
}
/stats`
);
return
this
.
http
.
get
(
`
${
this
.
adminUrl
}
/stats`
);
}
}
getUsers
(
role
?:
string
):
Observable
<
any
>
{
getUsers
(
role
?:
string
):
Observable
<
any
>
{
const
url
=
role
?
`
${
this
.
adminUrl
}
/users?role=
${
role
}
`
:
`
${
this
.
adminUrl
}
/users`
;
const
url
=
role
?
`
${
this
.
adminUrl
}
/users?role=
${
role
}
`
:
`
${
this
.
adminUrl
}
/users`
;
return
this
.
http
.
get
(
url
);
return
this
.
http
.
get
(
url
);
}
}
getLoggedInUsers
():
Observable
<
any
>
{
getLoggedInUsers
():
Observable
<
any
>
{
return
this
.
http
.
get
(
`
${
this
.
adminUrl
}
/users/logged-in`
);
return
this
.
http
.
get
(
`
${
this
.
adminUrl
}
/users/logged-in`
);
}
}
createHRUser
(
data
:
{
name
:
string
;
email
:
string
;
password
:
string
}):
Observable
<
any
>
{
createHRUser
(
data
:
{
name
:
string
;
email
:
string
;
password
:
string
}):
Observable
<
any
>
{
return
this
.
http
.
post
(
`
${
this
.
adminUrl
}
/users/create-hr`
,
data
);
return
this
.
http
.
post
(
`
${
this
.
adminUrl
}
/users/create-hr`
,
data
);
}
}
deleteUser
(
userId
:
string
):
Observable
<
any
>
{
deleteUser
(
userId
:
string
):
Observable
<
any
>
{
return
this
.
http
.
delete
(
`
${
this
.
adminUrl
}
/users/
${
userId
}
`
);
return
this
.
http
.
delete
(
`
${
this
.
adminUrl
}
/users/
${
userId
}
`
);
}
}
getUserHistory
(
userId
:
string
):
Observable
<
any
>
{
getUserHistory
(
userId
:
string
):
Observable
<
any
>
{
return
this
.
http
.
get
(
`
${
this
.
adminUrl
}
/users/
${
userId
}
/history`
);
return
this
.
http
.
get
(
`
${
this
.
adminUrl
}
/users/
${
userId
}
/history`
);
}
}
getSubmissionDetails
(
submissionId
:
string
):
Observable
<
any
>
{
getSubmissionDetails
(
submissionId
:
string
):
Observable
<
any
>
{
return
this
.
http
.
get
(
`
${
this
.
adminUrl
}
/submissions/
${
submissionId
}
`
);
return
this
.
http
.
get
(
`
${
this
.
adminUrl
}
/submissions/
${
submissionId
}
`
);
}
}
createQuiz
(
formData
:
FormData
):
Observable
<
any
>
{
createQuiz
(
formData
:
FormData
):
Observable
<
any
>
{
return
this
.
http
.
post
(
`
${
this
.
adminUrl
}
/quiz/create`
,
formData
);
return
this
.
http
.
post
(
`
${
this
.
adminUrl
}
/quiz/create`
,
formData
);
}
}
createQuizManual
(
data
:
any
):
Observable
<
any
>
{
createQuizManual
(
data
:
any
):
Observable
<
any
>
{
return
this
.
http
.
post
(
`
${
this
.
adminUrl
}
/quiz/create-manual`
,
data
);
return
this
.
http
.
post
(
`
${
this
.
adminUrl
}
/quiz/create-manual`
,
data
);
}
}
getAdminQuizzes
():
Observable
<
any
>
{
getAdminQuizzes
():
Observable
<
any
>
{
return
this
.
http
.
get
(
`
${
this
.
adminUrl
}
/quizzes`
);
return
this
.
http
.
get
(
`
${
this
.
adminUrl
}
/quizzes`
);
}
}
getAdminQuiz
(
quizId
:
string
):
Observable
<
any
>
{
getAdminQuiz
(
quizId
:
string
):
Observable
<
any
>
{
return
this
.
http
.
get
(
`
${
this
.
adminUrl
}
/quiz/
${
quizId
}
`
);
return
this
.
http
.
get
(
`
${
this
.
adminUrl
}
/quiz/
${
quizId
}
`
);
}
}
updateQuiz
(
quizId
:
string
,
data
:
any
):
Observable
<
any
>
{
updateQuiz
(
quizId
:
string
,
data
:
any
):
Observable
<
any
>
{
return
this
.
http
.
put
(
`
${
this
.
adminUrl
}
/quiz/
${
quizId
}
`
,
data
);
return
this
.
http
.
put
(
`
${
this
.
adminUrl
}
/quiz/
${
quizId
}
`
,
data
);
}
}
assignQuiz
(
quizId
:
string
,
data
:
any
):
Observable
<
any
>
{
assignQuiz
(
quizId
:
string
,
data
:
any
):
Observable
<
any
>
{
return
this
.
http
.
put
(
`
${
this
.
adminUrl
}
/quiz/
${
quizId
}
/assign`
,
data
);
return
this
.
http
.
put
(
`
${
this
.
adminUrl
}
/quiz/
${
quizId
}
/assign`
,
data
);
}
}
deleteQuiz
(
quizId
:
string
):
Observable
<
any
>
{
deleteQuiz
(
quizId
:
string
):
Observable
<
any
>
{
return
this
.
http
.
delete
(
`
${
this
.
adminUrl
}
/quiz/
${
quizId
}
`
);
return
this
.
http
.
delete
(
`
${
this
.
adminUrl
}
/quiz/
${
quizId
}
`
);
}
}
getAdminCategories
():
Observable
<
any
>
{
getAdminCategories
():
Observable
<
any
>
{
return
this
.
http
.
get
(
`
${
this
.
adminUrl
}
/categories`
);
return
this
.
http
.
get
(
`
${
this
.
adminUrl
}
/categories`
);
}
}
getAdminGroups
():
Observable
<
any
>
{
getAdminGroups
():
Observable
<
any
>
{
return
this
.
http
.
get
(
`
${
this
.
adminUrl
}
/groups`
);
return
this
.
http
.
get
(
`
${
this
.
adminUrl
}
/groups`
);
}
}
// ========== HR ENDPOINTS ==========
// ========== HR ENDPOINTS ==========
getHRStats
():
Observable
<
any
>
{
getHRStats
():
Observable
<
any
>
{
return
this
.
http
.
get
(
`
${
this
.
hrUrl
}
/stats`
);
return
this
.
http
.
get
(
`
${
this
.
hrUrl
}
/stats`
);
}
}
getHRQuizzes
():
Observable
<
any
>
{
getHRQuizzes
():
Observable
<
any
>
{
return
this
.
http
.
get
(
`
${
this
.
hrUrl
}
/quizzes`
);
return
this
.
http
.
get
(
`
${
this
.
hrUrl
}
/quizzes`
);
}
}
getHRQuiz
(
quizId
:
string
):
Observable
<
any
>
{
getHRQuiz
(
quizId
:
string
):
Observable
<
any
>
{
return
this
.
http
.
get
(
`
${
this
.
hrUrl
}
/quiz/
${
quizId
}
`
);
return
this
.
http
.
get
(
`
${
this
.
hrUrl
}
/quiz/
${
quizId
}
`
);
}
}
createHRQuiz
(
formData
:
FormData
):
Observable
<
any
>
{
createHRQuiz
(
formData
:
FormData
):
Observable
<
any
>
{
return
this
.
http
.
post
(
`
${
this
.
hrUrl
}
/quiz/create`
,
formData
);
return
this
.
http
.
post
(
`
${
this
.
hrUrl
}
/quiz/create`
,
formData
);
}
}
createHRQuizManual
(
data
:
any
):
Observable
<
any
>
{
createHRQuizManual
(
data
:
any
):
Observable
<
any
>
{
return
this
.
http
.
post
(
`
${
this
.
hrUrl
}
/quiz/create-manual`
,
data
);
return
this
.
http
.
post
(
`
${
this
.
hrUrl
}
/quiz/create-manual`
,
data
);
}
}
updateHRQuiz
(
quizId
:
string
,
data
:
any
):
Observable
<
any
>
{
updateHRQuiz
(
quizId
:
string
,
data
:
any
):
Observable
<
any
>
{
return
this
.
http
.
put
(
`
${
this
.
hrUrl
}
/quiz/
${
quizId
}
`
,
data
);
return
this
.
http
.
put
(
`
${
this
.
hrUrl
}
/quiz/
${
quizId
}
`
,
data
);
}
}
deleteHRQuiz
(
quizId
:
string
):
Observable
<
any
>
{
deleteHRQuiz
(
quizId
:
string
):
Observable
<
any
>
{
return
this
.
http
.
delete
(
`
${
this
.
hrUrl
}
/quiz/
${
quizId
}
`
);
return
this
.
http
.
delete
(
`
${
this
.
hrUrl
}
/quiz/
${
quizId
}
`
);
}
}
getHRCandidates
():
Observable
<
any
>
{
getHRCandidates
():
Observable
<
any
>
{
return
this
.
http
.
get
(
`
${
this
.
hrUrl
}
/candidates`
);
return
this
.
http
.
get
(
`
${
this
.
hrUrl
}
/candidates`
);
}
}
getHRCandidateHistory
(
userId
:
string
):
Observable
<
any
>
{
getHRCandidateHistory
(
userId
:
string
):
Observable
<
any
>
{
return
this
.
http
.
get
(
`
${
this
.
hrUrl
}
/candidates/
${
userId
}
/history`
);
return
this
.
http
.
get
(
`
${
this
.
hrUrl
}
/candidates/
${
userId
}
/history`
);
}
}
getHRSubmissionDetails
(
submissionId
:
string
):
Observable
<
any
>
{
getHRSubmissionDetails
(
submissionId
:
string
):
Observable
<
any
>
{
return
this
.
http
.
get
(
`
${
this
.
hrUrl
}
/submissions/
${
submissionId
}
`
);
return
this
.
http
.
get
(
`
${
this
.
hrUrl
}
/submissions/
${
submissionId
}
`
);
}
}
getHRCategories
():
Observable
<
any
>
{
getHRCategories
():
Observable
<
any
>
{
return
this
.
http
.
get
(
`
${
this
.
hrUrl
}
/categories`
);
return
this
.
http
.
get
(
`
${
this
.
hrUrl
}
/categories`
);
}
}
getHRGroups
():
Observable
<
any
>
{
getHRGroups
():
Observable
<
any
>
{
return
this
.
http
.
get
(
`
${
this
.
hrUrl
}
/groups`
);
return
this
.
http
.
get
(
`
${
this
.
hrUrl
}
/groups`
);
}
}
// ========== CANDIDATE ENDPOINTS ==========
// ========== CANDIDATE ENDPOINTS ==========
getAvailableQuizzes
():
Observable
<
any
>
{
getAvailableQuizzes
():
Observable
<
any
>
{
return
this
.
http
.
get
(
`
${
this
.
candidateUrl
}
/quizzes`
);
return
this
.
http
.
get
(
`
${
this
.
candidateUrl
}
/quizzes`
);
}
}
getQuizForTaking
(
quizId
:
string
):
Observable
<
any
>
{
getQuizForTaking
(
quizId
:
string
):
Observable
<
any
>
{
return
this
.
http
.
get
(
`
${
this
.
candidateUrl
}
/quiz/
${
quizId
}
`
);
return
this
.
http
.
get
(
`
${
this
.
candidateUrl
}
/quiz/
${
quizId
}
`
);
}
}
submitQuiz
(
quizId
:
string
,
answers
:
any
[],
timeTaken
:
number
):
Observable
<
any
>
{
submitQuiz
(
quizId
:
string
,
answers
:
any
[],
timeTaken
:
number
):
Observable
<
any
>
{
return
this
.
http
.
post
(
`
${
this
.
candidateUrl
}
/quiz/
${
quizId
}
/submit`
,
{
answers
,
timeTaken
});
return
this
.
http
.
post
(
`
${
this
.
candidateUrl
}
/quiz/
${
quizId
}
/submit`
,
{
answers
,
timeTaken
});
}
}
getCandidateProfile
():
Observable
<
any
>
{
getCandidateProfile
():
Observable
<
any
>
{
return
this
.
http
.
get
(
`
${
this
.
candidateUrl
}
/profile`
);
return
this
.
http
.
get
(
`
${
this
.
candidateUrl
}
/profile`
);
}
}
getResultDetails
(
submissionId
:
string
):
Observable
<
any
>
{
getResultDetails
(
submissionId
:
string
):
Observable
<
any
>
{
return
this
.
http
.
get
(
`
${
this
.
candidateUrl
}
/results/
${
submissionId
}
`
);
return
this
.
http
.
get
(
`
${
this
.
candidateUrl
}
/results/
${
submissionId
}
`
);
}
}
}
}
Frontend/src/app/services/theme.service.ts
View file @
d2648061
...
@@ -33,6 +33,21 @@ export class ThemeService {
...
@@ -33,6 +33,21 @@ export class ThemeService {
}
}
private
applyTheme
(
theme
:
ThemeMode
):
void
{
private
applyTheme
(
theme
:
ThemeMode
):
void
{
// Existing (your custom theme)
document
.
documentElement
.
setAttribute
(
'
data-theme
'
,
theme
);
document
.
documentElement
.
setAttribute
(
'
data-theme
'
,
theme
);
// NEW (Material theme)
const
body
=
document
.
body
;
body
.
classList
.
remove
(
'
azure-theme
'
,
'
magenta-theme
'
,
'
cyan-theme
'
,
'
rose-theme
'
);
// Map your themes → material themes
const
themeMap
:
Record
<
ThemeMode
,
string
>
=
{
light
:
'
azure-theme
'
,
dark
:
'
magenta-theme
'
,
blue
:
'
cyan-theme
'
};
body
.
classList
.
add
(
themeMap
[
theme
]
||
'
azure-theme
'
);
}
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment