237 lines
7.5 KiB
Markdown
237 lines
7.5 KiB
Markdown
# Техническое задание: Эндпоинт получения попыток по цепочке
|
||
|
||
## Цель
|
||
|
||
Создать новый 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 часов (включая тесты)
|
||
|