Files
UniVerse/docs/backend.md
serega404 98ad8ae74f
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 10s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 14s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 17s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 8s
docs: добавил подробную документацию по backend
2026-05-13 00:48:50 +03:00

28 KiB
Raw Permalink Blame History

Backend UniVerse

Этот документ помогает быстро вкатиться в backend UniVerse: понять слои, основные сущности, API, фоновые процессы, интеграции и точки расширения. Диаграммы написаны в Mermaid, поэтому их можно смотреть прямо в IDE/Markdown-просмотрщике с поддержкой Mermaid.

Что делает backend

Backend UniVerse обслуживает платформу открытых лекций:

  • хранит пользователей, роли, профили студентов и преподавателей;
  • ведет каталог курсов, тегов, локаций и лекций;
  • позволяет студентам записываться на лекции и оставлять отзывы;
  • анализирует отзывы через LLM в фоне;
  • начисляет монеты, XP и достижения;
  • отправляет и планирует уведомления;
  • синхронизирует расписание и аудитории из Modeus;
  • публикует REST API и Swagger/OpenAPI.

Основной solution: backend/UniVerse.sln.

Быстрый старт для чтения кода

Начинай с этих файлов:

  1. backend/UniVerse.Api/Program.cs - composition root: DI, middleware, auth, Swagger, фоновые сервисы.
  2. backend/UniVerse.Api/Controllers - HTTP API и роли доступа.
  3. backend/UniVerse.Application/Interfaces - контракты бизнес-сервисов.
  4. backend/UniVerse.Infrastructure/Services - реализация сценариев.
  5. backend/UniVerse.Domain/Entities - доменная модель.
  6. backend/UniVerse.Infrastructure/Data/AppDbContext.cs и Configurations - схема PostgreSQL.

Проекты и ответственность

flowchart LR
    Client[Frontend / API client]
    Api[UniVerse.Api<br/>Controllers, middleware, hosted services]
    App[UniVerse.Application<br/>DTO, interfaces, mappings]
    Infra[UniVerse.Infrastructure<br/>EF Core, services, external clients]
    Domain[UniVerse.Domain<br/>Entities, enums, exceptions]
    Db[(PostgreSQL)]
    Llm[OpenAI-compatible LLM]
    Modeus[Modeus / schedule.rdcenter.ru]
    Email[SMTP]

    Client --> Api
    Api --> App
    Api --> Infra
    Infra --> App
    Infra --> Domain
    App --> Domain
    Infra --> Db
    Infra --> Llm
    Infra --> Modeus
    Infra --> Email
Проект Что внутри Зависит от
UniVerse.Api ASP.NET Core Web API: контроллеры, JWT, CORS, Swagger, middleware, background services Application, Infrastructure, ServiceDefaults
UniVerse.Application DTO, интерфейсы сервисов, mapping extensions Domain
UniVerse.Domain Entities, enums, доменные исключения ничего
UniVerse.Infrastructure EF Core, PostgreSQL, сервисы, LLM/Modeus/SMTP-клиенты, Quartz scheduler Domain, Application
UniVerse.AppHost .NET Aspire host для совместного запуска API и frontend dev server API и frontend как внешняя команда
UniVerse.Api.Tests xUnit-тесты авторизации, Swagger и геймификации API

Жизненный цикл HTTP-запроса

sequenceDiagram
    participant C as Client
    participant M as ASP.NET middleware
    participant Auth as JWT auth
    participant Ctrl as Controller
    participant Svc as Application interface / Infrastructure service
    participant Db as AppDbContext

    C->>M: HTTP request /api/v1/...
    M->>M: RequestLoggingMiddleware
    M->>M: ExceptionHandlingMiddleware
    M->>Auth: UseAuthentication / UseAuthorization
    Auth-->>Ctrl: ClaimsPrincipal with roles
    Ctrl->>Svc: DTO/request + current user id
    Svc->>Db: EF Core query/command
    Db-->>Svc: entities
    Svc-->>Ctrl: DTO/result
    Ctrl-->>C: JSON response

