/
TimurIsm
/
OpenEyes
Обзор
Документация
Войти
/
TimurIsm
/
OpenEyes
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
back/controllers/resultsController.js
506 строк
21 KB
TimurIsm
first_commit
16 май 2026, 15:34
16 май 2026, 15:34
4d3f198
Код
Авторство
О чём код?
const { UserTestResult, VisionTest, TestQuestion, Articles, Question, Answer } = require('../models/models'); const ApiError = require('../error/ApiError'); class ResultsController { async saveTestResult(req, res, next) { try { const userId = req.user.id; const { testId, title, userAnswers, result } = req.body; if (!title || !userAnswers || !result) { return next(ApiError.badRequest('Не все обязательные поля заполнены!')); } let parsedAnswers; try { parsedAnswers = typeof userAnswers === 'string' ? JSON.parse(userAnswers) : userAnswers; } catch (e) { return next(ApiError.badRequest('Некорректный формат ответов!')); } let finalUserAnswers; if (testId) { const test = await VisionTest.findOne({ where: { id: testId }, include: [{ model: TestQuestion, as: 'questions', attributes: ['id'], order: [['id', 'ASC']] }] }); if (test && test.questions) { const questionOrder = test.questions.map(q => q.id); if (parsedAnswers.answers && Array.isArray(parsedAnswers.answers)) { finalUserAnswers = { answers: parsedAnswers.answers, questionOrder: questionOrder, savedAt: new Date().toISOString() }; } else if (Array.isArray(parsedAnswers)) { finalUserAnswers = { answers: parsedAnswers, questionOrder: questionOrder, savedAt: new Date().toISOString() }; } else if (parsedAnswers && typeof parsedAnswers === 'object') { const answersArray = []; Object.keys(parsedAnswers).forEach(key => { const index = parseInt(key); if (!isNaN(index)) { answersArray[index] = parsedAnswers[key]; } }); finalUserAnswers = { answers: answersArray, questionOrder: questionOrder, savedAt: new Date().toISOString() }; } else { finalUserAnswers = { answers: [], questionOrder: questionOrder, savedAt: new Date().toISOString() }; } } else { finalUserAnswers = { answers: Array.isArray(parsedAnswers) ? parsedAnswers : [], questionOrder: [], savedAt: new Date().toISOString() }; } } else { if (parsedAnswers.answers && typeof parsedAnswers.answers === 'object') { finalUserAnswers = { answers: parsedAnswers.answers, savedAt: new Date().toISOString() }; } else if (parsedAnswers && typeof parsedAnswers === 'object') { finalUserAnswers = { answers: parsedAnswers, savedAt: new Date().toISOString() }; } else { finalUserAnswers = { answers: {}, savedAt: new Date().toISOString() }; } } let hasAnswers = false; if (finalUserAnswers.answers) { if (Array.isArray(finalUserAnswers.answers)) { hasAnswers = finalUserAnswers.answers.some(answer => answer !== undefined && answer !== null && answer !== ''); } else if (typeof finalUserAnswers.answers === 'object') { hasAnswers = Object.values(finalUserAnswers.answers).some(answer => { if (answer === undefined || answer === null) return false; if (typeof answer === 'string') return answer !== ''; if (Array.isArray(answer)) return answer.length > 0; if (typeof answer === 'object') { return (answer.selectedOptions && answer.selectedOptions.length > 0) || (answer.customText && answer.customText.trim() !== ''); } return true; }); } } if (!hasAnswers) { return next(ApiError.badRequest('Нельзя сохранить результат без ответов!')); } const testResult = await UserTestResult.create({ userId, testId: testId || null, title, userAnswers: JSON.stringify(finalUserAnswers), result }); return res.json({ ...testResult.toJSON(), userAnswers: finalUserAnswers, actualQuestionsCount: Array.isArray(finalUserAnswers.answers) ? finalUserAnswers.answers.filter(a => a !== undefined && a !== null && a !== '').length : Object.keys(finalUserAnswers.answers).filter(key => finalUserAnswers.answers[key] !== undefined && finalUserAnswers.answers[key] !== null && finalUserAnswers.answers[key] !== '' ).length }); } catch (e) { return next(ApiError.internal('Ошибка при сохранении результата!')); } } async getUserResults(req, res, next) { try { const userId = req.user.id; const results = await UserTestResult.findAll({ where: { userId }, order: [['createdAt', 'DESC']], include: [ { model: VisionTest, as: 'test', attributes: ['id', 'type', 'short_description'], include: [{ model: TestQuestion, as: 'questions', attributes: ['id'] }] } ] }); const allSurveyQuestions = await Question.findAll({ include: [{ model: Answer, as: 'answers', attributes: ['id', 'text'] }], order: [['id', 'ASC']] }); const resultsToDelete = []; const formattedResults = []; for (const result of results) { let userAnswers; let actualQuestionsCount = 0; try { const parsed = JSON.parse(result.userAnswers); if (parsed && parsed.answers !== undefined) { userAnswers = parsed; if (result.testId && result.test && result.test.questions) { const { answers, questionOrder } = parsed; const existingQuestions = result.test.questions; if (questionOrder && Array.isArray(questionOrder)) { const existingQuestionsMap = {}; existingQuestions.forEach(q => {existingQuestionsMap[q.id] = true;}); questionOrder.forEach((questionId, index) => { const userAnswer = Array.isArray(answers) ? answers[index] : undefined; const questionExists = existingQuestionsMap[questionId]; if (questionExists && userAnswer !== undefined && userAnswer !== null && userAnswer !== '') { actualQuestionsCount++; } }); } else { const answersArray = Array.isArray(answers) ? answers : []; const existingQuestionsCount = existingQuestions.length; for (let i = 0; i < Math.min(answersArray.length, existingQuestionsCount); i++) { const userAnswer = answersArray[i]; if (userAnswer !== undefined && userAnswer !== null && userAnswer !== '') { actualQuestionsCount++; } } } } else if (!result.testId) { if (typeof parsed.answers === 'object' && !Array.isArray(parsed.answers)) { allSurveyQuestions.forEach(question => { const userAnswer = parsed.answers[question.id]; if (userAnswer !== undefined && userAnswer !== null) { const hasAnswer = Array.isArray(userAnswer) ? userAnswer.length > 0 : (typeof userAnswer === 'object' ? (userAnswer.selectedOptions && userAnswer.selectedOptions.length > 0) || (userAnswer.customText && userAnswer.customText.trim() !== '') : userAnswer !== ''); if (hasAnswer) { actualQuestionsCount++; } } }); } } } else { resultsToDelete.push(result.id); continue; } } catch (e) { resultsToDelete.push(result.id); continue; } if (actualQuestionsCount === 0) { resultsToDelete.push(result.id); continue; } formattedResults.push({ ...result.toJSON(), userAnswers, testType: result.testId ? 'vision_test' : 'survey', totalQuestions: actualQuestionsCount, actualQuestionsCount }); } if (resultsToDelete.length > 0) { await UserTestResult.destroy({ where: { id: resultsToDelete, userId: userId } }); } return res.json(formattedResults); } catch (e) { return next(ApiError.internal('Ошибка при получении результатов!')); } } async getResultById(req, res, next) { try { const { id } = req.params; const userId = req.user.id; const result = await UserTestResult.findOne({ where: { id, userId }, include: [ { model: VisionTest, as: 'test', required: false, include: [{ model: TestQuestion, as: 'questions', attributes: ['id', 'content', 'correct_answer', 'answer_options'] }] } ] }); if (!result) { return next(ApiError.notFound('Результат не найден!')); } const resultJson = result.toJSON(); try { const parsed = JSON.parse(resultJson.userAnswers); if (parsed && parsed.answers !== undefined) { resultJson.userAnswers = parsed; } else { resultJson.userAnswers = { answers: parsed || {}, savedAt: new Date().toISOString() }; } } catch (e) { resultJson.userAnswers = { answers: {}, savedAt: new Date().toISOString(), parseError: true }; } if (resultJson.test && resultJson.test.questions) { resultJson.test.questions = resultJson.test.questions.map(question => ({ ...question, answer_options: JSON.parse(question.answer_options || '[]') })); } let actualQuestionsCount = 0; let shouldDelete = false; if (resultJson.testId) { resultJson.testType = 'vision_test'; const { answers, questionOrder } = resultJson.userAnswers; if (resultJson.test?.questions) { const existingQuestions = resultJson.test.questions; if (questionOrder && Array.isArray(questionOrder)) { const existingQuestionsMap = {}; existingQuestions.forEach(q => {existingQuestionsMap[q.id] = true;}); questionOrder.forEach((questionId, index) => { const userAnswer = Array.isArray(answers) ? answers[index] : undefined; const questionExists = existingQuestionsMap[questionId]; if (questionExists && userAnswer !== undefined && userAnswer !== null && userAnswer !== '') { actualQuestionsCount++; } }); } else { const answersArray = Array.isArray(answers) ? answers : []; const existingQuestionsCount = existingQuestions.length; for (let i = 0; i < Math.min(answersArray.length, existingQuestionsCount); i++) { const userAnswer = answersArray[i]; if (userAnswer !== undefined && userAnswer !== null && userAnswer !== '') { actualQuestionsCount++; } } } } if (actualQuestionsCount === 0) { shouldDelete = true; } } else { resultJson.testType = 'survey'; const surveyQuestions = await Question.findAll({ include: [{ model: Answer, as: 'answers', attributes: ['id', 'text'] }], order: [['id', 'ASC']] }); resultJson.surveyQuestions = surveyQuestions; if (typeof resultJson.userAnswers.answers === 'object' && !Array.isArray(resultJson.userAnswers.answers)) { const answersObject = resultJson.userAnswers.answers; surveyQuestions.forEach(question => { const userAnswer = answersObject[question.id]; if (userAnswer !== undefined && userAnswer !== null) { const hasAnswer = Array.isArray(userAnswer) ? userAnswer.length > 0 : (typeof userAnswer === 'object' ? (userAnswer.selectedOptions && userAnswer.selectedOptions.length > 0) || (userAnswer.customText && userAnswer.customText.trim() !== '') : userAnswer !== ''); if (hasAnswer) { actualQuestionsCount++; } } }); const existingQuestionIds = surveyQuestions.map(q => q.id); const filteredAnswers = {}; Object.keys(resultJson.userAnswers.answers).forEach(key => { const questionId = parseInt(key); if (!isNaN(questionId) && existingQuestionIds.includes(questionId)) { filteredAnswers[questionId] = resultJson.userAnswers.answers[key]; } }); resultJson.userAnswers.answers = filteredAnswers; } if (actualQuestionsCount === 0) { shouldDelete = true; } } resultJson.actualQuestionsCount = actualQuestionsCount; if (shouldDelete) { await UserTestResult.destroy({ where: { id: resultJson.id, userId: userId } }); return next(ApiError.notFound('Результат был удален, так как все вопросы были удалены из теста/опроса!')); } return res.json(resultJson); } catch (e) { return next(ApiError.internal('Ошибка при получении результата!')); } } async getUserStats(req, res, next) { try { const userId = req.user.id; const results = await UserTestResult.findAll({ where: { userId }, attributes: ['id', 'testId'] }); let visionTestsCount = 0; let surveysCount = 0; results.forEach(result => { if (result.testId) { visionTestsCount++; } else { surveysCount++; } }); return res.json({ visionTestsCount, surveysCount, totalResults: results.length }); } catch (e) { return next(ApiError.internal('Ошибка при получении статистики пользователя!')); } } async getAdminStats(req, res, next) { try { const userId = req.user.id; const userResults = await UserTestResult.findAll({ where: { userId }, attributes: ['id', 'testId'] }); let visionTestsCount = 0; let surveysCount = 0; userResults.forEach(result => { if (result.testId) { visionTestsCount++; } else { surveysCount++; } }); const [articlesCount, questionsCount, testsCount] = await Promise.all([ Articles.count({ where: { adminId: userId } }), Question.count({ where: { adminId: userId } }), VisionTest.count({ where: { adminId: userId } }) ]); return res.json({ visionTestsCount, surveysCount, totalResults: userResults.length, createdArticles: articlesCount, createdQuestions: questionsCount, createdTests: testsCount }); } catch (e) { return next(ApiError.internal('Ошибка при получении статистики администратора!')); } } async deleteResult(req, res, next) { try { const { id } = req.params; const userId = req.user.id; const deleted = await UserTestResult.destroy({where: { id, userId }}); if (!deleted) { return next(ApiError.notFound('Результат не найден!')); } return res.json({success: true, message: 'Результат успешно удален!'}); } catch (e) { return next(ApiError.internal('Ошибка при удалении результата!')); } } } module.exports = new ResultsController();