# Техническое задание: Эндпоинт получения попыток по цепочке ## Цель Создать новый API эндпоинт для получения списка попыток (submissions) участников в рамках конкретной цепочки заданий. Это упростит работу админ-панели и уменьшит объём передаваемых данных. ## Текущая проблема Сейчас для отображения попыток по цепочке фронтенд должен: 1. Загрузить список цепочек (`GET /challenge/chains/admin`) 2. Загрузить общую статистику (`GET /challenge/stats/v2`) 3. Для каждого участника отдельно загрузить его submissions (`GET /challenge/user/:userId/submissions`) 4. На клиенте фильтровать submissions по taskIds из выбранной цепочки Это создаёт избыточные запросы и усложняет логику на фронтенде. --- ## Новый эндпоинт ### `GET /challenge/chain/:chainId/submissions` Возвращает все попытки всех участников для заданий из указанной цепочки. ### Параметры URL | Параметр | Тип | Обязательный | Описание | |----------|-----|--------------|----------| | `chainId` | string | Да | ID цепочки заданий | ### Query параметры (опциональные) | Параметр | Тип | По умолчанию | Описание | |----------|-----|--------------|----------| | `userId` | string | - | Фильтр по конкретному пользователю | | `status` | string | - | Фильтр по статусу: `pending`, `in_progress`, `accepted`, `needs_revision` | | `limit` | number | 100 | Лимит записей | | `offset` | number | 0 | Смещение для пагинации | ### Формат ответа ```typescript interface ChainSubmissionsResponse { success: boolean; body: { chain: { id: string; name: string; tasks: Array<{ id: string; title: string; }>; }; participants: Array<{ userId: string; nickname: string; completedTasks: number; totalTasks: number; progressPercent: number; }>; submissions: Array<{ id: string; user: { id: string; nickname: string; }; task: { id: string; title: string; }; status: 'pending' | 'in_progress' | 'accepted' | 'needs_revision'; attemptNumber: number; submittedAt: string; // ISO date checkedAt?: string; // ISO date feedback?: string; }>; pagination: { total: number; limit: number; offset: number; }; }; } ``` ### Пример запроса ```bash GET /api/challenge/chain/607f1f77bcf86cd799439021/submissions?status=needs_revision&limit=50 ``` ### Пример ответа ```json { "success": true, "body": { "chain": { "id": "607f1f77bcf86cd799439021", "name": "Основы JavaScript", "tasks": [ { "id": "507f1f77bcf86cd799439011", "title": "Реализовать сортировку массива" }, { "id": "507f1f77bcf86cd799439015", "title": "Валидация формы" } ] }, "participants": [ { "userId": "user_123", "nickname": "alex_dev", "completedTasks": 1, "totalTasks": 2, "progressPercent": 50 }, { "userId": "user_456", "nickname": "maria_coder", "completedTasks": 2, "totalTasks": 2, "progressPercent": 100 } ], "submissions": [ { "id": "sub_001", "user": { "id": "user_123", "nickname": "alex_dev" }, "task": { "id": "507f1f77bcf86cd799439011", "title": "Реализовать сортировку массива" }, "status": "needs_revision", "attemptNumber": 2, "submittedAt": "2024-12-10T14:30:00.000Z", "checkedAt": "2024-12-10T14:30:45.000Z", "feedback": "Алгоритм работает неверно для отрицательных чисел" } ], "pagination": { "total": 15, "limit": 50, "offset": 0 } } } ``` --- ## Логика на бэкенде ### Алгоритм 1. Получить цепочку по `chainId` 2. Если цепочка не найдена — вернуть 404 3. Получить список `taskIds` из цепочки 4. Найти все submissions где `task._id` входит в `taskIds` 5. Применить фильтры (`userId`, `status`) если указаны 6. Вычислить прогресс по каждому участнику: - Найти уникальных пользователей из submissions - Для каждого посчитать `completedTasks` (количество уникальных tasks со статусом `accepted`) - Рассчитать `progressPercent = (completedTasks / totalTasks) * 100` 7. Применить пагинацию к submissions 8. Вернуть результат ### Индексы MongoDB (рекомендуется) ```javascript // Для быстрой выборки submissions по task db.submissions.createIndex({ "task": 1, "submittedAt": -1 }) // Составной индекс для фильтрации db.submissions.createIndex({ "task": 1, "status": 1, "submittedAt": -1 }) ``` --- ## Права доступа Эндпоинт должен быть доступен только пользователям с ролями: - `challenge-admin` - `challenge-teacher` --- ## Коды ошибок | Код | Описание | |-----|----------| | 200 | Успешный ответ | | 400 | Некорректные параметры запроса | | 401 | Не авторизован | | 403 | Недостаточно прав | | 404 | Цепочка не найдена | | 500 | Внутренняя ошибка сервера | --- ## Изменения на фронтенде после реализации После добавления эндпоинта в `src/__data__/api/api.ts` нужно добавить: ```typescript // В endpoints builder getChainSubmissions: builder.query({ query: ({ chainId, userId, status, limit, offset }) => ({ url: `/challenge/chain/${chainId}/submissions`, params: { userId, status, limit, offset }, }), transformResponse: (response: { body: ChainSubmissionsResponse }) => response.body, providesTags: ['Submission'], }), ``` Это позволит упростить `SubmissionsPage.tsx`: - Один запрос вместо нескольких - Убрать клиентскую фильтрацию по taskIds - Получать готовый прогресс участников --- ## Приоритет **Средний** — текущая реализация работает, но создаёт избыточную нагрузку при большом количестве участников. ## Оценка трудозатрат ~4-6 часов (включая тесты)