Пайплайн в Program.cs:

  • RequestLoggingMiddleware пишет входящие запросы.
  • ExceptionHandlingMiddleware переводит доменные исключения в application/problem+json.
  • CORS берет origins из Cors:Origins.
  • JWT Bearer проверяет issuer, audience, lifetime и signing key.
  • Swagger доступен в Development по /api/docs, JSON - /api/docs/v1/swagger.json.

Конфигурация

Основные секции:

Секция Назначение
ConnectionStrings:DefaultConnection подключение к PostgreSQL
Jwt:* issuer, audience, secret, TTL access/refresh токенов
AzureAd:* Microsoft Entra ID OAuth flow
Cors:Origins разрешенные origins frontend
Llm:* OpenAI-compatible endpoint, API key, model
ModeusApi:* endpoint и ключ внешнего расписания
Email:Smtp:* SMTP-провайдер уведомлений
Gamification:XpThresholds пороги уровней по XP
Aspire:Enabled включает service defaults при запуске через AppHost

Для окружений удобнее задавать переменные в формате Section__Key, например:

ConnectionStrings__DefaultConnection="Host=localhost;Port=5432;Database=universe;Username=postgres;Password=postgres"
Jwt__Secret="local-secret-at-least-32-characters"
Llm__ApiKey="..."
ModeusApi__ApiKey="..."
Email__Smtp__Host="smtp.example.com"

Важно: секреты не должны попадать в документацию, README, коммиты и логи. В dev-файлах могут быть локальные значения, но для реального запуска используй переменные окружения или secret storage.

Как запустить backend

Из корня репозитория:

cd backend
dotnet restore UniVerse.sln
dotnet ef database update --project UniVerse.Infrastructure --startup-project UniVerse.Api
dotnet run --project UniVerse.Api --launch-profile http

По launch profile API слушает http://localhost:5019, Swagger UI: http://localhost:5019/api/docs.

Тесты:

cd backend
dotnet test UniVerse.sln

Docker compose есть в корне:

Авторизация и роли

Роли заданы enum UserRole: Student, Teacher, Admin.

Токены:

  • access token - JWT Bearer, передается в Authorization: Bearer <token>;
  • refresh token - HttpOnly cookie refreshToken;
  • logout отзывает refresh token в БД, но уже выданный access token живет до истечения TTL.

Вход:

sequenceDiagram
    participant U as User browser
    participant F as Frontend
    participant A as UniVerse.Api
    participant MS as Microsoft Entra ID
    participant DB as PostgreSQL

    U->>F: Нажимает "Войти"
    F->>A: GET /api/v1/auth/login/microsoft?returnUrl=...
    A->>A: Генерирует state и cookie
    A-->>U: 302 redirect на Microsoft
    U->>MS: OAuth authorize
    MS-->>A: GET /api/v1/auth/callback/microsoft?code&state
    A->>A: Проверяет state
    A->>MS: Обменивает code на token
    A->>DB: Upsert user, roles, refresh token
    A-->>U: refreshToken cookie + redirect с access token во fragment

Dev-вход доступен только в Development:

POST /api/v1/auth/login/dev
Content-Type: application/json

{
  "email": "student@example.com",
  "displayName": "Student",
  "roles": ["Student"]
}

Карта API

Базовый префикс: /api/v1.

