# Техническое задание: Платформа для онлайн-курсов ## 1. Описание проекта Веб-платформа для публикации и прохождения онлайн-курсов с отслеживанием прогресса студентов и системой тестирования. Проект закрепляет навыки CRUD, вложенных сущностей (курс → уроки → тесты), разграничения прав и совместной работы через Git. **Команда:** 2–3 человека --- ## 2. Функциональные требования ### 2.1. Аутентификация и регистрация - Регистрация, вход, выход (Laravel Breeze) - Восстановление пароля (опционально) ### 2.2. Каталог курсов - Просмотр списка доступных курсов с пагинацией - Фильтрация по категориям - Детальная страница курса (описание, автор, список уроков, рейтинг) - Запись на курс (кнопка «Записаться») ### 2.3. Управление курсами (преподаватель/админ) - CRUD курсов: название, описание, обложка, категория, уровень сложности - Вложенный CRUD уроков внутри курса: - Название урока, порядковый номер, тип (текст/видео) - Текстовое содержание (WYSIWYG или markdown) - URL видео (опционально, YouTube/Vimeo embed) - CRUD тестов/викторин к урокам: - Вопросы с вариантами ответов - Один или несколько правильных ответов - Проходной балл (в процентах) ### 2.4. Прохождение курса (студент) - Список записанных курсов (личный кабинет) - Последовательное прохождение уроков - Отметка урока как пройденного - Сдача теста после урока (или в конце курса) - Просмотр результатов теста (правильные/неправильные ответы) - Прогресс-бар прохождения курса (в процентах) ### 2.5. Панель преподавателя - Мои курсы (созданные мной) - Список студентов по каждому курсу - Прогресс каждого студента - Статистика: количество курсов, студентов, средний балл ### 2.6. Панель администратора - Управление пользователями и ролями - Модерация курсов (скрыть/показать) - Общая статистика платформы --- ## 3. Структура базы данных ### Таблица `users` | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | name | VARCHAR(255) | Имя | | email | VARCHAR(255) | Email (unique) | | password | VARCHAR(255) | Хеш пароля | | role | ENUM('admin', 'instructor', 'student') | Роль | | avatar | VARCHAR(255) \| NULL | Аватар | | created_at | TIMESTAMP | | | updated_at | TIMESTAMP | | ### Таблица `categories` | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | name | VARCHAR(255) | Название категории | | slug | VARCHAR(255) | URL-идентификатор (unique) | | created_at | TIMESTAMP | | | updated_at | TIMESTAMP | | ### Таблица `courses` | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | title | VARCHAR(255) | Название курса | | slug | VARCHAR(255) | URL-идентификатор (unique) | | description | TEXT | Описание | | cover_image | VARCHAR(255) \| NULL | Обложка курса | | difficulty_level | ENUM('beginner', 'intermediate', 'advanced') | Уровень | | instructor_id | BIGINT UNSIGNED | Преподаватель (FK → users) | | category_id | BIGINT UNSIGNED | Категория (FK → categories) | | is_published | BOOLEAN | Опубликован ли | | created_at | TIMESTAMP | | | updated_at | TIMESTAMP | | ### Таблица `lessons` | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | course_id | BIGINT UNSIGNED | Курс (FK → courses) | | title | VARCHAR(255) | Название урока | | position | INT | Порядковый номер | | content_type | ENUM('text', 'video') | Тип контента | | content | TEXT | Содержимое (текст или URL видео) | | created_at | TIMESTAMP | | | updated_at | TIMESTAMP | | ### Таблица `quizzes` | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | lesson_id | BIGINT UNSIGNED | Урок (FK → lessons) | | title | VARCHAR(255) | Название теста | | passing_score | INT | Проходной балл (%) | | created_at | TIMESTAMP | | | updated_at | TIMESTAMP | | ### Таблица `quiz_questions` | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | quiz_id | BIGINT UNSIGNED | Тест (FK → quizzes) | | question | TEXT | Вопрос | | position | INT | Порядковый номер | | created_at | TIMESTAMP | | | updated_at | TIMESTAMP | | ### Таблица `quiz_answers` | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | question_id | BIGINT UNSIGNED | Вопрос (FK → quiz_questions) | | answer_text | VARCHAR(255) | Текст ответа | | is_correct | BOOLEAN | Правильный ли | | created_at | TIMESTAMP | | | updated_at | TIMESTAMP | | ### Таблица `course_user` (запись на курс) | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | course_id | BIGINT UNSIGNED | FK → courses | | user_id | BIGINT UNSIGNED | FK → users | | progress | INT | Прогресс в процентах (0–100) | | enrolled_at | TIMESTAMP | Дата записи | | completed_at | TIMESTAMP \| NULL | Дата завершения | ### Таблица `lesson_progress` | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | user_id | BIGINT UNSIGNED | FK → users | | lesson_id | BIGINT UNSIGNED | FK → lessons | | is_completed | BOOLEAN | Пройден ли | | completed_at | TIMESTAMP \| NULL | | ### Таблица `quiz_results` | Поле | Тип | Описание | |---|---|---| | id | BIGINT UNSIGNED | Первичный ключ | | user_id | BIGINT UNSIGNED | FK → users | | quiz_id | BIGINT UNSIGNED | FK → quizzes | | score | INT | Набранный балл (%) | | passed | BOOLEAN | Сдал ли | | taken_at | TIMESTAMP | Дата прохождения | --- ## 4. Маршруты и контроллеры ```php // Публичные маршруты Route::get('/', [CourseController::class, 'index'])->name('courses.index'); Route::get('/courses/{course:slug}', [CourseController::class, 'show'])->name('courses.show'); // Аутентифицированные маршруты Route::middleware('auth')->group(function () { // Запись на курс Route::post('/courses/{course}/enroll', [CourseController::class, 'enroll'])->name('courses.enroll'); // Прохождение курса Route::prefix('my-courses')->name('my-courses.')->group(function () { Route::get('/', [StudentCourseController::class, 'index'])->name('index'); Route::get('/{course}', [StudentCourseController::class, 'show'])->name('show'); Route::get('/{course}/lessons/{lesson}', [LessonController::class, 'show'])->name('lessons.show'); Route::post('/{course}/lessons/{lesson}/complete', [LessonController::class, 'complete'])->name('lessons.complete'); // Тесты Route::get('/{course}/quizzes/{quiz}', [QuizController::class, 'take'])->name('quizzes.take'); Route::post('/{course}/quizzes/{quiz}/submit', [QuizController::class, 'submit'])->name('quizzes.submit'); }); // Преподаватель Route::prefix('instructor')->name('instructor.')->middleware('can:be-instructor')->group(function () { Route::resource('courses', InstructorCourseController::class); Route::resource('courses.lessons', InstructorLessonController::class)->scoped(['courses' => 'course']); Route::resource('lessons.quizzes', InstructorQuizController::class)->scoped(['lessons' => 'lesson']); Route::resource('quizzes.questions', InstructorQuestionController::class)->scoped(['quizzes' => 'quiz']); Route::get('courses/{course}/students', [InstructorCourseController::class, 'students'])->name('courses.students'); Route::get('dashboard', [InstructorDashboardController::class, 'index'])->name('dashboard'); }); // Админка Route::prefix('admin')->name('admin.')->middleware('can:access-admin-panel')->group(function () { Route::resource('users', AdminUserController::class); Route::patch('courses/{course}/toggle-publish', [AdminCourseController::class, 'togglePublish'])->name('courses.publish'); Route::get('dashboard', [AdminDashboardController::class, 'index'])->name('dashboard'); }); }); ``` ### Контроллеры - `CourseController` — публичный каталог и запись - `StudentCourseController` — прохождение курсов студентом - `LessonController` — просмотр уроков и отметка пройденными - `QuizController` — прохождение тестов - `InstructorCourseController` — CRUD курсов преподавателем - `InstructorLessonController` — CRUD уроков - `InstructorQuizController` — CRUD тестов - `InstructorQuestionController` — CRUD вопросов - `InstructorDashboardController` — панель преподавателя - `AdminUserController`, `AdminCourseController`, `AdminDashboardController` — админка --- ## 5. Роли, Gates и Policies ### Роли | Роль | Описание | |---|---| | `admin` | Полный доступ, модерация курсов, управление пользователями | | `instructor` | Создание/редактирование своих курсов, просмотр студентов | | `student` | Запись на курсы, прохождение уроков и тестов | ### Gates ```php Gate::define('be-instructor', function (User $user) { return in_array($user->role, ['admin', 'instructor']); }); Gate::define('access-admin-panel', function (User $user) { return $user->role === 'admin'; }); ``` ### Policies **CoursePolicy:** - `view` — все (если опубликован), автор или admin (если не опубликован) - `create` — instructor или admin - `update`, `delete` — автор курса или admin - `enroll` — student (не записан на этот курс) **LessonPolicy:** - `view` — студент, записанный на курс, или преподаватель/admin - `create`, `update`, `delete` — преподаватель курса или admin **QuizPolicy:** - `view` — студент, записанный на курс, или преподаватель/admin - `take` — студент, записанный на курс - `create`, `update`, `delete` — преподаватель курса или admin **QuizResultPolicy:** - `view` — владелец результата или преподаватель/admin - `create` — все студенты --- ## 6. Требования к интерфейсу (Bootstrap 5) ### Компоненты - **Навбар** — логотип, каталог, «Мои курсы», профиль - **Карточки курсов** — обложка, название, автор, уровень (бейдж), прогресс-бар - **Страница курса** — описание, список уроков (accordion), кнопка записи - **Страница урока** — контент (текст или embed видео), кнопка «Отметить пройденным» - **Прогресс-бар** — Bootstrap `progress` для отображения прохождения курса - **Accordion** — список уроков курса - **Тест** — форма с radio/checkbox, кнопка отправки, результат - **Дашборд преподавателя** — статистика (карточки), таблица студентов - **Адаптивная сетка** — `col-md-4` для карточек, `col-md-8` + `col-md-4` для основного контента и сайдбара ### Цветовая схема уровней сложности - Начальный — `bg-success` - Средний — `bg-warning` - Продвинутый — `bg-danger` --- ## 7. Git-workflow для команды ### Распределение модулей (для 2 человек) | Участник | Модуль | Ветки | |---|---|---| | **Участник A** | Курсы, категории, обложки, дашборд преподавателя | `feature/courses-crud`, `feature/categories`, `feature-course-covers` | | **Участник B** | Уроки, тесты, вопросы, прохождение тестов | `feature/lessons-crud`, `feature/quizzes`, `feature-quiz-questions` | ### Распределение модулей (для 3 человек) | Участник | Модуль | Ветки | |---|---|---| | **Участник A** | Курсы, категории, загрузка обложек | `feature/courses-crud`, `feature/categories`, `feature-course-covers` | | **Участник B** | Уроки, тесты, вопросы | `feature/lessons-crud`, `feature/quizzes`, `feature-quiz-questions` | | **Участник C** | Запись на курс, прогресс, прохождение тестов, UI | `feature/enrollment`, `feature-progress-tracking`, `feature-student-ui` | ### Правила 1. Ветка `develop` — основная ветка разработки 2. Каждый участник создаёт фич-ветки от `develop` 3. Минимум 3 PR на участника 4. Обязательный код-ревью перед мёржем 5. Conventional Commits ### Визуализация workflow (2 участника) ```mermaid gitGraph commit id: "init" tag: "v1.0" branch develop checkout develop branch feature/courses-crud checkout develop branch feature/lessons-crud checkout feature/courses-crud commit id: "feat: add courses CRUD with covers" commit id: "feat: add categories CRUD" checkout feature/lessons-crud commit id: "feat: add lessons CRUD" commit id: "feat: add quizzes system" checkout develop merge feature/courses-crud tag: "PR + review" merge feature/lessons-crud tag: "PR + review" checkout main merge develop tag: "release v1.1" ``` ### Визуализация workflow (3 участника) ```mermaid gitGraph commit id: "init" tag: "v1.0" branch develop checkout develop branch feature/courses-crud checkout develop branch feature/lessons-crud checkout develop branch feature/enrollment checkout feature/courses-crud commit id: "feat: add courses CRUD with covers" commit id: "feat: add instructor dashboard" checkout feature/lessons-crud commit id: "feat: add lessons CRUD" commit id: "feat: add quizzes system" checkout feature/enrollment commit id: "feat: add enrollment flow" commit id: "feat: add progress tracking" checkout develop merge feature/courses-crud tag: "PR + review" merge feature/lessons-crud tag: "PR + review" merge feature/enrollment tag: "PR + review" checkout main merge develop tag: "release v1.1" ``` --- ## 8. Критерии приёмки ### Обязательно - [ ] CRUD курсов с загрузкой обложки - [ ] CRUD уроков внутри курса (вложенный ресурс) - [ ] CRUD тестов и вопросов (минимум 3 ответа на вопрос, 1+ правильный) - [ ] Запись на курс (студент) - [ ] Отметка уроков пройденными - [ ] Прохождение теста с подсчётом результата - [ ] Прогресс-бар прохождения курса - [ ] Панель преподавателя (мои курсы, студенты) - [ ] Policies: студент не может редактировать курсы, преподаватель не может редактировать чужие курсы - [ ] Адаптивная Bootstrap-вёрстка - [ ] Flash-сообщения - [ ] Пагинация каталога курсов - [ ] Git-история: минимум 3 ветки на участника, PR с ревью ### Дополнительно (бонусные баллы) - [ ] Рейтинг курса (средняя оценка студентов) - [ ] Сертификат при завершении курса (генерация PDF) - [ ] Поиск курсов по названию/описанию - [ ] Фильтрация по уровню сложности - [ ] Тесты: перемешивание вопросов и ответов - [ ] Soft Deletes для курсов и уроков --- ## 9. Дополнительные задания (для продвинутых) 1. **Рейтинг курсов:** студенты оценивают курс после завершения (1–5 звёзд) 2. **Сертификаты:** генерация PDF-сертификата при 100% прохождении 3. **Обсуждения:** комментарии к урокам (вложенные, как на YouTube) 4. **Тесты:** таймер на прохождение теста, автоматическая отправка по истечении 5. **API:** RESTful API для каталога курсов и прохождения 6. **Тесты:** PHPUnit Feature-тесты для QuizController 7. **Events & Listeners:** событие «урок пройден» → обновление прогресса --- ## 10. Рекомендуемые материалы - Laravel Docs: https://laravel.com/docs/12.x - Nested Resources: https://laravel.com/docs/12.x/controllers#restful-nested-resources - Bootstrap 5 Progress: https://getbootstrap.com/docs/5.3/components/progress/ - Bootstrap 5 Accordion: https://getbootstrap.com/docs/5.3/components/accordion/ - Laravel Policies: https://laravel.com/docs/12.x/authorization#creating-policies