diff --git a/server/routers/assessment-tools/index.js b/server/routers/assessment-tools/index.js new file mode 100644 index 0000000..2b93a66 --- /dev/null +++ b/server/routers/assessment-tools/index.js @@ -0,0 +1,17 @@ +const router = require('express').Router(); + +// Импортировать mongoose из общего модуля (подключение происходит в server/utils/mongoose.ts) +const mongoose = require('../../utils/mongoose'); + +const timer = (time = 300) => (req, res, next) => setTimeout(next, time); + +router.use(timer()); + +// Подключение маршрутов - прямые пути без path.join и __dirname +router.use('/events', require('./routes/event')); +router.use('/teams', require('./routes/teams')); +router.use('/experts', require('./routes/experts')); +router.use('/criteria', require('./routes/criteria')); +router.use('/ratings', require('./routes/ratings')); + +module.exports = router; diff --git a/server/routers/assessment-tools/models/Criteria.js b/server/routers/assessment-tools/models/Criteria.js new file mode 100644 index 0000000..3aa2ba4 --- /dev/null +++ b/server/routers/assessment-tools/models/Criteria.js @@ -0,0 +1,39 @@ +const mongoose = require('../../../utils/mongoose'); + +const criterionItemSchema = new mongoose.Schema({ + name: { + type: String, + required: true + }, + maxScore: { + type: Number, + default: 5, + min: 0, + max: 10 + } +}, { _id: false }); + +const criteriaSchema = new mongoose.Schema({ + blockName: { + type: String, + required: true + }, + criteria: [criterionItemSchema], + order: { + type: Number, + default: 0 + }, + createdAt: { + type: Date, + default: Date.now + }, + updatedAt: { + type: Date, + default: Date.now + } +}, { + timestamps: true +}); + +module.exports = mongoose.model('Criteria', criteriaSchema); + diff --git a/server/routers/assessment-tools/models/Event.js b/server/routers/assessment-tools/models/Event.js new file mode 100644 index 0000000..bb17b50 --- /dev/null +++ b/server/routers/assessment-tools/models/Event.js @@ -0,0 +1,44 @@ +const mongoose = require('../../../utils/mongoose'); + +const eventSchema = new mongoose.Schema({ + name: { + type: String, + required: true, + default: 'Новое мероприятие' + }, + description: { + type: String, + default: '' + }, + eventDate: { + type: Date, + required: true, + default: Date.now + }, + location: { + type: String, + default: '' + }, + status: { + type: String, + enum: ['draft', 'ready', 'active', 'completed'], + default: 'draft' + }, + votingEnabled: { + type: Boolean, + default: false + }, + createdAt: { + type: Date, + default: Date.now + }, + updatedAt: { + type: Date, + default: Date.now + } +}, { + timestamps: true +}); + +module.exports = mongoose.model('Event', eventSchema); + diff --git a/server/routers/assessment-tools/models/Expert.js b/server/routers/assessment-tools/models/Expert.js new file mode 100644 index 0000000..5a4ce67 --- /dev/null +++ b/server/routers/assessment-tools/models/Expert.js @@ -0,0 +1,38 @@ +const mongoose = require('../../../utils/mongoose'); +const crypto = require('crypto'); + +const expertSchema = new mongoose.Schema({ + fullName: { + type: String, + required: true + }, + token: { + type: String, + unique: true + }, + qrCodeUrl: { + type: String, + default: '' + }, + createdAt: { + type: Date, + default: Date.now + }, + updatedAt: { + type: Date, + default: Date.now + } +}, { + timestamps: true +}); + +// Generate unique token before saving +expertSchema.pre('save', function(next) { + if (!this.token) { + this.token = crypto.randomBytes(16).toString('hex'); + } + next(); +}); + +module.exports = mongoose.model('Expert', expertSchema); + diff --git a/server/routers/assessment-tools/models/Rating.js b/server/routers/assessment-tools/models/Rating.js new file mode 100644 index 0000000..13ed07f --- /dev/null +++ b/server/routers/assessment-tools/models/Rating.js @@ -0,0 +1,59 @@ +const mongoose = require('../../../utils/mongoose'); + +const ratingItemSchema = new mongoose.Schema({ + criteriaId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Criteria', + required: true + }, + criterionName: { + type: String, + required: true + }, + score: { + type: Number, + required: true, + min: 0, + max: 5 + } +}, { _id: false }); + +const ratingSchema = new mongoose.Schema({ + expertId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Expert', + required: true + }, + teamId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Team', + required: true + }, + ratings: [ratingItemSchema], + totalScore: { + type: Number, + default: 0 + }, + createdAt: { + type: Date, + default: Date.now + }, + updatedAt: { + type: Date, + default: Date.now + } +}, { + timestamps: true +}); + +// Calculate total score before saving +ratingSchema.pre('save', function(next) { + this.totalScore = this.ratings.reduce((sum, item) => sum + item.score, 0); + next(); +}); + +// Ensure unique combination of expert and team +ratingSchema.index({ expertId: 1, teamId: 1 }, { unique: true }); + +module.exports = mongoose.model('Rating', ratingSchema); + diff --git a/server/routers/assessment-tools/models/Team.js b/server/routers/assessment-tools/models/Team.js new file mode 100644 index 0000000..27aabc0 --- /dev/null +++ b/server/routers/assessment-tools/models/Team.js @@ -0,0 +1,47 @@ +const mongoose = require('../../../utils/mongoose'); + +const teamSchema = new mongoose.Schema({ + type: { + type: String, + enum: ['team', 'participant'], + required: true + }, + name: { + type: String, + required: true + }, + projectName: { + type: String, + default: '' + }, + caseDescription: { + type: String, + default: '' + }, + isActive: { + type: Boolean, + default: true + }, + votingStatus: { + type: String, + enum: ['not_evaluated', 'evaluating', 'evaluated'], + default: 'not_evaluated' + }, + isActiveForVoting: { + type: Boolean, + default: false + }, + createdAt: { + type: Date, + default: Date.now + }, + updatedAt: { + type: Date, + default: Date.now + } +}, { + timestamps: true +}); + +module.exports = mongoose.model('Team', teamSchema); + diff --git a/server/routers/assessment-tools/models/index.js b/server/routers/assessment-tools/models/index.js new file mode 100644 index 0000000..6f05978 --- /dev/null +++ b/server/routers/assessment-tools/models/index.js @@ -0,0 +1,14 @@ +const Event = require('./Event'); +const Team = require('./Team'); +const Expert = require('./Expert'); +const Criteria = require('./Criteria'); +const Rating = require('./Rating'); + +module.exports = { + Event, + Team, + Expert, + Criteria, + Rating +}; + diff --git a/server/routers/assessment-tools/routes/criteria.js b/server/routers/assessment-tools/routes/criteria.js new file mode 100644 index 0000000..6c7b405 --- /dev/null +++ b/server/routers/assessment-tools/routes/criteria.js @@ -0,0 +1,117 @@ +const router = require('express').Router(); +const { Criteria } = require('../models'); + +// Критерии по умолчанию из hack.md +const DEFAULT_CRITERIA = [ + { + blockName: 'Оценка проекта', + criteria: [ + { name: 'Соответствие решения поставленной задаче', maxScore: 5 }, + { name: 'Оригинальность - использование нестандартных технических и проектных подходов', maxScore: 5 }, + { name: 'Работоспособность решения', maxScore: 1 }, + { name: 'Технологическая сложность решения', maxScore: 2 }, + { name: 'Объем функциональных возможностей решения', maxScore: 2 }, + { name: 'Аргументация способа выбранного решения', maxScore: 5 }, + { name: 'Качество предоставления информации', maxScore: 5 }, + { name: 'Наличие удобного UX/UI', maxScore: 5 }, + { name: 'Наличие не менее 5 AI-агентов', maxScore: 5 } + ], + order: 0 + } +]; + +// GET /api/criteria - получить все блоки критериев +router.get('/', async (req, res) => { + try { + const criteria = await Criteria.find().sort({ order: 1 }); + res.json(criteria); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// GET /api/criteria/:id - получить блок критериев по ID +router.get('/:id', async (req, res) => { + try { + const criteria = await Criteria.findById(req.params.id); + if (!criteria) { + return res.status(404).json({ error: 'Criteria not found' }); + } + res.json(criteria); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// POST /api/criteria - создать блок критериев +router.post('/', async (req, res) => { + try { + const { blockName, criteria, order } = req.body; + + if (!blockName || !criteria || !Array.isArray(criteria)) { + return res.status(400).json({ error: 'Block name and criteria array are required' }); + } + + const criteriaBlock = await Criteria.create({ + blockName, + criteria, + order: order !== undefined ? order : 0 + }); + + res.status(201).json(criteriaBlock); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// POST /api/criteria/default - загрузить критерии по умолчанию из hack.md +router.post('/default', async (req, res) => { + try { + // Удаляем все существующие критерии + await Criteria.deleteMany({}); + + // Создаем критерии по умолчанию + const createdCriteria = await Criteria.insertMany(DEFAULT_CRITERIA); + + res.status(201).json(createdCriteria); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// PUT /api/criteria/:id - редактировать блок +router.put('/:id', async (req, res) => { + try { + const { blockName, criteria, order } = req.body; + + const criteriaBlock = await Criteria.findById(req.params.id); + if (!criteriaBlock) { + return res.status(404).json({ error: 'Criteria not found' }); + } + + if (blockName !== undefined) criteriaBlock.blockName = blockName; + if (criteria !== undefined) criteriaBlock.criteria = criteria; + if (order !== undefined) criteriaBlock.order = order; + + await criteriaBlock.save(); + res.json(criteriaBlock); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// DELETE /api/criteria/:id - удалить блок +router.delete('/:id', async (req, res) => { + try { + const criteria = await Criteria.findByIdAndDelete(req.params.id); + if (!criteria) { + return res.status(404).json({ error: 'Criteria not found' }); + } + res.json({ message: 'Criteria deleted successfully', criteria }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +module.exports = router; + diff --git a/server/routers/assessment-tools/routes/event.js b/server/routers/assessment-tools/routes/event.js new file mode 100644 index 0000000..5fc614d --- /dev/null +++ b/server/routers/assessment-tools/routes/event.js @@ -0,0 +1,108 @@ +const router = require('express').Router(); +const { Event } = require('../models'); + +// GET /api/events - получить все мероприятия +router.get('/', async (req, res) => { + try { + const events = await Event.find().sort({ eventDate: -1 }); + res.json(events); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// GET /api/events/:id - получить одно мероприятие +router.get('/:id', async (req, res) => { + try { + const event = await Event.findById(req.params.id); + + if (!event) { + return res.status(404).json({ error: 'Мероприятие не найдено' }); + } + + res.json(event); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// POST /api/events - создать новое мероприятие +router.post('/', async (req, res) => { + try { + const { name, description, eventDate, location, status } = req.body; + + const event = await Event.create({ + name: name || 'Новое мероприятие', + description: description || '', + eventDate: eventDate || new Date(), + location: location || '', + status: status || 'draft', + votingEnabled: false + }); + + res.status(201).json(event); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// PUT /api/events/:id - обновить мероприятие +router.put('/:id', async (req, res) => { + try { + const { name, description, eventDate, location, status } = req.body; + + const event = await Event.findById(req.params.id); + + if (!event) { + return res.status(404).json({ error: 'Мероприятие не найдено' }); + } + + if (name !== undefined) event.name = name; + if (description !== undefined) event.description = description; + if (eventDate !== undefined) event.eventDate = eventDate; + if (location !== undefined) event.location = location; + if (status !== undefined) event.status = status; + + await event.save(); + + res.json(event); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// DELETE /api/events/:id - удалить мероприятие +router.delete('/:id', async (req, res) => { + try { + const event = await Event.findByIdAndDelete(req.params.id); + + if (!event) { + return res.status(404).json({ error: 'Мероприятие не найдено' }); + } + + res.json({ message: 'Мероприятие удалено', event }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// PATCH /api/events/:id/toggle-voting - вкл/выкл оценку +router.patch('/:id/toggle-voting', async (req, res) => { + try { + const event = await Event.findById(req.params.id); + + if (!event) { + return res.status(404).json({ error: 'Мероприятие не найдено' }); + } + + event.votingEnabled = !event.votingEnabled; + await event.save(); + + res.json(event); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +module.exports = router; + diff --git a/server/routers/assessment-tools/routes/experts.js b/server/routers/assessment-tools/routes/experts.js new file mode 100644 index 0000000..dbbf43f --- /dev/null +++ b/server/routers/assessment-tools/routes/experts.js @@ -0,0 +1,104 @@ +const router = require('express').Router(); +const { Expert } = require('../models'); + +// GET /api/experts - список экспертов +router.get('/', async (req, res) => { + try { + const experts = await Expert.find().sort({ createdAt: -1 }); + res.json(experts); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// GET /api/experts/by-token/:token - получить данные эксперта по токену +router.get('/by-token/:token', async (req, res) => { + try { + const expert = await Expert.findOne({ token: req.params.token }); + if (!expert) { + return res.status(404).json({ error: 'Expert not found' }); + } + res.json(expert); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// GET /api/experts/:id - получить эксперта по ID +router.get('/:id', async (req, res) => { + try { + const expert = await Expert.findById(req.params.id); + if (!expert) { + return res.status(404).json({ error: 'Expert not found' }); + } + res.json(expert); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// POST /api/experts - создать эксперта (с генерацией уникальной ссылки) +router.post('/', async (req, res) => { + try { + const { fullName } = req.body; + + if (!fullName) { + return res.status(400).json({ error: 'Full name is required' }); + } + + // Создаем нового эксперта + const expert = new Expert({ + fullName + }); + + // Сохраняем эксперта (токен генерируется в pre-save хуке) + await expert.save(); + + // Формируем URL для QR кода ПОСЛЕ сохранения, когда токен уже сгенерирован + const baseUrl = req.protocol + '://' + req.get('host'); + expert.qrCodeUrl = `${baseUrl}/assessment-tools/expert/${expert.token}`; + + // Сохраняем еще раз с обновленным qrCodeUrl + await expert.save(); + + res.status(201).json(expert); + } catch (error) { + console.error('Error creating expert:', error); + res.status(500).json({ error: error.message }); + } +}); + +// PUT /api/experts/:id - редактировать эксперта +router.put('/:id', async (req, res) => { + try { + const { fullName } = req.body; + + const expert = await Expert.findById(req.params.id); + if (!expert) { + return res.status(404).json({ error: 'Expert not found' }); + } + + if (fullName !== undefined) expert.fullName = fullName; + + await expert.save(); + res.json(expert); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// DELETE /api/experts/:id - удалить эксперта +router.delete('/:id', async (req, res) => { + try { + const expert = await Expert.findByIdAndDelete(req.params.id); + if (!expert) { + return res.status(404).json({ error: 'Expert not found' }); + } + res.json({ message: 'Expert deleted successfully', expert }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +module.exports = router; + diff --git a/server/routers/assessment-tools/routes/ratings.js b/server/routers/assessment-tools/routes/ratings.js new file mode 100644 index 0000000..de1cc10 --- /dev/null +++ b/server/routers/assessment-tools/routes/ratings.js @@ -0,0 +1,215 @@ +const router = require('express').Router(); +const { Rating, Team, Expert, Criteria } = require('../models'); + +// GET /api/ratings - получить все оценки (с фильтрами) +router.get('/', async (req, res) => { + try { + const { expertId, teamId } = req.query; + const filter = {}; + + if (expertId) filter.expertId = expertId; + if (teamId) filter.teamId = teamId; + + const ratings = await Rating.find(filter) + .populate('expertId', 'fullName') + .populate('teamId', 'name type') + .sort({ createdAt: -1 }); + + res.json(ratings); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// GET /api/ratings/team/:teamId - оценки конкретной команды +router.get('/team/:teamId', async (req, res) => { + try { + const ratings = await Rating.find({ teamId: req.params.teamId }) + .populate('expertId', 'fullName') + .populate('teamId', 'name type projectName'); + + res.json(ratings); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// GET /api/ratings/expert/:expertId - оценки конкретного эксперта +router.get('/expert/:expertId', async (req, res) => { + try { + const ratings = await Rating.find({ expertId: req.params.expertId }) + .populate('teamId', 'name type projectName'); + + res.json(ratings); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// GET /api/ratings/statistics - статистика с группировкой по командам +router.get('/statistics', async (req, res) => { + try { + const { type } = req.query; + + // Получаем все команды + const teamFilter = type ? { type, isActive: true } : { isActive: true }; + const teams = await Team.find(teamFilter); + + // Получаем все оценки + const ratings = await Rating.find() + .populate('expertId', 'fullName') + .populate('teamId', 'name type projectName'); + + // Группируем оценки по командам + const statistics = teams.map(team => { + const teamRatings = ratings.filter(r => r.teamId && r.teamId._id.toString() === team._id.toString()); + + // Считаем средние оценки по критериям + const criteriaStats = {}; + teamRatings.forEach(rating => { + rating.ratings.forEach(item => { + if (!criteriaStats[item.criterionName]) { + criteriaStats[item.criterionName] = { + name: item.criterionName, + scores: [], + average: 0 + }; + } + criteriaStats[item.criterionName].scores.push(item.score); + }); + }); + + // Вычисляем средние значения + Object.keys(criteriaStats).forEach(key => { + const scores = criteriaStats[key].scores; + criteriaStats[key].average = scores.reduce((sum, s) => sum + s, 0) / scores.length; + }); + + // Считаем общий балл команды (среднее от всех экспертов) + const totalScore = teamRatings.length > 0 + ? teamRatings.reduce((sum, r) => sum + r.totalScore, 0) / teamRatings.length + : 0; + + return { + team: { + _id: team._id, + name: team.name, + type: team.type, + projectName: team.projectName + }, + ratings: teamRatings.map(r => ({ + expert: r.expertId ? r.expertId.fullName : 'Unknown', + criteria: r.ratings, + totalScore: r.totalScore + })), + criteriaStats: Object.values(criteriaStats), + totalScore: totalScore, + ratingsCount: teamRatings.length + }; + }); + + res.json(statistics); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// GET /api/ratings/top3 - топ-3 команды/участники +router.get('/top3', async (req, res) => { + try { + const { type } = req.query; + + // Получаем статистику + const teamFilter = type ? { type, isActive: true } : { isActive: true }; + const teams = await Team.find(teamFilter); + + const ratings = await Rating.find() + .populate('teamId', 'name type projectName'); + + // Группируем и считаем средние баллы + const teamScores = teams.map(team => { + const teamRatings = ratings.filter(r => r.teamId && r.teamId._id.toString() === team._id.toString()); + + const totalScore = teamRatings.length > 0 + ? teamRatings.reduce((sum, r) => sum + r.totalScore, 0) / teamRatings.length + : 0; + + return { + team: { + _id: team._id, + name: team.name, + type: team.type, + projectName: team.projectName + }, + totalScore: totalScore, + ratingsCount: teamRatings.length + }; + }); + + // Сортируем по баллам и берем топ-3 + const top3 = teamScores + .filter(t => t.ratingsCount > 0) + .sort((a, b) => b.totalScore - a.totalScore) + .slice(0, 3); + + res.json(top3); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// POST /api/ratings - создать/обновить оценку эксперта +router.post('/', async (req, res) => { + try { + const { expertId, teamId, ratings } = req.body; + + if (!expertId || !teamId || !ratings || !Array.isArray(ratings)) { + return res.status(400).json({ error: 'Expert ID, team ID, and ratings array are required' }); + } + + // Проверяем существование эксперта и команды + const expert = await Expert.findById(expertId); + const team = await Team.findById(teamId); + + if (!expert) { + return res.status(404).json({ error: 'Expert not found' }); + } + + if (!team) { + return res.status(404).json({ error: 'Team not found' }); + } + + // Проверяем, активна ли команда + if (!team.isActive) { + return res.status(400).json({ error: 'Team voting is disabled' }); + } + + // Ищем существующую оценку + let rating = await Rating.findOne({ expertId, teamId }); + + if (rating) { + // Обновляем существующую оценку + rating.ratings = ratings; + await rating.save(); + } else { + // Создаем новую оценку + rating = await Rating.create({ + expertId, + teamId, + ratings + }); + } + + // Возвращаем с populate + rating = await Rating.findById(rating._id) + .populate('expertId', 'fullName') + .populate('teamId', 'name type projectName'); + + res.status(rating ? 200 : 201).json(rating); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +module.exports = router; + diff --git a/server/routers/assessment-tools/routes/teams.js b/server/routers/assessment-tools/routes/teams.js new file mode 100644 index 0000000..746925a --- /dev/null +++ b/server/routers/assessment-tools/routes/teams.js @@ -0,0 +1,180 @@ +const router = require('express').Router(); +const { Team } = require('../models'); + +// GET /api/teams - список всех команд +router.get('/', async (req, res) => { + try { + const { type } = req.query; + const filter = type ? { type } : {}; + const teams = await Team.find(filter).sort({ createdAt: -1 }); + res.json(teams); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// GET /api/teams/active/voting - получить активную для оценки команду (ДОЛЖЕН БЫТЬ ПЕРЕД /:id) +router.get('/active/voting', async (req, res) => { + try { + const team = await Team.findOne({ isActiveForVoting: true }); + res.json(team); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// PATCH /api/teams/stop-all-voting/global - остановить все оценивания (ДОЛЖЕН БЫТЬ ПЕРЕД /:id) +router.patch('/stop-all-voting/global', async (req, res) => { + try { + // Находим все команды, которые сейчас оцениваются + const result = await Team.updateMany( + { isActiveForVoting: true }, + { + isActiveForVoting: false, + votingStatus: 'evaluated' + } + ); + + res.json({ + message: 'All voting stopped successfully', + modifiedCount: result.modifiedCount + }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// GET /api/teams/:id - получить команду по ID +router.get('/:id', async (req, res) => { + try { + const team = await Team.findById(req.params.id); + if (!team) { + return res.status(404).json({ error: 'Team not found' }); + } + res.json(team); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// POST /api/teams - создать команду/участника +router.post('/', async (req, res) => { + try { + const { type, name, projectName, caseDescription } = req.body; + + if (!type || !name) { + return res.status(400).json({ error: 'Type and name are required' }); + } + + const team = await Team.create({ + type, + name, + projectName: projectName || '', + caseDescription: caseDescription || '', + isActive: true + }); + + res.status(201).json(team); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// PUT /api/teams/:id - редактировать команду +router.put('/:id', async (req, res) => { + try { + const { type, name, projectName, caseDescription } = req.body; + + const team = await Team.findById(req.params.id); + if (!team) { + return res.status(404).json({ error: 'Team not found' }); + } + + if (type !== undefined) team.type = type; + if (name !== undefined) team.name = name; + if (projectName !== undefined) team.projectName = projectName; + if (caseDescription !== undefined) team.caseDescription = caseDescription; + + await team.save(); + res.json(team); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// DELETE /api/teams/:id - удалить команду +router.delete('/:id', async (req, res) => { + try { + const team = await Team.findByIdAndDelete(req.params.id); + if (!team) { + return res.status(404).json({ error: 'Team not found' }); + } + res.json({ message: 'Team deleted successfully', team }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// PATCH /api/teams/:id/activate-for-voting - активировать команду для оценки +router.patch('/:id/activate-for-voting', async (req, res) => { + try { + // Деактивируем все команды и сохраняем их статус + const previouslyActive = await Team.findOne({ isActiveForVoting: true }); + if (previouslyActive) { + previouslyActive.isActiveForVoting = false; + previouslyActive.votingStatus = 'evaluated'; + await previouslyActive.save(); + } + + // Активируем выбранную команду + const team = await Team.findById(req.params.id); + if (!team) { + return res.status(404).json({ error: 'Team not found' }); + } + + team.isActiveForVoting = true; + team.votingStatus = 'evaluating'; + await team.save(); + + res.json(team); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// PATCH /api/teams/:id/stop-voting - остановить оценивание конкретной команды +router.patch('/:id/stop-voting', async (req, res) => { + try { + const team = await Team.findById(req.params.id); + if (!team) { + return res.status(404).json({ error: 'Team not found' }); + } + + team.isActiveForVoting = false; + team.votingStatus = 'evaluated'; + await team.save(); + + res.json(team); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +// PATCH /api/teams/:id/toggle-active - остановить оценку команды +router.patch('/:id/toggle-active', async (req, res) => { + try { + const team = await Team.findById(req.params.id); + if (!team) { + return res.status(404).json({ error: 'Team not found' }); + } + + team.isActive = !team.isActive; + await team.save(); + + res.json(team); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + +module.exports = router; diff --git a/server/routers/assessment-tools/scripts/recreate-test-user.js b/server/routers/assessment-tools/scripts/recreate-test-user.js new file mode 100644 index 0000000..b1362de --- /dev/null +++ b/server/routers/assessment-tools/scripts/recreate-test-user.js @@ -0,0 +1,38 @@ +// Импортировать mongoose из общего модуля (подключение происходит в server/utils/mongoose.ts) +const mongoose = require('../../../utils/mongoose'); +const { Event } = require('../models'); + +async function recreateTestUser() { + try { + // Ждем, пока подключение будет готово + if (mongoose.connection.readyState !== 1) { + await new Promise(resolve => { + mongoose.connection.once('connected', resolve); + }); + } + + console.log('Connected to MongoDB'); + + // Создаем тестовое мероприятие если его нет + let event = await Event.findOne(); + if (!event) { + event = await Event.create({ + name: 'Tatar san', + status: 'draft', + votingEnabled: false + }); + console.log('Test event created:', event.name); + } + + console.log('Database initialized successfully'); + + await mongoose.disconnect(); + console.log('Disconnected from MongoDB'); + } catch (error) { + console.error('Error:', error); + process.exit(1); + } +} + +recreateTestUser(); +