Область Endpoint Доступ Назначение
Auth POST /auth/login/microsoft public обмен authorization code на токены
Auth GET /auth/login/microsoft public server-driven redirect на Microsoft
Auth GET /auth/callback/microsoft public OAuth callback
Auth POST /auth/login/dev public, только Development dev login
Auth POST /auth/refresh public + refresh cookie обновить access token
Auth POST /auth/logout auth отозвать refresh token
Auth GET /auth/me auth текущий пользователь
Users GET /users Admin список пользователей
Users GET /users/{id} auth профиль пользователя
Users PUT /users/{id} владелец или Admin обновить displayName, avatarUrl
Users GET /users/{id}/stats auth XP, уровень, монеты, посещения
Users GET /users/{id}/reviews auth отзывы пользователя
Users GET /users/{id}/achievements auth достижения пользователя
Users GET /users/{id}/transactions владелец или Admin история монет
Users PATCH /users/{id}/role Admin заменить набор ролей
Users PATCH /users/{id}/active Admin активировать/деактивировать пользователя
Courses GET /courses auth список курсов с фильтрами
Courses GET /courses/{id} auth курс с тегами
Courses POST /courses Admin создать курс
Courses PUT /courses/{id} Admin обновить курс
Courses DELETE /courses/{id} Admin удалить курс
Courses POST /courses/{id}/tags Admin привязать тег
Courses DELETE /courses/{id}/tags/{tagId} Admin отвязать тег
Lectures GET /lectures auth каталог лекций
Lectures GET /lectures/{id} auth детальная карточка
Lectures POST /lectures Admin создать лекцию
Lectures PUT /lectures/{id} Admin, Teacher обновить лекцию
Lectures DELETE /lectures/{id} Admin удалить лекцию
Lectures POST /lectures/{id}/enroll Student записаться
Lectures DELETE /lectures/{id}/enroll Student отменить запись
Lectures PATCH /lectures/{id}/attendance/{userId} Admin, Teacher отметить посещение
Lectures GET /lectures/{id}/enrollments Admin, Teacher записавшиеся
Lectures GET /lectures/{id}/reviews auth отзывы по лекции
Reviews POST /reviews Student создать отзыв
Reviews GET /reviews/{id} auth получить отзыв
Reviews PUT /reviews/{id} владелец обновить отзыв и сбросить LLM-статус
Reviews DELETE /reviews/{id} владелец или Admin удалить отзыв
Reviews GET /reviews/pending Admin очередь LLM-анализа
Reviews POST /reviews/{id}/reanalyze Admin поставить отзыв на повторный анализ
Tags GET /tags auth список тегов
Tags GET /tags/tree auth дерево тегов
Tags GET /tags/{id} auth тег по id
Tags POST /tags Admin создать тег
Tags PUT /tags/{id} Admin обновить тег
Tags DELETE /tags/{id} Admin удалить тег
Locations GET /locations auth список локаций
Locations GET /locations/{id} auth локация по id
Locations POST /locations Admin создать локацию
Locations PUT /locations/{id} Admin обновить локацию
Locations DELETE /locations/{id} Admin удалить локацию
Achievements GET /achievements auth каталог достижений
Achievements GET /achievements/{id} auth достижение по id
Achievements POST /achievements Admin создать достижение
Achievements PUT /achievements/{id} Admin обновить достижение
Achievements DELETE /achievements/{id} Admin удалить достижение
Notifications GET /notifications auth уведомления текущего пользователя
Notifications PATCH /notifications/read-all auth отметить свои уведомления прочитанными
Notifications POST /notifications/send Admin отправить уведомление сразу
Notifications POST /notifications/schedule Admin запланировать уведомление через Quartz
Sync POST /sync/schedule Admin синхронизация лекций из Modeus
Sync POST /sync/rooms Admin синхронизация аудиторий
Sync POST /sync/employees?fullname=... Admin поиск сотрудников
Sync GET /sync/status Admin статус последней синхронизации

Доменная модель

