Files
challenge-admin-pl/docs/API_CHAIN_SUBMISSIONS.md

237 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Техническое задание: Эндпоинт получения попыток по цепочке
## Цель
Создать новый 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 часов (включая тесты)