Add new API endpoint for retrieving submissions by challenge chain; update frontend to support chain selection and display participant progress. Enhance localization for submissions page in English and Russian.

This commit is contained in:
2025-12-13 20:16:40 +03:00
parent 9104280325
commit 18e2ccb6bc
7 changed files with 509 additions and 131 deletions

View File

@@ -0,0 +1,236 @@
# Техническое задание: Эндпоинт получения попыток по цепочке
## Цель
Создать новый 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<ChainSubmissionsResponse, {
chainId: string;
userId?: string;
status?: SubmissionStatus;
limit?: number;
offset?: number;
}>({
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 часов (включая тесты)