fix api calls
This commit is contained in:
@@ -0,0 +1,544 @@
|
||||
# Challenge Service API Documentation
|
||||
|
||||
Сервис для проверки заданий в реальном времени через LLM (GigaChat).
|
||||
|
||||
## Описание
|
||||
|
||||
Система позволяет:
|
||||
- Создавать задания с описанием в Markdown
|
||||
- Объединять задания в цепочки
|
||||
- Пользователям проходить задания и отправлять результаты
|
||||
- Автоматически проверять результаты через GigaChat с ограничением потоков
|
||||
- Отслеживать статистику по пользователям и системе
|
||||
|
||||
## Конфигурация
|
||||
|
||||
В `.env` файле добавьте:
|
||||
|
||||
```env
|
||||
CHALLENGE_LLM_THREADS=2 # Количество одновременных проверок через LLM
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
Все endpoints начинаются с префикса `/api/challenge`
|
||||
|
||||
### Аутентификация
|
||||
|
||||
#### POST `/auth`
|
||||
|
||||
Регистрация/авторизация пользователя по nickname.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"nickname": "user123"
|
||||
}
|
||||
```
|
||||
|
||||
**Валидация:**
|
||||
- `nickname`: обязательное поле, от 3 до 50 символов
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"error": null,
|
||||
"data": {
|
||||
"ok": true,
|
||||
"userId": "507f1f77bcf86cd799439011"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Управление заданиями
|
||||
|
||||
**Важно:** Все операции создания/обновления/удаления заданий требуют авторизации через Keycloak с ролью `teacher` или `challenge-author`.
|
||||
|
||||
#### POST `/task`
|
||||
|
||||
Создание нового задания (требует роль `teacher` или `challenge-author`).
|
||||
|
||||
**Headers:**
|
||||
```
|
||||
Authorization: Bearer <keycloak_token>
|
||||
```
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"title": "Написать функцию сортировки",
|
||||
"description": "# Задание\n\nНапишите функцию сортировки массива чисел...",
|
||||
"hiddenInstructions": "Проверь сложность алгоритма. Должна быть O(n log n), не принимай bubble sort"
|
||||
}
|
||||
```
|
||||
|
||||
**Валидация:**
|
||||
- `title`: обязательное поле, максимум 255 символов
|
||||
- `description`: обязательное поле
|
||||
- `hiddenInstructions`: опциональное поле
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"error": null,
|
||||
"data": {
|
||||
"_id": "507f1f77bcf86cd799439011",
|
||||
"title": "Написать функцию сортировки",
|
||||
"description": "# Задание\n\n...",
|
||||
"hiddenInstructions": "Проверь сложность алгоритма...",
|
||||
"creator": { "sub": "...", "preferred_username": "teacher1" },
|
||||
"createdAt": "2023-10-29T12:00:00.000Z",
|
||||
"updatedAt": "2023-10-29T12:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Примечание:** Поле `hiddenInstructions` опционально. Эти инструкции будут переданы в LLM при проверке, но скрыты от студентов.
|
||||
|
||||
#### GET `/task/:taskId`
|
||||
|
||||
Получение задания по ID.
|
||||
|
||||
**Важно:** Поля `hiddenInstructions` и `creator` возвращаются только для пользователей с ролью `teacher` или `challenge-author`. Обычные студенты их не увидят.
|
||||
|
||||
#### GET `/tasks`
|
||||
|
||||
Получение всех заданий.
|
||||
|
||||
**Примечание:** Для не-преподавателей поля `hiddenInstructions` и `creator` скрыты.
|
||||
|
||||
#### PUT `/task/:taskId`
|
||||
|
||||
Обновление задания (требует роль `teacher` или `challenge-author`).
|
||||
|
||||
**Headers:**
|
||||
```
|
||||
Authorization: Bearer <keycloak_token>
|
||||
```
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"title": "Новый заголовок",
|
||||
"description": "Новое описание",
|
||||
"hiddenInstructions": "Обновленные инструкции для LLM"
|
||||
}
|
||||
```
|
||||
|
||||
**Валидация:**
|
||||
- `title`: опциональное поле, максимум 255 символов
|
||||
- `description`: опциональное поле
|
||||
- `hiddenInstructions`: опциональное поле
|
||||
|
||||
#### DELETE `/task/:taskId`
|
||||
|
||||
Удаление задания (требует роль `teacher` или `challenge-author`). Также удаляется из всех цепочек.
|
||||
|
||||
**Headers:**
|
||||
```
|
||||
Authorization: Bearer <keycloak_token>
|
||||
```
|
||||
|
||||
### Управление цепочками заданий
|
||||
|
||||
**Важно:** Все операции создания/обновления/удаления цепочек требуют авторизации через Keycloak с ролью `teacher` или `challenge-author`.
|
||||
|
||||
#### POST `/chain`
|
||||
|
||||
Создание цепочки заданий (требует роль `teacher` или `challenge-author`).
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"name": "Основы программирования",
|
||||
"taskIds": ["507f1f77bcf86cd799439011", "507f1f77bcf86cd799439012"]
|
||||
}
|
||||
```
|
||||
|
||||
**Валидация:**
|
||||
- `name`: обязательное поле, максимум 255 символов
|
||||
- `taskIds`: обязательное поле, массив строк
|
||||
|
||||
#### GET `/chains`
|
||||
|
||||
Получение всех цепочек с заданиями.
|
||||
|
||||
**Примечание:** Для не-преподавателей поля `hiddenInstructions` и `creator` скрыты в заданиях внутри цепочек.
|
||||
|
||||
#### GET `/chain/:chainId`
|
||||
|
||||
Получение цепочки по ID.
|
||||
|
||||
**Примечание:** Для не-преподавателей поля `hiddenInstructions` и `creator` скрыты в заданиях внутри цепочки.
|
||||
|
||||
#### PUT `/chain/:chainId`
|
||||
|
||||
Обновление цепочки (требует роль `teacher` или `challenge-author`).
|
||||
|
||||
**Headers:**
|
||||
```
|
||||
Authorization: Bearer <keycloak_token>
|
||||
```
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"name": "Новое название цепочки",
|
||||
"taskIds": ["507f1f77bcf86cd799439011", "507f1f77bcf86cd799439012"]
|
||||
}
|
||||
```
|
||||
|
||||
**Валидация:**
|
||||
- `name`: опциональное поле, максимум 255 символов
|
||||
- `taskIds`: опциональное поле, массив строк
|
||||
|
||||
#### DELETE `/chain/:chainId`
|
||||
|
||||
Удаление цепочки (требует роль `teacher` или `challenge-author`).
|
||||
|
||||
**Headers:**
|
||||
```
|
||||
Authorization: Bearer <keycloak_token>
|
||||
```
|
||||
|
||||
### Отправка и проверка заданий
|
||||
|
||||
#### POST `/submit`
|
||||
|
||||
Отправка результата выполнения задания на проверку.
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"userId": "507f1f77bcf86cd799439011",
|
||||
"taskId": "507f1f77bcf86cd799439012",
|
||||
"result": "function sort(arr) { return arr.sort((a, b) => a - b); }"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"error": null,
|
||||
"data": {
|
||||
"queueId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"submissionId": "507f1f77bcf86cd799439013"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Валидация:**
|
||||
- `userId`: обязательное поле
|
||||
- `taskId`: обязательное поле
|
||||
- `result`: обязательное поле
|
||||
|
||||
#### GET `/check-status/:queueId`
|
||||
|
||||
Polling endpoint для проверки статуса проверки.
|
||||
|
||||
**Response (в процессе):**
|
||||
```json
|
||||
{
|
||||
"error": null,
|
||||
"data": {
|
||||
"status": "waiting",
|
||||
"position": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response (завершено):**
|
||||
```json
|
||||
{
|
||||
"error": null,
|
||||
"data": {
|
||||
"status": "completed",
|
||||
"submission": {
|
||||
"_id": "507f1f77bcf86cd799439013",
|
||||
"user": {...},
|
||||
"task": {...},
|
||||
"result": "...",
|
||||
"status": "accepted",
|
||||
"feedback": "Отлично! Задание выполнено правильно.",
|
||||
"attemptNumber": 1,
|
||||
"submittedAt": "2023-10-29T12:00:00.000Z",
|
||||
"checkedAt": "2023-10-29T12:00:05.000Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Статусы проверки:
|
||||
- `waiting` - ожидает в очереди
|
||||
- `in_progress` - проверяется
|
||||
- `completed` - проверка завершена
|
||||
- `error` - ошибка при проверке
|
||||
- `not_found` - не найдено
|
||||
|
||||
Статусы submission:
|
||||
- `pending` - ожидает проверки
|
||||
- `in_progress` - проверяется
|
||||
- `accepted` - принято
|
||||
- `needs_revision` - требует доработки
|
||||
|
||||
### Просмотр попыток
|
||||
|
||||
#### GET `/user/:userId/submissions?taskId=...`
|
||||
|
||||
Получение всех попыток пользователя (опционально для конкретного задания).
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"error": null,
|
||||
"data": [
|
||||
{
|
||||
"_id": "507f1f77bcf86cd799439013",
|
||||
"task": {...},
|
||||
"result": "...",
|
||||
"status": "accepted",
|
||||
"feedback": "...",
|
||||
"attemptNumber": 2,
|
||||
"submittedAt": "2023-10-29T12:00:00.000Z",
|
||||
"checkedAt": "2023-10-29T12:00:05.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Статистика
|
||||
|
||||
#### GET `/user/:userId/stats`
|
||||
|
||||
Детальная статистика пользователя.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"error": null,
|
||||
"data": {
|
||||
"totalTasksAttempted": 5,
|
||||
"completedTasks": 3,
|
||||
"inProgressTasks": 1,
|
||||
"needsRevisionTasks": 1,
|
||||
"totalSubmissions": 8,
|
||||
"averageCheckTimeMs": 5234,
|
||||
"taskStats": [
|
||||
{
|
||||
"taskId": "507f1f77bcf86cd799439012",
|
||||
"taskTitle": "Написать функцию сортировки",
|
||||
"attempts": [...],
|
||||
"totalAttempts": 2,
|
||||
"status": "completed",
|
||||
"lastAttemptAt": "2023-10-29T12:00:00.000Z"
|
||||
}
|
||||
],
|
||||
"chainStats": [
|
||||
{
|
||||
"chainId": "507f1f77bcf86cd799439014",
|
||||
"chainName": "Основы программирования",
|
||||
"totalTasks": 5,
|
||||
"completedTasks": 3,
|
||||
"progress": 60
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Примечание:** Статусы заданий в `taskStats.status`:
|
||||
- `not_attempted` - задание не начато
|
||||
- `pending` - ожидает проверки
|
||||
- `in_progress` - проверяется
|
||||
- `needs_revision` - требует доработки
|
||||
- `completed` - выполнено
|
||||
|
||||
#### GET `/stats`
|
||||
|
||||
Общая статистика системы.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"error": null,
|
||||
"data": {
|
||||
"users": 150,
|
||||
"tasks": 25,
|
||||
"chains": 5,
|
||||
"submissions": {
|
||||
"total": 850,
|
||||
"accepted": 420,
|
||||
"rejected": 380,
|
||||
"pending": 30,
|
||||
"inProgress": 20
|
||||
},
|
||||
"averageCheckTimeMs": 5500,
|
||||
"queue": {
|
||||
"queueLength": 12,
|
||||
"waiting": 10,
|
||||
"inProgress": 2,
|
||||
"maxConcurrency": 2,
|
||||
"currentlyProcessing": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### GET `/stats/v2`
|
||||
|
||||
Расширенная статистика системы с детальными данными для таблиц и прогресс-баров.
|
||||
|
||||
**Query параметры:**
|
||||
- `chainId` (опционально): фильтрация статистики по конкретной цепочке
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"error": null,
|
||||
"data": {
|
||||
"users": 150,
|
||||
"tasks": 25,
|
||||
"chains": 5,
|
||||
"submissions": {
|
||||
"total": 850,
|
||||
"accepted": 420,
|
||||
"rejected": 380,
|
||||
"pending": 30,
|
||||
"inProgress": 20
|
||||
},
|
||||
"averageCheckTimeMs": 5500,
|
||||
"queue": {
|
||||
"queueLength": 12,
|
||||
"waiting": 10,
|
||||
"inProgress": 2,
|
||||
"maxConcurrency": 2,
|
||||
"currentlyProcessing": 2
|
||||
},
|
||||
"tasksTable": [
|
||||
{
|
||||
"taskId": "507f1f77bcf86cd799439012",
|
||||
"title": "Написать функцию сортировки",
|
||||
"totalAttempts": 45,
|
||||
"uniqueUsers": 20,
|
||||
"acceptedCount": 18,
|
||||
"successRate": 90,
|
||||
"averageAttemptsToSuccess": 1.5
|
||||
}
|
||||
],
|
||||
"activeParticipants": [
|
||||
{
|
||||
"userId": "507f1f77bcf86cd799439011",
|
||||
"nickname": "user123",
|
||||
"totalSubmissions": 10,
|
||||
"completedTasks": 5,
|
||||
"chainProgress": [
|
||||
{
|
||||
"chainId": "507f1f77bcf86cd799439014",
|
||||
"chainName": "Основы программирования",
|
||||
"totalTasks": 10,
|
||||
"completedTasks": 5,
|
||||
"progressPercent": 50
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"chainsDetailed": [
|
||||
{
|
||||
"chainId": "507f1f77bcf86cd799439014",
|
||||
"name": "Основы программирования",
|
||||
"totalTasks": 10,
|
||||
"tasks": [
|
||||
{
|
||||
"taskId": "507f1f77bcf86cd799439012",
|
||||
"title": "Написать функцию сортировки",
|
||||
"description": "# Задание\n\n..."
|
||||
}
|
||||
],
|
||||
"participantProgress": [
|
||||
{
|
||||
"userId": "507f1f77bcf86cd799439011",
|
||||
"nickname": "user123",
|
||||
"taskProgress": [
|
||||
{
|
||||
"taskId": "507f1f77bcf86cd799439012",
|
||||
"taskTitle": "Написать функцию сортировки",
|
||||
"status": "completed"
|
||||
}
|
||||
],
|
||||
"completedCount": 5,
|
||||
"progressPercent": 50
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Примечание:** Статусы задач в `taskProgress`:
|
||||
- `not_started` - задание не начато
|
||||
- `pending` - ожидает проверки
|
||||
- `in_progress` - проверяется
|
||||
- `needs_revision` - требует доработки
|
||||
- `completed` - выполнено
|
||||
|
||||
## Архитектура
|
||||
|
||||
### Модели данных
|
||||
|
||||
- **ChallengeUser** - пользователи сервиса
|
||||
- **ChallengeTask** - отдельные задания
|
||||
- **ChallengeChain** - цепочки заданий
|
||||
- **ChallengeSubmission** - результаты выполнения заданий
|
||||
|
||||
### Сервисы
|
||||
|
||||
- **challenge-checker.ts** - проверка заданий через GigaChat
|
||||
- **ChallengeCheckQueue.ts** - управление очередью проверок
|
||||
- **challengeQueueInstance.ts** - singleton экземпляр очереди
|
||||
|
||||
### Очередь проверки
|
||||
|
||||
Очередь работает in-memory с ограничением на количество одновременных проверок.
|
||||
|
||||
- Элементы добавляются в очередь при отправке задания
|
||||
- Обработка происходит автоматически каждую секунду
|
||||
- Количество параллельных проверок ограничено `CHALLENGE_LLM_THREADS`
|
||||
- После завершения проверки результаты сохраняются в БД
|
||||
- Записи в очереди удаляются через 5 минут после завершения
|
||||
|
||||
### Проверка через LLM
|
||||
|
||||
Для проверки используется GigaChat с промптом, который:
|
||||
- Получает описание задания
|
||||
- Получает результат пользователя
|
||||
- Возвращает статус (ПРИНЯТО/ДОРАБОТКА) и feedback
|
||||
|
||||
Ответ парсится и сохраняется в submission.
|
||||
|
||||
## Примеры использования
|
||||
|
||||
### Типичный flow пользователя
|
||||
|
||||
1. Авторизация: `POST /api/challenge/auth`
|
||||
2. Получение цепочек: `GET /api/challenge/chains`
|
||||
3. Получение заданий цепочки: `GET /api/challenge/chain/:chainId`
|
||||
4. Отправка результата: `POST /api/challenge/submit`
|
||||
5. Polling проверки: `GET /api/challenge/check-status/:queueId` (каждые 2-3 секунды)
|
||||
6. Просмотр статистики: `GET /api/challenge/user/:userId/stats`
|
||||
|
||||
### Типичный flow администратора
|
||||
|
||||
1. Создание заданий: `POST /api/challenge/task`
|
||||
2. Создание цепочки: `POST /api/challenge/chain`
|
||||
3. Просмотр общей статистики: `GET /api/challenge/stats`
|
||||
4. Просмотр расширенной статистики: `GET /api/challenge/stats/v2` (опционально с `?chainId=...`)
|
||||
|
||||
## Ограничения и особенности
|
||||
|
||||
- Очередь работает in-memory, при перезапуске сервера незавершенные проверки вернутся в статус `pending`
|
||||
- История всех попыток сохраняется полностью
|
||||
- Markdown описания заданий хранятся как простой текст в БД
|
||||
- Аутентификация простая, без паролей (только nickname)
|
||||
- Nickname должен быть уникальным в системе
|
||||
|
||||
Reference in New Issue
Block a user