Public Access
feat: add MyExams, MyResults, Settings, and Workspace components
Release Pipeline / build-and-release (push) Failing after 41s
Release Pipeline / build-and-release (push) Failing after 41s
- Implemented MyExams component for managing user exams, including loading, deleting, and creating new exams. - Created MyResults component to display user results with options to review and delete results. - Developed Settings component for updating user display name and changing password. - Introduced Workspace component to serve as a container for MyExams, MyResults, and Settings, with navigation between tabs.
This commit is contained in:
1 parent
ad777ef1c3
commit
62af83ce53
16 files changed
+2192
-47
No files matched your search
@@ -266,4 +266,68 @@ router.get('/me', requireAuth, (req, res) => {
|
||||
})
|
||||
})
|
||||
|
||||
// ─── Update Current User ─────────────────────────────────
|
||||
router.patch('/me', requireAuth, (req, res) => {
|
||||
try {
|
||||
const { displayName } = req.body
|
||||
|
||||
if (typeof displayName !== 'string' || displayName.trim().length < 1 || displayName.length > 100) {
|
||||
res.status(400).json({ error: 'Display name is required (max 100 characters)' })
|
||||
return
|
||||
}
|
||||
|
||||
const db = getDb()
|
||||
db.prepare(`
|
||||
UPDATE users SET display_name = ?, updated_at = unixepoch() WHERE id = ?
|
||||
`).run(displayName.trim(), req.user!.userId)
|
||||
|
||||
res.json({ message: 'Profile updated successfully' })
|
||||
} catch (err) {
|
||||
console.error('Update profile error:', err)
|
||||
res.status(500).json({ error: 'Internal server error' })
|
||||
}
|
||||
})
|
||||
|
||||
// ─── Change Password ─────────────────────────────────────
|
||||
router.post('/change-password', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { currentPassword, newPassword } = req.body
|
||||
|
||||
if (!currentPassword || !newPassword) {
|
||||
res.status(400).json({ error: 'Current password and new password are required' })
|
||||
return
|
||||
}
|
||||
if (typeof newPassword !== 'string' || newPassword.length < 8) {
|
||||
res.status(400).json({ error: 'New password must be at least 8 characters' })
|
||||
return
|
||||
}
|
||||
|
||||
const db = getDb()
|
||||
const user = db.prepare('SELECT password_hash FROM users WHERE id = ?').get(
|
||||
req.user!.userId
|
||||
) as Record<string, unknown> | undefined
|
||||
|
||||
if (!user) {
|
||||
res.status(404).json({ error: 'User not found' })
|
||||
return
|
||||
}
|
||||
|
||||
const valid = await bcrypt.compare(currentPassword as string, user['password_hash'] as string)
|
||||
if (!valid) {
|
||||
res.status(401).json({ error: 'Current password is incorrect' })
|
||||
return
|
||||
}
|
||||
|
||||
const passwordHash = await bcrypt.hash(newPassword, BCRYPT_ROUNDS)
|
||||
db.prepare(`
|
||||
UPDATE users SET password_hash = ?, updated_at = unixepoch() WHERE id = ?
|
||||
`).run(passwordHash, req.user!.userId)
|
||||
|
||||
res.json({ message: 'Password changed successfully' })
|
||||
} catch (err) {
|
||||
console.error('Change password error:', err)
|
||||
res.status(500).json({ error: 'Internal server error' })
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -76,6 +76,35 @@ router.post('/', requireAuth, (req, res) => {
|
||||
}
|
||||
})
|
||||
|
||||
// ─── Update Exam ─────────────────────────────────────────
|
||||
router.put('/:id', requireAuth, (req, res) => {
|
||||
try {
|
||||
const { exam } = req.body
|
||||
|
||||
if (!exam || !exam.title || !exam.questions?.length) {
|
||||
res.status(400).json({ error: 'Valid exam data with title and questions is required' })
|
||||
return
|
||||
}
|
||||
|
||||
const db = getDb()
|
||||
const result = db.prepare(`
|
||||
UPDATE saved_exams
|
||||
SET title = ?, subtitle = ?, exam_json = ?, updated_at = unixepoch()
|
||||
WHERE id = ? AND user_id = ?
|
||||
`).run(exam.title, exam.subtitle ?? null, JSON.stringify(exam), Number(req.params['id']), req.user!.userId)
|
||||
|
||||
if (result.changes === 0) {
|
||||
res.status(404).json({ error: 'Exam not found' })
|
||||
return
|
||||
}
|
||||
|
||||
res.json({ message: 'Exam updated successfully' })
|
||||
} catch (err) {
|
||||
console.error('Update exam error:', err)
|
||||
res.status(500).json({ error: 'Internal server error' })
|
||||
}
|
||||
})
|
||||
|
||||
// ─── Delete Exam ─────────────────────────────────────────
|
||||
router.delete('/:id', requireAuth, (req, res) => {
|
||||
try {
|
||||
|
||||
Reference in new issue
Block a user