Платформа для онлайн-курсов. Преподаватели создают курсы с уроками, студенты записываются на них и просматривают материалы. Загружаемые изображения проходят автоматическую обработку через сжатие и наложение водяного знака.
Проект построен на Node.js. Основной сервер написан на NestJS 11, сервис обработки изображений на Express.js. Для хранения данных используется MongoDB с Mongoose в качестве ODM. Кэширование списков и деталей курсов выполняется через Redis. Асинхронное взаимодействие между сервисами организовано через Kafka с двумя топиками: отправка задачи на обработку и подтверждение о завершении. Обработка изображений, а именно сжатие и водяной знак, реализована с помощью библиотеки Sharp. Для аутентификации используются JWT-токены, пароль хешируется через bcrypt.
Репозиторий организован как монорепозиторий на базе pnpm workspaces и включает два сервиса.
Main API на NestJS, работающий на порту 3000, отвечает за всю бизнес-логику: регистрацию и авторизацию, CRUD курсов и уроков, запись студентов на курсы, загрузку изображений, кэширование и отправку сообщений в Kafka.
Image Worker на Express.js, работающий на порту 4000, представляет собой отдельный микросервис. Он слушает топик Kafka, получает задачи на обработку изображений, выполняет сжатие и наложение водяного знака через Sharp, сохраняет результат в отдельную папку и обновляет статус в базе данных.
Оба сервиса находятся в директории packages. Корневой package.json через concurrently запускает их одновременно.
Для запуска потребуется установленный Docker и pnpm.
Склонируйте репозиторий и перейдите в корневую директорию проекта. Установите зависимости:
pnpm install
Запустите инфраструктуру:
docker compose -p eduplatform up -d
После того как контейнеры поднимутся, запустите оба сервиса:
pnpm run dev
Main API будет доступен на http://localhost:3000, Image Worker на http://localhost:4000.
Для тестирования API можно использовать любой HTTP-клиент. Все эндпоинты имеют префикс /api.
Зарегистрируйте преподавателя и студента через POST /api/auth/register, указав роль teacher или student. После регистрации вы получите JWT-токен, который нужно передавать в заголовке Authorization: Bearer для защищённых эндпоинтов.
Преподаватель может создавать курсы через POST /api/courses, добавлять в них уроки через POST /api/courses/:id/lessons и загружать обложку курса через POST /api/courses/:id/cover или изображения к уроку через POST /api/lessons/:id/images. Студент может просматривать список курсов через GET /api/courses и записываться на них через POST /api/courses/:id/enroll, после чего получает доступ к материалам уроков через GET /api/lessons/:id.
Обложки курсов доступны без аутентификации через GET /api/images/:filename. Изображения уроков требуют авторизации и записи на соответствующий курс.
При разработке я принял несколько решений, которые не оговаривались в техническом задании, но показались мне оправданными.
Я добавил ValidationPipe и DTO через библиотеку class-validator. ТЗ не требовало валидации входящих данных, но без неё сервер принимает любые запросы, вплоть до пустых названий курсов и мусорных полей. Теперь все эндпоинты проверяют типы полей, обязательность и длину, а лишние свойства в теле запроса вызывают ошибку.
Изображения уроков я сделал недоступными без аутентификации и записи на курс. Формально в ТЗ сказано "получить обработанное изображение по имени файла" без уточнения правил доступа, но я посчитал, что иллюстрации к уроку - часть самого занятия. Если урок нельзя посмотреть без подписки, то и картинки к нему нельзя.
JWT-токен я храню только на стороне клиента. Сервер не запоминает сессии, не пишет токены в базу и не проверяет их статус при каждом запросе.
При удалении курса я удаляю все его уроки и все файлы изображений как обложки, так и иллюстраций к урокам. При удалении отдельного урока с диска удаляются только его собственные изображения.