From 5874dcdfd455b52a9b26158cc7ed38d7519b5d2a Mon Sep 17 00:00:00 2001 From: Sergey Karmanov Date: Wed, 13 May 2026 17:06:51 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D1=8E?= =?UTF-8?q?=20api=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D0=B4=D0=BB=D1=8F=20git?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 + backend/UniVerse.Api/Program.cs | 18 +- backend/UniVerse.Api/UniVerse.Api.csproj | 13 + backend/UniVerse.Api/openapi.json | 5578 ++++++++++++++++++++++ 4 files changed, 5607 insertions(+), 5 deletions(-) create mode 100644 backend/UniVerse.Api/openapi.json diff --git a/README.md b/README.md index 4df483b..4e46651 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ UniVerse — backend (ASP.NET Core) для университетской платформы расписания, лекций, отзывов и геймификации. +[Документация API](backend/UniVerse.Api/openapi.v1.json) +[Документация бекнда](docs/backend.md) + ## Что внутри - Расписание/события и сущности: курсы, лекции, аудитории (locations) diff --git a/backend/UniVerse.Api/Program.cs b/backend/UniVerse.Api/Program.cs index f082b7c..741149a 100644 --- a/backend/UniVerse.Api/Program.cs +++ b/backend/UniVerse.Api/Program.cs @@ -18,6 +18,8 @@ using UniVerse.Infrastructure.Notifications; var builder = WebApplication.CreateBuilder(args); var useAspire = builder.Configuration.GetValue("Aspire:Enabled"); +var isOpenApiGeneration = AppDomain.CurrentDomain.GetAssemblies() + .Any(assembly => assembly.GetName().Name == "GetDocument.Insider"); if (useAspire) { @@ -98,10 +100,13 @@ builder.Services.AddTransient(); builder.Services.Configure(builder.Configuration.GetSection("Email:Smtp")); builder.Services.AddQuartz(); -builder.Services.AddQuartzHostedService(options => +if (!isOpenApiGeneration) { - options.WaitForJobsToComplete = true; -}); + builder.Services.AddQuartzHostedService(options => + { + options.WaitForJobsToComplete = true; + }); +} // --- HTTP Clients --- builder.Services.AddHttpClient(client => @@ -117,8 +122,11 @@ builder.Services.AddHttpClient(client => }); // --- Background Services --- -builder.Services.AddHostedService(); -builder.Services.AddHostedService(); +if (!isOpenApiGeneration) +{ + builder.Services.AddHostedService(); + builder.Services.AddHostedService(); +} // --- Controllers --- builder.Services.AddControllers() diff --git a/backend/UniVerse.Api/UniVerse.Api.csproj b/backend/UniVerse.Api/UniVerse.Api.csproj index 021e9aa..aa5300a 100644 --- a/backend/UniVerse.Api/UniVerse.Api.csproj +++ b/backend/UniVerse.Api/UniVerse.Api.csproj @@ -8,6 +8,9 @@ Linux true true + true + $(BaseIntermediateOutputPath)openapi + --file-name openapi $(NoWarn);1591 @@ -39,4 +42,14 @@ + + + + diff --git a/backend/UniVerse.Api/openapi.json b/backend/UniVerse.Api/openapi.json new file mode 100644 index 0000000..c7e2f32 --- /dev/null +++ b/backend/UniVerse.Api/openapi.json @@ -0,0 +1,5578 @@ +{ + "openapi": "3.0.4", + "info": { + "title": "UniVerse API", + "description": "REST API веб-платформы UniVerse.\n\nАутентификация: JWT Bearer (получить через `POST /api/v1/auth/login/microsoft` или `POST /api/v1/auth/login/dev` в Development).", + "contact": { + "name": "UniVerse Dev" + }, + "version": "v1" + }, + "paths": { + "/api/v1/achievements": { + "get": { + "tags": [ + "Achievements" + ], + "summary": "Получить список всех достижений.", + "description": "Возвращает определения достижений (без информации о получении конкретным пользователем).\n Для достижений конкретного пользователя используйте GET /api/v1/users/{id}/achievements.\n\n**Required:** any authenticated user", + "responses": { + "200": { + "description": "Список достижений.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AchievementDto" + } + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + }, + "post": { + "tags": [ + "Achievements" + ], + "summary": "Создать новое достижение.", + "description": "Только Admin. Достижения автоматически присваиваются студентам при выполнении условий.\n\n**Required roles:** Admin", + "requestBody": { + "description": "Название, описание, иконка, награда в XP/монетах и условие получения.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateAchievementRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/CreateAchievementRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/CreateAchievementRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Достижение создано.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AchievementDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/achievements/{id}": { + "get": { + "tags": [ + "Achievements" + ], + "summary": "Получить достижение по ID.", + "description": "**Required:** any authenticated user", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID достижения.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Данные достижения.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AchievementDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Достижение не найдено.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + }, + "put": { + "tags": [ + "Achievements" + ], + "summary": "Обновить достижение по ID.", + "description": "Только Admin.\n\n**Required roles:** Admin", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID достижения.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "description": "Обновляемые поля достижения.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateAchievementRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/UpdateAchievementRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/UpdateAchievementRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Обновлённые данные достижения.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AchievementDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Достижение не найдено.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + }, + "delete": { + "tags": [ + "Achievements" + ], + "summary": "Удалить достижение по ID.", + "description": "Только Admin. Удаление не отзывает достижение у уже получивших его пользователей.\n\n**Required roles:** Admin", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID достижения.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "Достижение удалено." + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Достижение не найдено.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/auth/login/microsoft": { + "post": { + "tags": [ + "Auth" + ], + "summary": "Вход через Microsoft Entra ID (SPA/PKCE flow).", + "description": "Фронтенд самостоятельно обрабатывает редирект к Microsoft и передаёт сюда\nполученный authorization code. В ответ возвращается пара JWT-токенов;\nrefresh token устанавливается в HttpOnly cookie.", + "requestBody": { + "description": "Authorization code и redirect URI из Microsoft OAuth2.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LoginMicrosoftRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/LoginMicrosoftRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/LoginMicrosoftRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Успешный вход — возвращает access token и данные пользователя.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthResponse" + } + } + } + }, + "400": { + "description": "Неверный или просроченный authorization code.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + }, + "get": { + "tags": [ + "Auth" + ], + "summary": "Инициация server-driven входа через Microsoft (редирект-flow).", + "description": "Браузер переходит на этот URL; backend строит Microsoft authorize URL с CSRF state\nи редиректит пользователя на `login.microsoftonline.com`.\nПосле успешного входа Microsoft редиректит на `GET /api/v1/auth/callback/microsoft`.", + "parameters": [ + { + "name": "returnUrl", + "in": "query", + "description": "URL для редиректа после успешного входа (опционально).", + "schema": { + "type": "string" + } + } + ], + "responses": { + "302": { + "description": "Редирект на Microsoft authorize endpoint." + }, + "500": { + "description": "Microsoft authentication не настроен (AzureAd:TenantId/ClientId отсутствуют)." + } + } + } + }, + "/api/v1/auth/callback/microsoft": { + "get": { + "tags": [ + "Auth" + ], + "summary": "OAuth2 callback — обмен code на токены (server-driven flow).", + "description": "Microsoft редиректит браузер сюда после успешного входа.\nBackend валидирует CSRF state, обменивает code на токены,\nустанавливает refresh token cookie и редиректит на `returnUrl` с access token в URL-фрагменте.", + "parameters": [ + { + "name": "code", + "in": "query", + "description": "Authorization code от Microsoft.", + "schema": { + "type": "string" + } + }, + { + "name": "state", + "in": "query", + "description": "CSRF state для верификации.", + "schema": { + "type": "string" + } + }, + { + "name": "error", + "in": "query", + "description": "Код ошибки от Microsoft (если вход не удался).", + "schema": { + "type": "string" + } + }, + { + "name": "error_description", + "in": "query", + "description": "Описание ошибки от Microsoft.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Если returnUrl не задан — возвращает JSON с токенами (удобно для тестирования).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthResponse" + } + } + } + }, + "302": { + "description": "Успешный вход — редирект на returnUrl с токеном в URL-фрагменте." + }, + "400": { + "description": "Отсутствует authorization code.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "Ошибка от Microsoft или невалидный CSRF state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "/api/v1/auth/login/dev": { + "post": { + "tags": [ + "Auth" + ], + "summary": "Dev-only вход без OAuth (только в Development-окружении).", + "description": "Создаёт или находит пользователя по email без реального OAuth flow.\nВозвращает 404 в Production и Staging.", + "requestBody": { + "description": "Email, отображаемое имя и роль тестового пользователя.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DevLoginRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/DevLoginRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/DevLoginRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Успешный вход.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthResponse" + } + } + } + }, + "404": { + "description": "Endpoint недоступен вне Development.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "/api/v1/auth/refresh": { + "post": { + "tags": [ + "Auth" + ], + "summary": "Обновление access token по refresh token из HttpOnly cookie.", + "description": "Refresh token читается из HttpOnly cookie `refreshToken` (устанавливается при входе).\nВозвращает новую пару токенов и обновляет cookie.", + "responses": { + "200": { + "description": "Новая пара токенов.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthResponse" + } + } + } + }, + "401": { + "description": "Refresh token отсутствует, просрочен или отозван.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + } + } + }, + "/api/v1/auth/logout": { + "post": { + "tags": [ + "Auth" + ], + "summary": "Выход из системы — отзыв refresh token.", + "description": "Инвалидирует текущий refresh token в БД и удаляет cookie.\nПосле этого вызова access token остаётся валидным до истечения его TTL (30 минут).\n\n**Required:** any authenticated user", + "responses": { + "204": { + "description": "Выход выполнен успешно." + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/auth/me": { + "get": { + "tags": [ + "Auth" + ], + "summary": "Получение профиля текущего авторизованного пользователя.", + "description": "**Required:** any authenticated user", + "responses": { + "200": { + "description": "Данные текущего пользователя.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Пользователь не найден в БД (рассинхронизация токена).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/courses": { + "get": { + "tags": [ + "Courses" + ], + "summary": "Получить список курсов с фильтрацией и пагинацией.", + "description": "**Required:** any authenticated user", + "parameters": [ + { + "name": "TagId", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "Search", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "IsSynced", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "Page", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "PageSize", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Список курсов (пагинированный).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CourseDtoPagedResult" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + }, + "post": { + "tags": [ + "Courses" + ], + "summary": "Создать новый курс.", + "description": "Только Admin.\n\n**Required roles:** Admin", + "requestBody": { + "description": "Название и описание курса.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCourseRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/CreateCourseRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/CreateCourseRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Курс создан.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CourseDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/courses/{id}": { + "get": { + "tags": [ + "Courses" + ], + "summary": "Получить курс по ID (включая теги).", + "description": "**Required:** any authenticated user", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID курса.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Данные курса с тегами.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CourseDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Курс не найден.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + }, + "put": { + "tags": [ + "Courses" + ], + "summary": "Обновить курс по ID.", + "description": "Только Admin.\n\n**Required roles:** Admin", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID курса.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "description": "Новое название и/или описание.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCourseRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCourseRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/UpdateCourseRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Обновлённые данные курса.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CourseDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Курс не найден.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + }, + "delete": { + "tags": [ + "Courses" + ], + "summary": "Удалить курс по ID.", + "description": "Только Admin. Удаление курса каскадно удаляет связанные лекции.\n\n**Required roles:** Admin", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID курса.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "Курс удалён." + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Курс не найден.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/courses/{id}/tags": { + "post": { + "tags": [ + "Courses" + ], + "summary": "Привязать тег к курсу.", + "description": "Только Admin. Тег должен существовать в системе.\n\n**Required roles:** Admin", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID курса.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "description": "ID тега.", + "content": { + "application/json": { + "schema": { + "type": "integer", + "format": "int32" + } + }, + "text/json": { + "schema": { + "type": "integer", + "format": "int32" + } + }, + "application/*+json": { + "schema": { + "type": "integer", + "format": "int32" + } + } + } + }, + "responses": { + "204": { + "description": "Тег привязан." + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Курс или тег не найден.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "409": { + "description": "Тег уже привязан к курсу.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/courses/{id}/tags/{tagId}": { + "delete": { + "tags": [ + "Courses" + ], + "summary": "Отвязать тег от курса.", + "description": "Только Admin.\n\n**Required roles:** Admin", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID курса.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "tagId", + "in": "path", + "description": "ID тега.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "Тег отвязан." + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Курс или тег не найден, либо связь не существует.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/lectures": { + "get": { + "tags": [ + "Lectures" + ], + "summary": "Получить каталог лекций с фильтрацией и пагинацией.", + "description": "**Required:** any authenticated user", + "parameters": [ + { + "name": "DateFrom", + "in": "query", + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "DateTo", + "in": "query", + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "CourseId", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "TeacherId", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "Format", + "in": "query", + "schema": { + "$ref": "#/components/schemas/LectureFormat" + } + }, + { + "name": "IsOpen", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "TagId", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "Search", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "Page", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "PageSize", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Список лекций (пагинированный).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LectureDtoPagedResult" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + }, + "post": { + "tags": [ + "Lectures" + ], + "summary": "Создать новую лекцию.", + "description": "Только Admin. Курс задаётся при создании и не может быть изменён.\n\n**Required roles:** Admin", + "requestBody": { + "description": "Данные лекции: курс, преподаватель, локация, время, формат, вместимость.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateLectureRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/CreateLectureRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/CreateLectureRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Лекция создана.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LectureDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/lectures/{id}": { + "get": { + "tags": [ + "Lectures" + ], + "summary": "Получить детальную карточку лекции по ID.", + "description": "Включает флаг `isEnrolled` — записан ли текущий пользователь на эту лекцию.\n\n**Required:** any authenticated user", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID лекции.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Детальные данные лекции.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LectureDetailDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Лекция не найдена.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + }, + "put": { + "tags": [ + "Lectures" + ], + "summary": "Обновить лекцию по ID.", + "description": "Admin или Teacher. CourseId изменить нельзя.\n\n**Required roles:** Admin, Teacher", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID лекции.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "description": "Обновляемые поля: преподаватель, локация, время, формат, описание.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateLectureRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/UpdateLectureRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/UpdateLectureRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Обновлённые данные лекции.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LectureDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin или Teacher.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Лекция не найдена.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + }, + "delete": { + "tags": [ + "Lectures" + ], + "summary": "Удалить лекцию по ID.", + "description": "Только Admin. Каскадно удаляет записи и отзывы.\n\n**Required roles:** Admin", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID лекции.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "Лекция удалена." + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Лекция не найдена.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/lectures/{id}/enroll": { + "post": { + "tags": [ + "Lectures" + ], + "summary": "Записаться на лекцию.", + "description": "Только Student. Проверяет наличие свободных мест и отсутствие повторной записи.\nПосле посещения начисляются монеты через gamification.\n\n**Required roles:** Student", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID лекции.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "Запись выполнена." + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Student.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Лекция не найдена.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "409": { + "description": "Студент уже записан или мест нет.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + }, + "delete": { + "tags": [ + "Lectures" + ], + "summary": "Отменить запись на лекцию.", + "description": "Только Student. Отменить можно только свою запись.\n\n**Required roles:** Student", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID лекции.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "Запись отменена." + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Student.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Лекция или запись не найдена.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/lectures/{id}/attendance/{userId}": { + "patch": { + "tags": [ + "Lectures" + ], + "summary": "Отметить посещение студента на лекции.", + "description": "Admin или Teacher. При отметке `attended=true` начисляются монеты за посещение\nчерез gamification service.\n\n**Required roles:** Admin, Teacher", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID лекции.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "userId", + "in": "path", + "description": "ID студента.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "description": "true — посетил, false — не посетил.", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + }, + "application/*+json": { + "schema": { + "type": "boolean" + } + } + } + }, + "responses": { + "204": { + "description": "Посещение отмечено." + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin или Teacher.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Лекция или запись студента не найдена.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/lectures/{id}/enrollments": { + "get": { + "tags": [ + "Lectures" + ], + "summary": "Получить список записавшихся студентов на лекцию.", + "description": "Только Admin или Teacher. Включает флаг посещения (`attended`).\n\n**Required roles:** Admin, Teacher", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID лекции.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "Page", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "PageSize", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Список записей (пагинированный).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EnrollmentDtoPagedResult" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin или Teacher.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Лекция не найдена.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/lectures/{id}/reviews": { + "get": { + "tags": [ + "Lectures" + ], + "summary": "Получить отзывы к лекции.", + "description": "**Required:** any authenticated user", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID лекции.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "Page", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "PageSize", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Список отзывов (пагинированный).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReviewDtoPagedResult" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Лекция не найдена.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/locations": { + "get": { + "tags": [ + "Locations" + ], + "summary": "Получить список всех локаций.", + "description": "**Required:** any authenticated user", + "responses": { + "200": { + "description": "Список локаций.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LocationDto" + } + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + }, + "post": { + "tags": [ + "Locations" + ], + "summary": "Создать новую локацию.", + "description": "Только Admin. Локации также создаются автоматически при синхронизации с Modeus.\n\n**Required roles:** Admin", + "requestBody": { + "description": "Название, корпус, аудитория и/или адрес.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateLocationRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/CreateLocationRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/CreateLocationRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Локация создана.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LocationDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/locations/{id}": { + "get": { + "tags": [ + "Locations" + ], + "summary": "Получить локацию по ID.", + "description": "**Required:** any authenticated user", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID локации.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Данные локации.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LocationDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Локация не найдена.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + }, + "put": { + "tags": [ + "Locations" + ], + "summary": "Обновить локацию по ID.", + "description": "Только Admin.\n\n**Required roles:** Admin", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID локации.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "description": "Обновляемые поля: название, корпус, аудитория, адрес.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateLocationRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/UpdateLocationRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/UpdateLocationRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Обновлённые данные локации.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LocationDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Локация не найдена.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + }, + "delete": { + "tags": [ + "Locations" + ], + "summary": "Удалить локацию по ID.", + "description": "Только Admin. При удалении локации у связанных лекций поле `locationId` становится null.\n\n**Required roles:** Admin", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID локации.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "Локация удалена." + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Локация не найдена.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/notifications": { + "get": { + "tags": [ + "Notifications" + ], + "summary": "Получить уведомления текущего пользователя.", + "description": "**Required:** any authenticated user", + "parameters": [ + { + "name": "Page", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "PageSize", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Список уведомлений.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserNotificationDtoPagedResult" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/notifications/read-all": { + "patch": { + "tags": [ + "Notifications" + ], + "summary": "Отметить все уведомления текущего пользователя как прочитанные.", + "description": "**Required:** any authenticated user", + "responses": { + "204": { + "description": "Уведомления отмечены прочитанными." + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/notifications/send": { + "post": { + "tags": [ + "Notifications" + ], + "summary": "Отправить уведомление немедленно.", + "description": "Канал задаётся строкой, например `email`. Новые провайдеры добавляются через `INotificationProvider`.\n\n**Required roles:** Admin", + "requestBody": { + "description": "Канал, получатель, тема и текст уведомления.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SendNotificationRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SendNotificationRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/SendNotificationRequest" + } + } + } + }, + "responses": { + "202": { + "description": "Уведомление принято к отправке." + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/notifications/schedule": { + "post": { + "tags": [ + "Notifications" + ], + "summary": "Запланировать отложенную отправку уведомления через Quartz.NET.", + "description": "**Required roles:** Admin", + "requestBody": { + "description": "Уведомление и момент отправки.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScheduleNotificationRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/ScheduleNotificationRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/ScheduleNotificationRequest" + } + } + } + }, + "responses": { + "202": { + "description": "Уведомление поставлено в очередь Quartz.NET.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ScheduledNotificationResponse" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/reviews": { + "post": { + "tags": [ + "Reviews" + ], + "summary": "Создать отзыв к лекции.", + "description": "Только Student. После создания отзыв помещается в очередь LLM-анализа\n(статус `Pending`). LLM оценивает содержательность и начисляет монеты\nскрытно от пользователя.\n\n**Required roles:** Student", + "requestBody": { + "description": "ID лекции, оценка (Like/Neutral/Dislike) и текст отзыва.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateReviewRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/CreateReviewRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/CreateReviewRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Отзыв создан и поставлен в очередь на LLM-анализ.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReviewDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Student.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Лекция не найдена.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "409": { + "description": "Студент уже оставил отзыв к этой лекции.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/reviews/{id}": { + "get": { + "tags": [ + "Reviews" + ], + "summary": "Получить отзыв по ID.", + "description": "**Required:** any authenticated user", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID отзыва.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Данные отзыва (включая LLM-статус и сентимент).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReviewDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Отзыв не найден.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + }, + "put": { + "tags": [ + "Reviews" + ], + "summary": "Обновить отзыв.", + "description": "Разрешено любому авторизованному пользователю, но сервис проверяет владельца.\nИзменение текста сбрасывает LLM-статус в `Pending` (повторный анализ).\n\n**Required:** any authenticated user", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID отзыва.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "description": "Новая оценка и/или текст.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateReviewRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/UpdateReviewRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/UpdateReviewRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Обновлённые данные отзыва.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReviewDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Отзыв принадлежит другому пользователю.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Отзыв не найден.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + }, + "delete": { + "tags": [ + "Reviews" + ], + "summary": "Удалить отзыв.", + "description": "Владелец может удалить свой отзыв. Admin может удалить любой.\n\n**Required:** any authenticated user", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID отзыва.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "Отзыв удалён." + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Нет прав на удаление (не владелец и не Admin).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Отзыв не найден.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/reviews/pending": { + "get": { + "tags": [ + "Reviews" + ], + "summary": "Получить список отзывов, ожидающих LLM-анализа.", + "description": "Только Admin. Используется для мониторинга очереди обработки.\n\n**Required roles:** Admin", + "parameters": [ + { + "name": "Page", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "PageSize", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Список отзывов со статусом Pending (пагинированный).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReviewDtoPagedResult" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/reviews/{id}/reanalyze": { + "post": { + "tags": [ + "Reviews" + ], + "summary": "Запустить повторный LLM-анализ отзыва.", + "description": "Только Admin. Сбрасывает статус отзыва на `Pending` и ставит его\nв очередь на повторную обработку фоновым сервисом.\n\n**Required roles:** Admin", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID отзыва.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "Повторный анализ запланирован." + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Отзыв не найден.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/sync/schedule": { + "post": { + "tags": [ + "Sync" + ], + "summary": "Запустить синхронизацию расписания лекций из Modeus.", + "description": "Только Admin. Выполняет upsert лекций и связанных курсов на основе данных\nиз внешнего API `schedule.rdcenter.ru`. Поддерживает фильтрацию по специальности,\nпериоду и типу занятий.\n\n**Required roles:** Admin", + "requestBody": { + "description": "Параметры синхронизации: specialtyCode, timeMin/timeMax, typeId.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SyncScheduleRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/SyncScheduleRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/SyncScheduleRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Результат синхронизации: кол-во созданных, обновлённых и пропущенных записей.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SyncResultDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/sync/status": { + "get": { + "tags": [ + "Sync" + ], + "summary": "Получить статус последней синхронизации.", + "description": "Только Admin. Возвращает время и результат последней успешной синхронизации.\n\n**Required roles:** Admin", + "responses": { + "200": { + "description": "Статус синхронизации.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SyncStatusDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/sync/rooms": { + "post": { + "tags": [ + "Sync" + ], + "summary": "Синхронизировать аудитории (локации) из Modeus.", + "description": "Только Admin. Импортирует аудитории из `schedule.rdcenter.ru` и создаёт\nсоответствующие записи в таблице locations.\n\n**Required roles:** Admin", + "responses": { + "200": { + "description": "Результат синхронизации аудиторий.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SyncResultDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/sync/employees": { + "post": { + "tags": [ + "Sync" + ], + "summary": "Поиск преподавателей в Modeus по ФИО.", + "description": "Только Admin. Ищет преподавателей через внешнее API и возвращает список\nдля ручного импорта. Найденные преподаватели не создаются автоматически.\n\n**Required roles:** Admin", + "parameters": [ + { + "name": "fullname", + "in": "query", + "description": "Полное имя или часть имени преподавателя для поиска.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Список найденных преподавателей.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EmployeeDto" + } + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/tags": { + "get": { + "tags": [ + "Tags" + ], + "summary": "Получить список тегов с опциональной фильтрацией по типу и родителю.", + "description": "**Required:** any authenticated user", + "parameters": [ + { + "name": "type", + "in": "query", + "description": "Тип тега: Institute, Faculty, Subject, Organization, Topic, Other.", + "schema": { + "$ref": "#/components/schemas/TagType" + } + }, + { + "name": "parentId", + "in": "query", + "description": "ID родительского тега (фильтрация дочерних).", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Список тегов.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TagDto" + } + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + }, + "post": { + "tags": [ + "Tags" + ], + "summary": "Создать новый тег.", + "description": "Только Admin.\n\n**Required roles:** Admin", + "requestBody": { + "description": "Название, тип и опциональный родительский тег.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTagRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/CreateTagRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/CreateTagRequest" + } + } + } + }, + "responses": { + "201": { + "description": "Тег создан.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TagDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/tags/{id}": { + "get": { + "tags": [ + "Tags" + ], + "summary": "Получить тег по ID.", + "description": "**Required:** any authenticated user", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID тега.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Данные тега.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TagDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Тег не найден.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + }, + "put": { + "tags": [ + "Tags" + ], + "summary": "Обновить тег по ID.", + "description": "Только Admin.\n\n**Required roles:** Admin", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID тега.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "description": "Новое название, тип и/или родительский тег.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateTagRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/UpdateTagRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/UpdateTagRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Обновлённые данные тега.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TagDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Тег не найден.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + }, + "delete": { + "tags": [ + "Tags" + ], + "summary": "Удалить тег по ID.", + "description": "Только Admin. Удаление тега каскадно удаляет привязки к курсам (`course_tags`).\nДочерние теги остаются, но их `parentId` становится null.\n\n**Required roles:** Admin", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID тега.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "Тег удалён." + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Тег не найден.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/tags/tree": { + "get": { + "tags": [ + "Tags" + ], + "summary": "Получить иерархическое дерево всех тегов.", + "description": "Возвращает корневые теги с вложенными дочерними тегами.\nПолезно для построения фильтрующих UI-компонентов.\n\n**Required:** any authenticated user", + "responses": { + "200": { + "description": "Иерархический список тегов.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TagTreeDto" + } + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/users/{id}": { + "get": { + "tags": [ + "Users" + ], + "summary": "Получить профиль пользователя по ID.", + "description": "**Required:** any authenticated user", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID пользователя.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Данные пользователя.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Пользователь не найден.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + }, + "put": { + "tags": [ + "Users" + ], + "summary": "Обновить профиль пользователя (displayName, avatarUrl).", + "description": "Разрешено только самому пользователю или Admin.\n\n**Required:** any authenticated user", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID пользователя.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "description": "Обновляемые поля профиля.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateUserRequest" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/UpdateUserRequest" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/UpdateUserRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Обновлённые данные пользователя.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Нет прав — только владелец профиля или Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Пользователь не найден.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/users/{id}/stats": { + "get": { + "tags": [ + "Users" + ], + "summary": "Получить статистику пользователя (XP, монеты, уровень, посещения).", + "description": "**Required:** any authenticated user", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID пользователя.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Статистика пользователя.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserStatsDto" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Пользователь не найден.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/users/{id}/enrollments": { + "get": { + "tags": [ + "Users" + ], + "summary": "Получить список записей пользователя на лекции.", + "description": "Разрешено только самому пользователю или Admin.\n\n**Required:** any authenticated user", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID пользователя.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "Page", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "PageSize", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Список записей (пагинированный)." + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Нет прав — только владелец или Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/users/{id}/reviews": { + "get": { + "tags": [ + "Users" + ], + "summary": "Получить отзывы пользователя.", + "description": "**Required:** any authenticated user", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID пользователя.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "Page", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "PageSize", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Список отзывов (пагинированный).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReviewDtoPagedResult" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/users/{id}/achievements": { + "get": { + "tags": [ + "Users" + ], + "summary": "Получить достижения пользователя.", + "description": "**Required:** any authenticated user", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID пользователя.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Список полученных достижений.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserAchievementDto" + } + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/users/{id}/transactions": { + "get": { + "tags": [ + "Users" + ], + "summary": "Получить историю транзакций монет пользователя.", + "description": "Разрешено только самому пользователю или Admin.\n\n**Required:** any authenticated user", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID пользователя.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "Page", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "PageSize", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "История транзакций (пагинированная).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CoinTransactionDtoPagedResult" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Нет прав — только владелец или Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/users": { + "get": { + "tags": [ + "Users" + ], + "summary": "Получить список всех пользователей с фильтрацией и пагинацией.", + "description": "Только Admin.\n\n**Required roles:** Admin", + "parameters": [ + { + "name": "Search", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "Role", + "in": "query", + "schema": { + "$ref": "#/components/schemas/UserRole" + } + }, + { + "name": "IsActive", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "Page", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "PageSize", + "in": "query", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Список пользователей (пагинированный).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDtoPagedResult" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/users/{id}/role": { + "patch": { + "tags": [ + "Users" + ], + "summary": "Изменить набор ролей пользователя.", + "description": "Только Admin. Доступные роли: Student, Teacher, Admin.\n\n**Required roles:** Admin", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID пользователя.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "description": "Новый набор ролей пользователя.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserRole" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserRole" + } + } + }, + "application/*+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserRole" + } + } + } + } + }, + "responses": { + "204": { + "description": "Роли успешно изменены." + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Пользователь не найден.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + }, + "/api/v1/users/{id}/active": { + "patch": { + "tags": [ + "Users" + ], + "summary": "Активировать или деактивировать аккаунт пользователя.", + "description": "Только Admin. Деактивированный пользователь не может войти в систему.\n\n**Required roles:** Admin", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID пользователя.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "description": "true — активировать, false — деактивировать.", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + }, + "text/json": { + "schema": { + "type": "boolean" + } + }, + "application/*+json": { + "schema": { + "type": "boolean" + } + } + } + }, + "responses": { + "204": { + "description": "Статус успешно изменён." + }, + "401": { + "description": "Требуется аутентификация.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "Требуется роль Admin.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Пользователь не найден.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + } + }, + "security": [ + { + "Bearer": [ ] + } + ] + } + } + }, + "components": { + "schemas": { + "AchievementDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, + "iconUrl": { + "type": "string", + "nullable": true + }, + "xpReward": { + "type": "integer", + "format": "int32" + }, + "coinReward": { + "type": "integer", + "format": "int32" + }, + "condition": { + "type": "string", + "nullable": true + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false + }, + "AuthResponse": { + "type": "object", + "properties": { + "accessToken": { + "type": "string", + "nullable": true + }, + "expiresAt": { + "type": "string", + "format": "date-time" + }, + "user": { + "$ref": "#/components/schemas/UserAuthDto" + } + }, + "additionalProperties": false + }, + "CoinTransactionDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "amount": { + "type": "integer", + "format": "int32" + }, + "type": { + "$ref": "#/components/schemas/CoinTransactionType" + }, + "reviewId": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "achievementId": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false + }, + "CoinTransactionDtoPagedResult": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CoinTransactionDto" + }, + "nullable": true + }, + "totalCount": { + "type": "integer", + "format": "int32" + }, + "page": { + "type": "integer", + "format": "int32" + }, + "pageSize": { + "type": "integer", + "format": "int32" + }, + "totalPages": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "CoinTransactionType": { + "enum": [ + "ReviewReward", + "AchievementReward", + "AttendanceReward", + "AdminAdjustment" + ], + "type": "string" + }, + "CourseDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, + "isSynced": { + "type": "boolean" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TagDto" + }, + "nullable": true + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false + }, + "CourseDtoPagedResult": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CourseDto" + }, + "nullable": true + }, + "totalCount": { + "type": "integer", + "format": "int32" + }, + "page": { + "type": "integer", + "format": "int32" + }, + "pageSize": { + "type": "integer", + "format": "int32" + }, + "totalPages": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "CreateAchievementRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, + "iconUrl": { + "type": "string", + "nullable": true + }, + "xpReward": { + "type": "integer", + "format": "int32" + }, + "coinReward": { + "type": "integer", + "format": "int32" + }, + "condition": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "CreateCourseRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "CreateLectureRequest": { + "type": "object", + "properties": { + "courseId": { + "type": "integer", + "format": "int32" + }, + "teacherId": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "locationId": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "title": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, + "format": { + "$ref": "#/components/schemas/LectureFormat" + }, + "startsAt": { + "type": "string", + "format": "date-time" + }, + "endsAt": { + "type": "string", + "format": "date-time" + }, + "isOpen": { + "type": "boolean" + }, + "maxEnrollments": { + "type": "integer", + "format": "int32" + }, + "onlineUrl": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "CreateLocationRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "building": { + "type": "string", + "nullable": true + }, + "room": { + "type": "string", + "nullable": true + }, + "address": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "CreateReviewRequest": { + "type": "object", + "properties": { + "lectureId": { + "type": "integer", + "format": "int32" + }, + "rating": { + "$ref": "#/components/schemas/ReviewRating" + }, + "text": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "CreateTagRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "type": { + "$ref": "#/components/schemas/TagType" + }, + "parentId": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + "additionalProperties": false + }, + "DevLoginRequest": { + "type": "object", + "properties": { + "email": { + "type": "string", + "nullable": true + }, + "displayName": { + "type": "string", + "nullable": true + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserRole" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "EmployeeDto": { + "type": "object", + "properties": { + "id": { + "type": "string", + "nullable": true + }, + "fullName": { + "type": "string", + "nullable": true + }, + "department": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "EnrollmentDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "userId": { + "type": "integer", + "format": "int32" + }, + "userName": { + "type": "string", + "nullable": true + }, + "userEmail": { + "type": "string", + "nullable": true + }, + "attended": { + "type": "boolean" + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false + }, + "EnrollmentDtoPagedResult": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EnrollmentDto" + }, + "nullable": true + }, + "totalCount": { + "type": "integer", + "format": "int32" + }, + "page": { + "type": "integer", + "format": "int32" + }, + "pageSize": { + "type": "integer", + "format": "int32" + }, + "totalPages": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "LectureDetailDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "courseId": { + "type": "integer", + "format": "int32" + }, + "courseName": { + "type": "string", + "nullable": true + }, + "teacherId": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "teacherName": { + "type": "string", + "nullable": true + }, + "locationId": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "locationName": { + "type": "string", + "nullable": true + }, + "title": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, + "format": { + "$ref": "#/components/schemas/LectureFormat" + }, + "startsAt": { + "type": "string", + "format": "date-time" + }, + "endsAt": { + "type": "string", + "format": "date-time" + }, + "isOpen": { + "type": "boolean" + }, + "maxEnrollments": { + "type": "integer", + "format": "int32" + }, + "enrollmentsCount": { + "type": "integer", + "format": "int32" + }, + "onlineUrl": { + "type": "string", + "nullable": true + }, + "createdAt": { + "type": "string", + "format": "date-time" + }, + "isEnrolled": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "LectureDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "courseId": { + "type": "integer", + "format": "int32" + }, + "courseName": { + "type": "string", + "nullable": true + }, + "teacherId": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "teacherName": { + "type": "string", + "nullable": true + }, + "locationId": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "locationName": { + "type": "string", + "nullable": true + }, + "title": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, + "format": { + "$ref": "#/components/schemas/LectureFormat" + }, + "startsAt": { + "type": "string", + "format": "date-time" + }, + "endsAt": { + "type": "string", + "format": "date-time" + }, + "isOpen": { + "type": "boolean" + }, + "maxEnrollments": { + "type": "integer", + "format": "int32" + }, + "enrollmentsCount": { + "type": "integer", + "format": "int32" + }, + "onlineUrl": { + "type": "string", + "nullable": true + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false + }, + "LectureDtoPagedResult": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LectureDto" + }, + "nullable": true + }, + "totalCount": { + "type": "integer", + "format": "int32" + }, + "page": { + "type": "integer", + "format": "int32" + }, + "pageSize": { + "type": "integer", + "format": "int32" + }, + "totalPages": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "LectureFormat": { + "enum": [ + "Online", + "Offline" + ], + "type": "string" + }, + "LocationDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string", + "nullable": true + }, + "building": { + "type": "string", + "nullable": true + }, + "room": { + "type": "string", + "nullable": true + }, + "address": { + "type": "string", + "nullable": true + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false + }, + "LoginMicrosoftRequest": { + "type": "object", + "properties": { + "authorizationCode": { + "type": "string", + "nullable": true + }, + "redirectUri": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "ProblemDetails": { + "type": "object", + "properties": { + "type": { + "type": "string", + "nullable": true + }, + "title": { + "type": "string", + "nullable": true + }, + "status": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "detail": { + "type": "string", + "nullable": true + }, + "instance": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": { } + }, + "ReviewDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "lectureId": { + "type": "integer", + "format": "int32" + }, + "lectureTitle": { + "type": "string", + "nullable": true + }, + "userId": { + "type": "integer", + "format": "int32" + }, + "userName": { + "type": "string", + "nullable": true + }, + "rating": { + "$ref": "#/components/schemas/ReviewRating" + }, + "text": { + "type": "string", + "nullable": true + }, + "llmStatus": { + "$ref": "#/components/schemas/ReviewLlmStatus" + }, + "sentiment": { + "$ref": "#/components/schemas/ReviewSentiment" + }, + "qualityScore": { + "type": "number", + "format": "double", + "nullable": true + }, + "isInformative": { + "type": "boolean", + "nullable": true + }, + "llmTags": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false + }, + "ReviewDtoPagedResult": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ReviewDto" + }, + "nullable": true + }, + "totalCount": { + "type": "integer", + "format": "int32" + }, + "page": { + "type": "integer", + "format": "int32" + }, + "pageSize": { + "type": "integer", + "format": "int32" + }, + "totalPages": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "ReviewLlmStatus": { + "enum": [ + "Pending", + "Analyzed", + "Rejected" + ], + "type": "string" + }, + "ReviewRating": { + "enum": [ + "Like", + "Neutral", + "Dislike" + ], + "type": "string" + }, + "ReviewSentiment": { + "enum": [ + "Positive", + "Neutral", + "Negative" + ], + "type": "string" + }, + "ScheduleNotificationRequest": { + "type": "object", + "properties": { + "channel": { + "type": "string", + "nullable": true + }, + "recipient": { + "type": "string", + "nullable": true + }, + "subject": { + "type": "string", + "nullable": true + }, + "body": { + "type": "string", + "nullable": true + }, + "sendAt": { + "type": "string", + "format": "date-time" + }, + "recipientName": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "ScheduledNotificationResponse": { + "type": "object", + "properties": { + "jobId": { + "type": "string", + "nullable": true + }, + "sendAt": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false + }, + "SendNotificationRequest": { + "type": "object", + "properties": { + "channel": { + "type": "string", + "nullable": true + }, + "recipient": { + "type": "string", + "nullable": true + }, + "subject": { + "type": "string", + "nullable": true + }, + "body": { + "type": "string", + "nullable": true + }, + "recipientName": { + "type": "string", + "nullable": true + }, + "metadata": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "SyncResultDto": { + "type": "object", + "properties": { + "created": { + "type": "integer", + "format": "int32" + }, + "updated": { + "type": "integer", + "format": "int32" + }, + "skipped": { + "type": "integer", + "format": "int32" + }, + "error": { + "type": "string", + "nullable": true + }, + "details": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "SyncScheduleRequest": { + "type": "object", + "properties": { + "specialtyCode": { + "type": "string", + "nullable": true + }, + "timeMin": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "timeMax": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "typeId": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "SyncStatusDto": { + "type": "object", + "properties": { + "lastSyncAt": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "status": { + "type": "string", + "nullable": true + }, + "lastResult": { + "$ref": "#/components/schemas/SyncResultDto" + } + }, + "additionalProperties": false + }, + "TagDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string", + "nullable": true + }, + "type": { + "$ref": "#/components/schemas/TagType" + }, + "parentId": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false + }, + "TagTreeDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "name": { + "type": "string", + "nullable": true + }, + "type": { + "$ref": "#/components/schemas/TagType" + }, + "children": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TagTreeDto" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "TagType": { + "enum": [ + "Institute", + "Faculty", + "Subject", + "Organization", + "Topic", + "Other" + ], + "type": "string" + }, + "UpdateAchievementRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, + "iconUrl": { + "type": "string", + "nullable": true + }, + "xpReward": { + "type": "integer", + "format": "int32" + }, + "coinReward": { + "type": "integer", + "format": "int32" + }, + "condition": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "UpdateCourseRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "UpdateLectureRequest": { + "type": "object", + "properties": { + "teacherId": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "locationId": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "title": { + "type": "string", + "nullable": true + }, + "description": { + "type": "string", + "nullable": true + }, + "format": { + "$ref": "#/components/schemas/LectureFormat" + }, + "startsAt": { + "type": "string", + "format": "date-time" + }, + "endsAt": { + "type": "string", + "format": "date-time" + }, + "isOpen": { + "type": "boolean" + }, + "maxEnrollments": { + "type": "integer", + "format": "int32" + }, + "onlineUrl": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "UpdateLocationRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "building": { + "type": "string", + "nullable": true + }, + "room": { + "type": "string", + "nullable": true + }, + "address": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "UpdateReviewRequest": { + "type": "object", + "properties": { + "rating": { + "$ref": "#/components/schemas/ReviewRating" + }, + "text": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "UpdateTagRequest": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "type": { + "$ref": "#/components/schemas/TagType" + }, + "parentId": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + "additionalProperties": false + }, + "UpdateUserRequest": { + "type": "object", + "properties": { + "displayName": { + "type": "string", + "nullable": true + }, + "avatarUrl": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "UserAchievementDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "achievement": { + "$ref": "#/components/schemas/AchievementDto" + }, + "awardedAt": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false + }, + "UserAuthDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "email": { + "type": "string", + "nullable": true + }, + "displayName": { + "type": "string", + "nullable": true + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserRole" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "UserDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "email": { + "type": "string", + "nullable": true + }, + "displayName": { + "type": "string", + "nullable": true + }, + "avatarUrl": { + "type": "string", + "nullable": true + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserRole" + }, + "nullable": true + }, + "isActive": { + "type": "boolean" + }, + "xp": { + "type": "integer", + "format": "int32" + }, + "coins": { + "type": "integer", + "format": "int32" + }, + "level": { + "type": "integer", + "format": "int32" + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false + }, + "UserDtoPagedResult": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserDto" + }, + "nullable": true + }, + "totalCount": { + "type": "integer", + "format": "int32" + }, + "page": { + "type": "integer", + "format": "int32" + }, + "pageSize": { + "type": "integer", + "format": "int32" + }, + "totalPages": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "UserNotificationDto": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string", + "nullable": true + }, + "title": { + "type": "string", + "nullable": true + }, + "body": { + "type": "string", + "nullable": true + }, + "isRead": { + "type": "boolean" + }, + "createdAt": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false + }, + "UserNotificationDtoPagedResult": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserNotificationDto" + }, + "nullable": true + }, + "totalCount": { + "type": "integer", + "format": "int32" + }, + "page": { + "type": "integer", + "format": "int32" + }, + "pageSize": { + "type": "integer", + "format": "int32" + }, + "totalPages": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + }, + "UserRole": { + "enum": [ + "Student", + "Teacher", + "Admin" + ], + "type": "string" + }, + "UserStatsDto": { + "type": "object", + "properties": { + "totalLectures": { + "type": "integer", + "format": "int32" + }, + "attendedLectures": { + "type": "integer", + "format": "int32" + }, + "totalReviews": { + "type": "integer", + "format": "int32" + }, + "xp": { + "type": "integer", + "format": "int32" + }, + "coins": { + "type": "integer", + "format": "int32" + }, + "level": { + "type": "integer", + "format": "int32" + }, + "achievementsCount": { + "type": "integer", + "format": "int32" + } + }, + "additionalProperties": false + } + }, + "securitySchemes": { + "Bearer": { + "type": "http", + "description": "Введите JWT access token, полученный из `/api/v1/auth/login/microsoft`.\n\nПример: `eyJhbGci...`", + "scheme": "bearer", + "bearerFormat": "JWT" + } + } + }, + "tags": [ + { + "name": "Achievements" + }, + { + "name": "Auth" + }, + { + "name": "Courses" + }, + { + "name": "Lectures" + }, + { + "name": "Locations" + }, + { + "name": "Notifications" + }, + { + "name": "Reviews" + }, + { + "name": "Sync" + }, + { + "name": "Tags" + }, + { + "name": "Users" + } + ] +} \ No newline at end of file