erDiagram
    USERS ||--o{ USER_ROLES : has
    USERS ||--o| STUDENT_PROFILES : may_have
    USERS ||--o| TEACHER_PROFILES : may_have
    USERS ||--o{ REFRESH_TOKENS : owns
    USERS ||--o{ LECTURE_ENROLLMENTS : enrolls
    USERS ||--o{ REVIEWS : writes
    USERS ||--o{ USER_ACHIEVEMENTS : earns
    USERS ||--o{ COIN_TRANSACTIONS : receives
    USERS ||--o{ USER_NOTIFICATIONS : receives

    COURSES ||--o{ LECTURES : contains
    COURSES ||--o{ COURSE_TAGS : tagged
    TAGS ||--o{ COURSE_TAGS : assigned
    TAGS ||--o{ TAGS : parent_of

    LOCATIONS ||--o{ LECTURES : hosts
    LECTURES ||--o{ LECTURE_ENROLLMENTS : has
    LECTURES ||--o{ REVIEWS : receives
    USERS ||--o{ LECTURES : teaches

    ACHIEVEMENTS ||--o{ USER_ACHIEVEMENTS : awarded_as
    REVIEWS ||--o{ COIN_TRANSACTIONS : may_reward
    ACHIEVEMENTS ||--o{ COIN_TRANSACTIONS : may_reward

Ключевые таблицы и поля:

Сущность Смысл
users аккаунт: email, display name, avatar, active flag, Microsoft id, XP, coins
user_roles join table ролей; у пользователя может быть несколько ролей
student_profiles, teacher_profiles дополнительные данные профиля
refresh_tokens refresh token, expiry, revoked flag
courses дисциплины/курсы; могут быть синхронизированы из Modeus
tags иерархические теги с типом: institute/faculty/subject/topic/etc
course_tags связь many-to-many между курсами и тегами
lectures открытые лекции: курс, преподаватель, локация, время, формат, вместимость
lecture_enrollments записи студентов, флаг посещения
reviews отзывы студентов и результат LLM-анализа
achievements каталог достижений и условие получения
user_achievements полученные пользователем достижения
coin_transactions история начисления монет/XP
user_notifications in-app уведомления пользователя

EF Core приводит имена таблиц и колонок к snake_case в AppDbContext. PostgreSQL enums регистрируются для ролей, типов тегов, LLM-статусов, сентимента и типов транзакций.

Основные сценарии

Запись на лекцию

sequenceDiagram
    participant S as Student
    participant C as LecturesController
    participant LS as LectureService
    participant GS as GamificationService
    participant DB as PostgreSQL

    S->>C: POST /api/v1/lectures/{id}/enroll
    C->>LS: EnrollAsync(lectureId, currentUserId)
    LS->>DB: Load lecture with enrollments
    LS->>LS: Проверить isOpen, capacity, duplicate
    LS->>DB: Insert lecture_enrollment
    LS->>GS: CheckAndAwardAchievementsAsync(userId)
    GS->>DB: Проверить условия достижений
    GS-->>C: done
    C-->>S: 204 No Content

Условия:

  • лекция должна быть открыта (IsOpen = true);
  • если MaxEnrollments > 0, число записей не должно превышать лимит;
  • повторная запись дает 409 Conflict;
  • после записи проверяются достижения.

Посещение лекции

PATCH /api/v1/lectures/{id}/attendance/{userId} доступен Admin и Teacher. Сервис меняет LectureEnrollment.Attended. Если attended = true, запускается проверка достижений. В текущей реализации отдельное начисление монет за посещение не вызывается напрямую, но достижения могут начислить награду.

Отзыв и LLM-анализ

sequenceDiagram
    participant S as Student
    participant RC as ReviewsController
    participant RS as ReviewService
    participant BG as LlmProcessingBackgroundService
    participant LLM as LlmAnalysisService
    participant Client as LlmClient
    participant G as GamificationService
    participant DB as PostgreSQL

    S->>RC: POST /api/v1/reviews
    RC->>RS: CreateAsync(userId, request)
    RS->>DB: Insert review with LlmStatus=Pending
    RS->>G: CheckAndAwardAchievementsAsync(userId)
    RC-->>S: 201 ReviewDto

    loop каждые 2 минуты
        BG->>LLM: ProcessPendingReviewsAsync()
        LLM->>DB: взять до 10 Pending отзывов
        LLM->>Client: AnalyzeReviewAsync(text, lecture context)
        Client-->>LLM: qualityScore, sentiment, tags, isInformative
        LLM->>DB: сохранить результат, LlmStatus=Analyzed
        alt отзыв информативный
            LLM->>G: AwardCoinsAsync(..., 10, ReviewReward)
        end
        LLM->>G: CheckAndAwardAchievementsAsync(userId)
    end

LLM-запись хранит:

  • LlmStatus: Pending, Analyzed, Rejected;
  • Sentiment: Positive, Neutral, Negative;
  • QualityScore;
  • IsInformative;
  • LlmTags.

Если LLM-клиент падает, статус остается Pending, а фоновый сервис попробует снова на следующем цикле.

Геймификация

GamificationService делает две вещи:

  • начисляет монеты и XP через AwardCoinsAsync;
  • проверяет каталог достижений через CheckAndAwardAchievementsAsync.

Условия достижений хранятся строкой type:value, например:

Условие Что проверяется
first_activity:1 есть запись, отзыв или посещение
lectures_attended:N посещено минимум N лекций
reviews_written:N написано минимум N отзывов
lectures_registered:N оформлено минимум N записей
active_registrations:N есть N будущих записей
attendance_streak_weeks:N посещения N недель подряд
coins_earned:N получено минимум N положительных монет
level_reached:N достигнут уровень N
profile_completed:1 заполнены DisplayName и AvatarUrl

Каталог базовых достижений заполняется при старте через AchievementCatalogHostedService и AchievementCatalogSeeder.

Уведомления

flowchart LR
    Admin[Admin API request]
    Achievement[Achievement earned]
    NotificationService[NotificationService]
    Db[(user_notifications)]
    Provider[INotificationProvider]
    Email[EmailNotificationProvider / SMTP]
    Quartz[QuartzNotificationScheduler]
    Job[NotificationJob]

    Admin -->|POST /notifications/send| NotificationService
    Admin -->|POST /notifications/schedule| Quartz
    Achievement --> NotificationService
    NotificationService --> Db
    NotificationService --> Provider
    Provider --> Email
    Quartz --> Job
    Job --> NotificationService

Сейчас основной канал - email. In-app уведомления хранятся в user_notifications; пользователь может получить их через GET /api/v1/notifications и отметить все прочитанными через PATCH /api/v1/notifications/read-all.

Синхронизация Modeus

flowchart TD
    Admin[Admin] --> Sync[SyncController]
    Sync --> Service[ScheduleSyncService]
    Service --> Modeus[ModeusApiClient]
    Modeus --> External[schedule.rdcenter.ru]
    Service --> Courses[(courses)]
    Service --> Lectures[(lectures)]
    Service --> Locations[(locations)]
    Service --> Status[static _lastStatus]

POST /api/v1/sync/schedule:

  • запрашивает события из Modeus;
  • upsert-ит курс по ExternalId;
  • upsert-ит локацию из комнаты события;
  • upsert-ит лекцию по ExternalId;
  • сохраняет счетчики created, updated, skipped.

POST /api/v1/sync/rooms импортирует аудитории в locations. POST /api/v1/sync/employees только ищет сотрудников и не создает пользователей автоматически.

Статус последней синхронизации хранится в памяти процесса (static _lastStatus), поэтому после рестарта API он снова idle.

Ошибки и ответы

Доменные исключения:

Исключение HTTP
NotFoundException 404 Not Found
ForbiddenException 403 Forbidden
ConflictException 409 Conflict
UnauthorizedAccessException 401 Unauthorized
неизвестная ошибка 500 Internal Server Error

Формат ошибки:

{
  "type": "https://httpstatuses.com/404",
  "title": "Not Found",
  "status": 404,
  "detail": "Lecture with id 10 was not found.",
  "traceId": "..."
}

Фильтрация и пагинация

Общий формат пагинации:

{
  "items": [],
  "total": 0,
  "page": 1,
  "pageSize": 20,
  "totalPages": 0
}

Частые query-параметры:

  • page, pageSize - для пагинации;
  • search - текстовый поиск;
  • tagId, courseId, teacherId - фильтры каталога;
  • dateFrom, dateTo - даты лекций;
  • format - Online или Offline;
  • isOpen - открыта ли запись.

Swagger и тесты

Swagger:

  • UI: /api/docs;
  • JSON: /api/docs/v1/swagger.json;
  • AuthorizeOperationFilter добавляет Bearer security только к защищенным endpoint-ам и дописывает требуемые роли в описание.

Тесты:

  • EndpointAuthorizationTests проверяет, что защищенные endpoint-ы возвращают 401 анонимам и 403 неправильным ролям;
  • SwaggerDocumentTests проверяет генерацию OpenAPI и security metadata;
  • GamificationServiceTests проверяет правила достижений/наград.

Как добавить новый backend-сценарий

Обычный путь изменения:

  1. Добавить или изменить entity/enum в UniVerse.Domain, если меняется модель данных.
  2. Добавить DTO в UniVerse.Application/DTOs.
  3. Добавить контракт в UniVerse.Application/Interfaces.
  4. Реализовать сервис в UniVerse.Infrastructure/Services.
  5. Зарегистрировать сервис в DI в Program.cs.
  6. Добавить endpoint в controller.
  7. Если меняется БД, создать EF migration через dotnet ef, не редактировать миграции вручную.
  8. Добавить тесты авторизации/бизнес-логики.
  9. Проверить Swagger JSON и, если нужно, frontend-контракт.

Команда миграции:

cd backend
dotnet ef migrations add NameOfMigration --project UniVerse.Infrastructure --startup-project UniVerse.Api
dotnet ef database update --project UniVerse.Infrastructure --startup-project UniVerse.Api

Места, на которые стоит обратить внимание

  • GET /api/v1/users/{id}/enrollments сейчас проверяет доступ, но возвращает пустой 200 OK; полноценная выдача записей пользователя еще не реализована.
  • CreatedAtAction в нескольких POST endpoint-ах передает { id = 0 }, хотя созданная сущность уже известна. Если клиенту важен Location header, это стоит поправить.
  • SyncStatusDto хранится в памяти процесса, не в БД.
  • ReviewLlmStatus.Rejected есть в enum, но текущий LlmAnalysisService не переводит отзывы в rejected.
  • В CourseTag, LectureEnrollment, UserAchievement entity есть поле Id, но AppDbContext.OnModelCreating дополнительно задает составные ключи для этих сущностей после применения конфигураций. При изменении модели обязательно сверяй snapshot/миграцию.
  • Secure = true у refresh cookie означает, что для cookie-flow в браузере нужен HTTPS. В локальной разработке через plain HTTP это может влиять на поведение cookie.

Ментальная модель

Если совсем коротко:

flowchart LR
    Auth[Auth: Microsoft/dev login] --> User[User + roles]
    User --> Catalog[Catalog: courses, tags, locations]
    Catalog --> Lecture[Lectures]
    User --> Enrollment[Enrollments]
    Lecture --> Enrollment
    Enrollment --> Attendance[Attendance]
    Lecture --> Review[Reviews]
    Review --> Llm[LLM analysis]
    Llm --> Rewards[Coins + XP]
    Attendance --> Achievements[Achievements]
    Enrollment --> Achievements
    Review --> Achievements
    Rewards --> Achievements
    Achievements --> Notifications[Notifications]
    Modeus[Modeus sync] --> Catalog
    Modeus --> Lecture

Почти все пользовательские сценарии проходят через пользователя с ролью, меняют лекции/записи/отзывы, а потом запускают геймификацию. Внешние интеграции - LLM, Modeus и SMTP - изолированы в Infrastructure и подключены через интерфейсы Application.