|
| 1 | +import { Response } from 'express'; |
| 2 | +import jwt, { JwtPayload } from 'jsonwebtoken'; |
| 3 | +import historyEntryModel from '../models/HistoryEntry'; |
| 4 | +import { AuthenticatedRequest } from 'middlewares/auth'; |
| 5 | + |
| 6 | +const getErrorMessage = (error: unknown): string => { |
| 7 | + if (error instanceof Error) return error.message; |
| 8 | + return 'An unexpected error occurred'; |
| 9 | +}; |
| 10 | + |
| 11 | +const extractUserIdFromToken = (req: AuthenticatedRequest): string | null => { |
| 12 | + const userId = req.userId; |
| 13 | + if (!userId) { |
| 14 | + console.error('userId missing - Token is likely invalid'); |
| 15 | + return null; |
| 16 | + } |
| 17 | + return userId |
| 18 | + }; |
| 19 | + |
| 20 | +export const getUserHistoryEntries = async (req: AuthenticatedRequest, res: Response) => { |
| 21 | + try { |
| 22 | + const userId = extractUserIdFromToken(req); |
| 23 | + |
| 24 | + if (!userId) { |
| 25 | + return res.status(401).json({ error: 'Invalid or missing token' }); |
| 26 | + } |
| 27 | + |
| 28 | + const historyEntries = await historyEntryModel.find({ userId }) |
| 29 | + .populate({ |
| 30 | + path: 'question', |
| 31 | + populate: { |
| 32 | + path: 'categories', |
| 33 | + model: 'category', |
| 34 | + }, |
| 35 | + }); |
| 36 | + const historyViewModels = historyEntries.map((entry) => { |
| 37 | + return { |
| 38 | + id: entry._id, |
| 39 | + key: entry._id, |
| 40 | + attemptStartedAt: entry.attemptStartedAt.getTime(), |
| 41 | + attemptCompletedAt: entry.attemptCompletedAt.getTime(), |
| 42 | + title: entry.question.title, |
| 43 | + difficulty: entry.question.difficulty, |
| 44 | + topics: entry.question.categories.map((cat: any) => cat.name), |
| 45 | + attemptCode: entry.attemptCode, |
| 46 | + }}); |
| 47 | + res.status(200).json(historyViewModels); |
| 48 | + } catch (error) { |
| 49 | + res.status(500).json({ error: getErrorMessage(error) }); |
| 50 | + } |
| 51 | +}; |
| 52 | + |
| 53 | +export const createOrUpdateUserHistoryEntry = async (req: AuthenticatedRequest, res: Response) => { |
| 54 | + try { |
| 55 | + const userId = extractUserIdFromToken(req); |
| 56 | + |
| 57 | + if (!userId) { |
| 58 | + return res.status(401).json({ error: 'Invalid or missing token' }); |
| 59 | + } |
| 60 | + |
| 61 | + const { questionId, roomId, attemptStartedAt, attemptCompletedAt, collaboratorId, attemptCode } = req.body; |
| 62 | + |
| 63 | + if (!roomId) { |
| 64 | + return res.status(400).json({ error: 'roomId is required' }); |
| 65 | + } |
| 66 | + |
| 67 | + const existingEntry = await historyEntryModel.findOne({ userId, roomId }); |
| 68 | + |
| 69 | + if (existingEntry) { |
| 70 | + existingEntry.question = questionId; |
| 71 | + existingEntry.attemptStartedAt = attemptStartedAt; |
| 72 | + existingEntry.attemptCompletedAt = attemptCompletedAt; |
| 73 | + existingEntry.collaboratorId = collaboratorId; |
| 74 | + existingEntry.attemptCode = attemptCode; |
| 75 | + |
| 76 | + const updatedEntry = await existingEntry.save(); |
| 77 | + |
| 78 | + return res.status(200).json(updatedEntry); |
| 79 | + } else { |
| 80 | + const newHistoryEntry = new historyEntryModel({ |
| 81 | + userId, |
| 82 | + question: questionId, |
| 83 | + roomId, |
| 84 | + attemptStartedAt, |
| 85 | + attemptCompletedAt, |
| 86 | + collaboratorId, |
| 87 | + attemptCode, |
| 88 | + }); |
| 89 | + |
| 90 | + const savedEntry = await newHistoryEntry.save(); |
| 91 | + |
| 92 | + return res.status(201).json(savedEntry); |
| 93 | + } |
| 94 | + } catch (error) { |
| 95 | + return res.status(500).json({ error: getErrorMessage(error) }); |
| 96 | + } |
| 97 | +}; |
| 98 | + |
| 99 | +export const deleteUserHistoryEntry = async (req: AuthenticatedRequest, res: Response) => { |
| 100 | + try { |
| 101 | + const userId = extractUserIdFromToken(req); |
| 102 | + |
| 103 | + if (!userId) { |
| 104 | + return res.status(401).json({ error: 'Invalid or missing token' }); |
| 105 | + } |
| 106 | + |
| 107 | + const { id } = req.params; |
| 108 | + |
| 109 | + const deletedEntry = await historyEntryModel.findOneAndDelete({ _id: id, userId }); |
| 110 | + |
| 111 | + if (!deletedEntry) { |
| 112 | + return res.status(404).json({ message: 'History entry not found' }); |
| 113 | + } |
| 114 | + |
| 115 | + res.status(200).json({ message: 'History entry deleted successfully' }); |
| 116 | + } catch (error) { |
| 117 | + res.status(500).json({ error: getErrorMessage(error) }); |
| 118 | + } |
| 119 | +}; |
| 120 | + |
| 121 | +export const deleteUserHistoryEntries = async (req: AuthenticatedRequest, res: Response) => { |
| 122 | + try { |
| 123 | + const userId = extractUserIdFromToken(req); |
| 124 | + |
| 125 | + if (!userId) { |
| 126 | + return res.status(401).json({ error: 'Invalid or missing token' }); |
| 127 | + } |
| 128 | + |
| 129 | + const { ids } = req.body; |
| 130 | + if (!Array.isArray(ids)) { |
| 131 | + return res.status(400).json({ message: '"ids" must be an array of history entry IDs.' }); |
| 132 | + } |
| 133 | + |
| 134 | + const result = await historyEntryModel.deleteMany({ _id: { $in: ids }, userId }); |
| 135 | + res.status(200).json({ message: `${result.deletedCount} history entries deleted.` }); |
| 136 | + } catch (error) { |
| 137 | + res.status(500).json({ error: getErrorMessage(error) }); |
| 138 | + } |
| 139 | +}; |
| 140 | + |
| 141 | +export const deleteAllUserHistoryEntries = async (req: AuthenticatedRequest, res: Response) => { |
| 142 | + try { |
| 143 | + const userId = extractUserIdFromToken(req); |
| 144 | + |
| 145 | + if (!userId) { |
| 146 | + return res.status(401).json({ error: 'Invalid or missing token' }); |
| 147 | + } |
| 148 | + |
| 149 | + const result = await historyEntryModel.deleteMany({ userId }); |
| 150 | + res.status(200).json({ |
| 151 | + message: `${result.deletedCount} history entries deleted for user ${userId}.`, |
| 152 | + }); |
| 153 | + } catch (error) { |
| 154 | + res.status(500).json({ error: getErrorMessage(error) }); |
| 155 | + } |
| 156 | +}; |
0 commit comments