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
Show 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,8 +43,8 @@ const quizSchema = new mongoose.Schema({
...
@@ -43,8 +43,8 @@ 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
,
...
...
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
'
]
}
};
...
@@ -29,12 +29,12 @@ router.get('/users', async (req, res) => {
...
@@ -29,12 +29,12 @@ router.get('/users', async (req, res) => {
}
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
// @route GET /api/admin/users/logged-in
// @desc Get all currently logged-in users
// @desc Get all currently logged-in users
// @access Admin
// @access Admin
router
.
get
(
'
/users/logged-in
'
,
async
(
req
,
res
)
=>
{
router
.
get
(
'
/users/logged-in
'
,
async
(
req
,
res
)
=>
{
try
{
try
{
const
users
=
await
User
.
find
({
role
:
{
$in
:
[
'
candidate
'
,
'
hr
'
]
},
isLoggedIn
:
true
})
const
users
=
await
User
.
find
({
role
:
{
$in
:
[
'
candidate
'
,
'
hr
'
]
},
isLoggedIn
:
true
})
.
select
(
'
-password
'
)
.
select
(
'
-password
'
)
...
@@ -44,12 +44,12 @@ router.get('/users/logged-in', async (req, res) => {
...
@@ -44,12 +44,12 @@ router.get('/users/logged-in', async (req, res) => {
}
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 POST /api/admin/users/create-hr
// @route POST /api/admin/users/create-hr
// @desc Create an HR user
// @desc Create an HR user
// @access Admin
// @access Admin
router
.
post
(
'
/users/create-hr
'
,
async
(
req
,
res
)
=>
{
router
.
post
(
'
/users/create-hr
'
,
async
(
req
,
res
)
=>
{
try
{
try
{
const
{
name
,
email
,
password
}
=
req
.
body
;
const
{
name
,
email
,
password
}
=
req
.
body
;
...
@@ -76,12 +76,12 @@ router.post('/users/create-hr', async (req, res) => {
...
@@ -76,12 +76,12 @@ router.post('/users/create-hr', async (req, res) => {
}
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 DELETE /api/admin/users/:userId
// @route DELETE /api/admin/users/:userId
// @desc Delete a user
// @desc Delete a user
// @access Admin
// @access Admin
router
.
delete
(
'
/users/:userId
'
,
async
(
req
,
res
)
=>
{
router
.
delete
(
'
/users/:userId
'
,
async
(
req
,
res
)
=>
{
try
{
try
{
const
{
userId
}
=
req
.
params
;
const
{
userId
}
=
req
.
params
;
const
user
=
await
User
.
findById
(
userId
);
const
user
=
await
User
.
findById
(
userId
);
...
@@ -95,12 +95,12 @@ router.delete('/users/:userId', async (req, res) => {
...
@@ -95,12 +95,12 @@ router.delete('/users/:userId', async (req, res) => {
}
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/:userId/history
// @route GET /api/admin/users/:userId/history
// @desc Get a user's test history
// @desc Get a user's test history
// @access Admin
// @access Admin
router
.
get
(
'
/users/:userId/history
'
,
async
(
req
,
res
)
=>
{
router
.
get
(
'
/users/:userId/history
'
,
async
(
req
,
res
)
=>
{
try
{
try
{
const
{
userId
}
=
req
.
params
;
const
{
userId
}
=
req
.
params
;
...
@@ -117,12 +117,12 @@ router.get('/users/:userId/history', async (req, res) => {
...
@@ -117,12 +117,12 @@ router.get('/users/:userId/history', async (req, res) => {
}
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/submissions/:submissionId
// @route GET /api/admin/submissions/:submissionId
// @desc Get detailed submission - answers, correct answers, scores
// @desc Get detailed submission - answers, correct answers, scores
// @access Admin
// @access Admin
router
.
get
(
'
/submissions/:submissionId
'
,
async
(
req
,
res
)
=>
{
router
.
get
(
'
/submissions/:submissionId
'
,
async
(
req
,
res
)
=>
{
try
{
try
{
const
{
submissionId
}
=
req
.
params
;
const
{
submissionId
}
=
req
.
params
;
...
@@ -172,14 +172,14 @@ router.get('/submissions/:submissionId', async (req, res) => {
...
@@ -172,14 +172,14 @@ router.get('/submissions/:submissionId', async (req, res) => {
}
catch
(
error
)
{
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
}
});
});
// ============ QUIZ MANAGEMENT ============
// ============ QUIZ MANAGEMENT ============
// @route POST /api/admin/quiz/create
// @route POST /api/admin/quiz/create
// @desc Create quiz with Excel upload
// @desc Create quiz with Excel upload
// @access Admin
// @access Admin
router
.
post
(
'
/quiz/create
'
,
upload
.
single
(
'
questionsFile
'
),
async
(
req
,
res
)
=>
{
router
.
post
(
'
/quiz/create
'
,
upload
.
single
(
'
questionsFile
'
),
async
(
req
,
res
)
=>
{
try
{
try
{
const
{
title
,
timer
,
category
,
difficulty
,
topic
,
assignToAll
,
assignees
,
assignedGroups
}
=
req
.
body
;
const
{
title
,
timer
,
category
,
difficulty
,
topic
,
assignToAll
,
assignees
,
assignedGroups
}
=
req
.
body
;
...
@@ -190,7 +190,8 @@ router.post('/quiz/create', upload.single('questionsFile'), async (req, res) =>
...
@@ -190,7 +190,8 @@ router.post('/quiz/create', upload.single('questionsFile'), async (req, res) =>
if
(
!
req
.
file
)
{
if
(
!
req
.
file
)
{
return
res
.
status
(
400
).
json
({
message
:
'
Please upload an Excel file with questions
'
});
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
// Parse Excel file
const
workbook
=
xlsx
.
readFile
(
req
.
file
.
path
);
const
workbook
=
xlsx
.
readFile
(
req
.
file
.
path
);
const
sheetName
=
workbook
.
SheetNames
[
0
];
const
sheetName
=
workbook
.
SheetNames
[
0
];
...
@@ -229,6 +230,7 @@ router.post('/quiz/create', upload.single('questionsFile'), async (req, res) =>
...
@@ -229,6 +230,7 @@ router.post('/quiz/create', upload.single('questionsFile'), async (req, res) =>
// Parse and create questions
// Parse and create questions
const
questions
=
parseExcelQuestions
(
data
,
quiz
.
_id
);
const
questions
=
parseExcelQuestions
(
data
,
quiz
.
_id
);
console
.
log
(
"
PARSED QUESTIONS:
"
,
questions
);
await
Question
.
insertMany
(
questions
);
await
Question
.
insertMany
(
questions
);
// Clean up uploaded file
// Clean up uploaded file
...
@@ -250,12 +252,12 @@ router.post('/quiz/create', upload.single('questionsFile'), async (req, res) =>
...
@@ -250,12 +252,12 @@ router.post('/quiz/create', upload.single('questionsFile'), async (req, res) =>
}
}
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 POST /api/admin/quiz/create-manual
// @desc Create quiz manually (for AI-generated quizzes or manual entry)
// @desc Create quiz manually (for AI-generated quizzes or manual entry)
// @access Admin
// @access Admin
router
.
post
(
'
/quiz/create-manual
'
,
async
(
req
,
res
)
=>
{
router
.
post
(
'
/quiz/create-manual
'
,
async
(
req
,
res
)
=>
{
try
{
try
{
const
{
title
,
timer
,
category
,
difficulty
,
topic
,
assignToAll
,
assignees
,
assignedGroups
,
questions
,
generatedByAI
}
=
req
.
body
;
const
{
title
,
timer
,
category
,
difficulty
,
topic
,
assignToAll
,
assignees
,
assignedGroups
,
questions
,
generatedByAI
}
=
req
.
body
;
...
@@ -306,12 +308,12 @@ router.post('/quiz/create-manual', async (req, res) => {
...
@@ -306,12 +308,12 @@ router.post('/quiz/create-manual', async (req, res) => {
}
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/quizzes
// @route GET /api/admin/quizzes
// @desc Get all quizzes with attempt count
// @desc Get all quizzes with attempt count
// @access Admin
// @access Admin
router
.
get
(
'
/quizzes
'
,
async
(
req
,
res
)
=>
{
router
.
get
(
'
/quizzes
'
,
async
(
req
,
res
)
=>
{
try
{
try
{
const
quizzes
=
await
Quiz
.
find
()
const
quizzes
=
await
Quiz
.
find
()
.
populate
(
'
createdBy
'
,
'
name email
'
)
.
populate
(
'
createdBy
'
,
'
name email
'
)
...
@@ -329,12 +331,12 @@ router.get('/quizzes', async (req, res) => {
...
@@ -329,12 +331,12 @@ router.get('/quizzes', async (req, res) => {
}
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/quiz/:quizId
// @route GET /api/admin/quiz/:quizId
// @desc Get quiz with its questions for editing
// @desc Get quiz with its questions for editing
// @access Admin
// @access Admin
router
.
get
(
'
/quiz/:quizId
'
,
async
(
req
,
res
)
=>
{
router
.
get
(
'
/quiz/:quizId
'
,
async
(
req
,
res
)
=>
{
try
{
try
{
const
{
quizId
}
=
req
.
params
;
const
{
quizId
}
=
req
.
params
;
const
quiz
=
await
Quiz
.
findById
(
quizId
)
const
quiz
=
await
Quiz
.
findById
(
quizId
)
...
@@ -352,12 +354,12 @@ router.get('/quiz/:quizId', async (req, res) => {
...
@@ -352,12 +354,12 @@ router.get('/quiz/:quizId', async (req, res) => {
}
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
// @desc Edit quiz (only if no attempts have been made)
// @desc Edit quiz (only if no attempts have been made)
// @access Admin
// @access Admin
router
.
put
(
'
/quiz/:quizId
'
,
async
(
req
,
res
)
=>
{
router
.
put
(
'
/quiz/:quizId
'
,
async
(
req
,
res
)
=>
{
try
{
try
{
const
{
quizId
}
=
req
.
params
;
const
{
quizId
}
=
req
.
params
;
...
@@ -395,7 +397,7 @@ router.put('/quiz/:quizId', async (req, res) => {
...
@@ -395,7 +397,7 @@ router.put('/quiz/:quizId', async (req, res) => {
type
:
q
.
correctAnswers
.
length
>
1
?
'
mcq
'
:
'
single
'
type
:
q
.
correctAnswers
.
length
>
1
?
'
mcq
'
:
'
single
'
}));
}));
await
Question
.
insertMany
(
questionDoc
s
);
await
Question
.
insertMany
(
question
s
);
updateData
.
totalQuestions
=
questions
.
length
;
updateData
.
totalQuestions
=
questions
.
length
;
}
}
...
@@ -405,12 +407,12 @@ router.put('/quiz/:quizId', async (req, res) => {
...
@@ -405,12 +407,12 @@ router.put('/quiz/:quizId', async (req, res) => {
}
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/assign
// @route PUT /api/admin/quiz/:quizId/assign
// @desc Assign quiz to users/groups
// @desc Assign quiz to users/groups
// @access Admin
// @access Admin
router
.
put
(
'
/quiz/:quizId/assign
'
,
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
;
const
{
assignToAll
,
assignees
,
assignedGroups
}
=
req
.
body
;
...
@@ -427,12 +429,12 @@ router.put('/quiz/:quizId/assign', async (req, res) => {
...
@@ -427,12 +429,12 @@ router.put('/quiz/:quizId/assign', async (req, res) => {
}
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 DELETE /api/admin/quiz/:quizId
// @route DELETE /api/admin/quiz/:quizId
// @desc Delete a quiz (only if no attempts have been made)
// @desc Delete a quiz (only if no attempts have been made)
// @access Admin
// @access Admin
router
.
delete
(
'
/quiz/:quizId
'
,
async
(
req
,
res
)
=>
{
router
.
delete
(
'
/quiz/:quizId
'
,
async
(
req
,
res
)
=>
{
try
{
try
{
const
{
quizId
}
=
req
.
params
;
const
{
quizId
}
=
req
.
params
;
...
@@ -452,36 +454,65 @@ router.delete('/quiz/:quizId', async (req, res) => {
...
@@ -452,36 +454,65 @@ router.delete('/quiz/:quizId', async (req, res) => {
}
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/categories
// @route GET /api/admin/categories
// @desc Get all unique quiz categories
// @desc Get all unique quiz categories
// @access Admin
// @access Admin
router
.
get
(
'
/categories
'
,
async
(
req
,
res
)
=>
{
router
.
get
(
'
/categories
'
,
async
(
req
,
res
)
=>
{
try
{
try
{
const
categories
=
await
Quiz
.
distinct
(
'
category
'
);
const
categories
=
await
Quiz
.
distinct
(
'
category
'
);
res
.
json
({
categories
});
res
.
json
({
categories
});
}
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/groups
// @route GET /api/admin/groups
// @desc Get all unique user groups
// @desc Get all unique user groups
// @access Admin
// @access Admin
router
.
get
(
'
/groups
'
,
async
(
req
,
res
)
=>
{
router
.
get
(
'
/groups
'
,
async
(
req
,
res
)
=>
{
try
{
try
{
const
groups
=
await
User
.
distinct
(
'
group
'
);
const
groups
=
await
User
.
distinct
(
'
group
'
);
res
.
json
({
groups
});
res
.
json
({
groups
});
}
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/stats
// @route POST /api/admin/groups
// @desc Get dashboard statistics
// @desc Create a new group
// @access Admin
// @access Admin
router
.
get
(
'
/stats
'
,
async
(
req
,
res
)
=>
{
router
.
post
(
'
/groups
'
,
async
(
req
,
res
)
=>
{
try
{
const
{
name
}
=
req
.
body
;
if
(
!
name
||
!
name
.
trim
())
{
return
res
.
status
(
400
).
json
({
message
:
'
Group name is required
'
});
}
// Check if already exists
const
existing
=
await
User
.
findOne
({
group
:
name
.
trim
()
});
if
(
existing
)
{
return
res
.
status
(
400
).
json
({
message
:
'
Group already exists
'
});
}
// ⚠️ Since you're using group as string,
// we don't store separately — just return success
res
.
status
(
201
).
json
({
message
:
'
Group created successfully
'
,
group
:
name
.
trim
()
});
}
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
{
try
{
const
totalUsers
=
await
User
.
countDocuments
({
role
:
'
candidate
'
});
const
totalUsers
=
await
User
.
countDocuments
({
role
:
'
candidate
'
});
const
totalHR
=
await
User
.
countDocuments
({
role
:
'
hr
'
});
const
totalHR
=
await
User
.
countDocuments
({
role
:
'
hr
'
});
...
@@ -503,11 +534,11 @@ router.get('/stats', async (req, res) => {
...
@@ -503,11 +534,11 @@ router.get('/stats', async (req, res) => {
}
catch
(
error
)
{
}
catch
(
error
)
{
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
res
.
status
(
500
).
json
({
message
:
'
Server error
'
,
error
:
error
.
message
});
}
}
});
});
// ============ HELPER FUNCTIONS ============
// ============ HELPER FUNCTIONS ============
function
parseExcelQuestions
(
data
,
quizId
)
{
function
parseExcelQuestions
(
data
,
quizId
)
{
return
data
.
map
(
row
=>
{
return
data
.
map
(
row
=>
{
const
question
=
row
[
'
Question
'
]
||
row
[
'
question
'
]
||
''
;
const
question
=
row
[
'
Question
'
]
||
row
[
'
question
'
]
||
''
;
const
option1
=
row
[
'
Option1
'
]
||
row
[
'
option1
'
]
||
row
[
'
Option 1
'
]
||
''
;
const
option1
=
row
[
'
Option1
'
]
||
row
[
'
option1
'
]
||
row
[
'
Option 1
'
]
||
''
;
...
@@ -516,11 +547,24 @@ function parseExcelQuestions(data, quizId) {
...
@@ -516,11 +547,24 @@ function parseExcelQuestions(data, quizId) {
const
option4
=
row
[
'
Option4
'
]
||
row
[
'
option4
'
]
||
row
[
'
Option 4
'
]
||
''
;
const
option4
=
row
[
'
Option4
'
]
||
row
[
'
option4
'
]
||
row
[
'
Option 4
'
]
||
''
;
const
correct
=
row
[
'
Correct
'
]
||
row
[
'
correct
'
]
||
row
[
'
Answer
'
]
||
row
[
'
answer
'
]
||
''
;
const
correct
=
row
[
'
Correct
'
]
||
row
[
'
correct
'
]
||
row
[
'
Answer
'
]
||
row
[
'
answer
'
]
||
''
;
const
options
=
[
option1
.
toString
().
trim
(),
option2
.
toString
().
trim
(),
option3
.
toString
().
trim
(),
option4
.
toString
().
trim
()
];
const
correctStr
=
correct
.
toString
().
trim
();
const
correctStr
=
correct
.
toString
().
trim
();
const
correctAnswers
=
correctStr
.
includes
(
'
,
'
)
const
correctArr
=
correctStr
.
split
(
'
,
'
).
map
(
s
=>
s
.
trim
());
?
correctStr
.
split
(
'
,
'
).
map
(
a
=>
a
.
trim
())
:
[
correctStr
];
// 🔥 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
'
;
const
type
=
correctAnswers
.
length
>
1
?
'
mcq
'
:
'
single
'
;
return
{
return
{
...
@@ -536,13 +580,13 @@ function parseExcelQuestions(data, quizId) {
...
@@ -536,13 +580,13 @@ function parseExcelQuestions(data, quizId) {
type
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
);
...
@@ -41,27 +41,27 @@ app.use(cors({
...
@@ -41,27 +41,27 @@ app.use(cors({
credentials
:
true
,
credentials
:
true
,
methods
:
[
'
GET
'
,
'
POST
'
,
'
PUT
'
,
'
DELETE
'
,
'
OPTIONS
'
],
methods
:
[
'
GET
'
,
'
POST
'
,
'
PUT
'
,
'
DELETE
'
,
'
OPTIONS
'
],
allowedHeaders
:
[
'
Content-Type
'
,
'
Authorization
'
]
allowedHeaders
:
[
'
Content-Type
'
,
'
Authorization
'
]
}));
}));
app
.
use
(
express
.
json
());
app
.
use
(
express
.
json
());
app
.
use
(
express
.
urlencoded
({
extended
:
true
}));
app
.
use
(
express
.
urlencoded
({
extended
:
true
}));
app
.
use
(
cookieParser
());
app
.
use
(
cookieParser
());
// Routes
// Routes
app
.
use
(
'
/api/auth
'
,
require
(
'
./routes/auth
'
));
app
.
use
(
'
/api/auth
'
,
require
(
'
./routes/auth
'
));
app
.
use
(
'
/api/admin
'
,
require
(
'
./routes/admin
'
));
app
.
use
(
'
/api/admin
'
,
require
(
'
./routes/admin
'
));
app
.
use
(
'
/api/hr
'
,
require
(
'
./routes/hr
'
));
app
.
use
(
'
/api/hr
'
,
require
(
'
./routes/hr
'
));
app
.
use
(
'
/api/candidate
'
,
require
(
'
./routes/candidate
'
));
app
.
use
(
'
/api/candidate
'
,
require
(
'
./routes/candidate
'
));
// Keep backward compatibility for old student endpoints
// Keep backward compatibility for old student endpoints
app
.
use
(
'
/api/student
'
,
require
(
'
./routes/candidate
'
));
app
.
use
(
'
/api/student
'
,
require
(
'
./routes/candidate
'
));
// Health check
// Health check
app
.
get
(
'
/api/health
'
,
(
req
,
res
)
=>
{
app
.
get
(
'
/api/health
'
,
(
req
,
res
)
=>
{
res
.
json
({
status
:
'
OK
'
,
message
:
'
QuizMaster Pro API is running
'
});
res
.
json
({
status
:
'
OK
'
,
message
:
'
QuizMaster Pro API is running
'
});
});
});
// Error handling middleware
// Error handling middleware
app
.
use
((
err
,
req
,
res
,
next
)
=>
{
app
.
use
((
err
,
req
,
res
,
next
)
=>
{
console
.
error
(
err
.
stack
);
console
.
error
(
err
.
stack
);
if
(
err
.
name
===
'
MulterError
'
)
{
if
(
err
.
name
===
'
MulterError
'
)
{
...
@@ -72,10 +72,10 @@ app.use((err, req, res, next) => {
...
@@ -72,10 +72,10 @@ app.use((err, req, res, next) => {
}
}
res
.
status
(
500
).
json
({
message
:
'
Something went wrong!
'
,
error
:
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,20 +56,40 @@ export class EditQuizComponent implements OnInit {
...
@@ -56,20 +56,40 @@ export class EditQuizComponent implements OnInit {
}
}
});
});
}
}
onSave
():
void
{
if
(
!
this
.
title
.
trim
())
{
this
.
error
.
set
(
'
Title is required
'
);
return
;
}
onSave
():
void
{
if
(
!
this
.
title
.
trim
())
{
this
.
error
.
set
(
'
Title is required
'
);
return
;
}
this
.
saving
.
set
(
true
);
this
.
saving
.
set
(
true
);
this
.
error
.
set
(
''
);
this
.
error
.
set
(
''
);
// 🔥 Format questions correctly
const
formattedQuestions
=
this
.
questions
().
map
(
q
=>
{
const
options
=
q
.
options
;
const
correctIndex
=
options
.
findIndex
(
(
opt
:
string
)
=>
opt
==
q
.
correctAnswer
);
return
{
question
:
q
.
question
,
options
,
correctAnswers
:
[
correctIndex
.
toString
()],
type
:
'
single
'
};
});
// ✅ closes map()
// 🔥 Final data
const
data
=
{
const
data
=
{
title
:
this
.
title
,
title
:
this
.
title
,
timer
:
this
.
timer
,
timer
:
this
.
timer
,
category
:
this
.
category
,
category
:
this
.
category
,
difficulty
:
this
.
difficulty
,
difficulty
:
this
.
difficulty
,
topic
:
this
.
topic
,
topic
:
this
.
topic
,
questions
:
this
.
questions
()
questions
:
formattedQuestions
};
};
// ✅ closes data object
this
.
quizService
.
updateQuiz
(
this
.
quizId
,
data
).
subscribe
({
this
.
quizService
.
updateQuiz
(
this
.
quizId
,
data
).
subscribe
({
next
:
()
=>
{
next
:
()
=>
{
...
@@ -81,9 +101,8 @@ export class EditQuizComponent implements OnInit {
...
@@ -81,9 +101,8 @@ export class EditQuizComponent implements OnInit {
this
.
saving
.
set
(
false
);
this
.
saving
.
set
(
false
);
this
.
error
.
set
(
err
.
error
?.
message
||
'
Failed to update quiz
'
);
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>
...
@@ -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>
...
@@ -59,20 +63,44 @@
...
@@ -59,20 +63,44 @@
</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>
...
...
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
'
;
...
@@ -150,4 +150,4 @@ export class QuizService {
...
@@ -150,4 +150,4 @@ export class QuizService {
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