Repository: mshossain110/examinee Branch: main Commit: 0852f4cbec3e Files: 328 Total size: 862.0 KB Directory structure: gitextract__3ge5ty2/ ├── .dockerignore ├── .editorconfig ├── .gitattributes ├── .github/ │ └── workflows/ │ └── laravel.yml ├── .gitignore ├── .styleci.yml ├── CHANGELOG.md ├── DOCKER.md ├── Dockerfile ├── Makefile ├── README.md ├── app/ │ ├── Actions/ │ │ ├── CourseWithSections.php │ │ ├── GetEnrolledCousesAction.php │ │ └── GetInstratorCousesAction.php │ ├── Enums/ │ │ ├── Attributes/ │ │ │ └── Description.php │ │ ├── PermissionsEnum.php │ │ ├── StatusesEnum.php │ │ └── Traits/ │ │ ├── AsSelectArray.php │ │ ├── EnumToArray.php │ │ └── GetsAttributes.php │ ├── Events/ │ │ └── ExamCreated.php │ ├── Exceptions/ │ │ ├── Handler.php │ │ └── SocialProviderDeniedException.php │ ├── Helpers/ │ │ └── Functions/ │ │ └── core.php │ ├── Http/ │ │ ├── Controllers/ │ │ │ ├── API/ │ │ │ │ ├── FileController.php │ │ │ │ ├── MyCourseController.php │ │ │ │ ├── SectionableController.php │ │ │ │ └── TakeExamController.php │ │ │ ├── Admin/ │ │ │ │ ├── CourseStudentController.php │ │ │ │ ├── CoursesController.php │ │ │ │ ├── DashboardController.php │ │ │ │ ├── ExamController.php │ │ │ │ ├── LessonController.php │ │ │ │ ├── QuestionController.php │ │ │ │ ├── RolesController.php │ │ │ │ ├── SectionController.php │ │ │ │ ├── ServerInfoController.php │ │ │ │ ├── SubjectController.php │ │ │ │ ├── TopicsController.php │ │ │ │ └── UsersController.php │ │ │ ├── Auth/ │ │ │ │ ├── AuthenticatedSessionController.php │ │ │ │ ├── ConfirmablePasswordController.php │ │ │ │ ├── EmailVerificationNotificationController.php │ │ │ │ ├── EmailVerificationPromptController.php │ │ │ │ ├── NewPasswordController.php │ │ │ │ ├── PasswordController.php │ │ │ │ ├── PasswordResetLinkController.php │ │ │ │ ├── RegisteredUserController.php │ │ │ │ ├── SocialiteController.php │ │ │ │ └── VerifyEmailController.php │ │ │ ├── Controller.php │ │ │ ├── CourseController.php │ │ │ ├── DownloadController.php │ │ │ ├── HomeController.php │ │ │ ├── InstructorController.php │ │ │ ├── LearningController.php │ │ │ ├── ProfileController.php │ │ │ └── UploadController.php │ │ ├── Middleware/ │ │ │ └── HandleInertiaRequests.php │ │ ├── Requests/ │ │ │ ├── Admin/ │ │ │ │ ├── StoreCoursesRequest.php │ │ │ │ ├── UpdateCoursesRequest.php │ │ │ │ └── UserRequest.php │ │ │ ├── Auth/ │ │ │ │ └── LoginRequest.php │ │ │ ├── ExamRequest.php │ │ │ ├── ProfileUpdateRequest.php │ │ │ ├── QuestionRequest.php │ │ │ ├── SettingRequest.php │ │ │ ├── SubjectRequest.php │ │ │ └── TopicRequest.php │ │ └── Resources/ │ │ ├── CourseResource.php │ │ ├── CourseStudentsResource.php │ │ ├── ExamResource.php │ │ ├── FileResource.php │ │ ├── LessonResource.php │ │ ├── QuestionResource.php │ │ ├── ResultResource.php │ │ ├── RoleResource.php │ │ ├── SectionResource.php │ │ ├── SubjectResource.php │ │ ├── TopicResource.php │ │ └── UserResource.php │ ├── Jobs/ │ │ ├── ResizedImage.php │ │ └── UploadToCloud.php │ ├── Models/ │ │ ├── Course.php │ │ ├── Exam.php │ │ ├── File.php │ │ ├── Lesson.php │ │ ├── Question.php │ │ ├── Result.php │ │ ├── Role.php │ │ ├── Section.php │ │ ├── Sectionable.php │ │ ├── Setting.php │ │ ├── SocialiteProvider.php │ │ ├── Subject.php │ │ ├── Topic.php │ │ ├── Traits/ │ │ │ ├── FileStorage.php │ │ │ ├── Fileable.php │ │ │ ├── HashesId.php │ │ │ └── Topicable.php │ │ └── User.php │ ├── Observers/ │ │ └── FileObserver.php │ ├── Pivots/ │ │ └── StudentCasting.php │ ├── Policies/ │ │ ├── CoursePolicy.php │ │ ├── ExamPolicy.php │ │ ├── RolePolicy.php │ │ ├── SubjectPolicy.php │ │ ├── TopicPolicy.php │ │ └── UserPolicy.php │ ├── Providers/ │ │ └── AppServiceProvider.php │ ├── Response/ │ │ ├── AudioVideoResponse.php │ │ ├── DownloadResponse.php │ │ ├── FileBuilder.php │ │ ├── FileContentResponseCreator.php │ │ └── ImageResponse.php │ └── Traits/ │ ├── AppSettingsTrait.php │ └── SocialiteProvidersTrait.php ├── artisan ├── bootstrap/ │ ├── app.php │ ├── cache/ │ │ └── .gitignore │ └── providers.php ├── composer.json ├── config/ │ ├── app.php │ ├── auth.php │ ├── cache.php │ ├── database.php │ ├── filesystems.php │ ├── logging.php │ ├── mail.php │ ├── permission.php │ ├── queue.php │ ├── services.php │ └── session.php ├── database/ │ ├── .gitignore │ ├── factories/ │ │ ├── CourseFactory.php │ │ ├── ExamFactory.php │ │ ├── LessonFactory.php │ │ ├── QuestionFactory.php │ │ ├── SectionFactory.php │ │ ├── SubjectFactory.php │ │ ├── TopicFactory.php │ │ └── UserFactory.php │ ├── migrations/ │ │ ├── 0001_01_01_000000_create_users_table.php │ │ ├── 0001_01_01_000001_create_cache_table.php │ │ ├── 0001_01_01_000002_create_jobs_table.php │ │ ├── 2024_05_07_000001_create_personal_access_tokens_table.php │ │ ├── 2024_05_07_054845_create_files_table.php │ │ ├── 2024_05_07_054954_create_fileables_table.php │ │ ├── 2024_05_07_092254_create_settings_table.php │ │ ├── 2024_05_07_112940_create_permission_tables.php │ │ ├── 2024_05_07_142204_create_sections_table.php │ │ ├── 2024_05_07_145026_create_subjects_table.php │ │ ├── 2024_05_07_152004_create_sectionables_table.php │ │ ├── 2024_05_07_153027_create_exams_table.php │ │ ├── 2024_05_07_153507_create_questions_table.php │ │ ├── 2024_05_07_170835_create_topics_table.php │ │ ├── 2024_05_07_180360_create_topicables_table.php │ │ ├── 2024_05_07_190045_create_results_table.php │ │ ├── 2024_05_07_191956_create_courses_table.php │ │ ├── 2024_05_07_193251_create_lessons_table.php │ │ ├── 2024_05_07_195152_create_subjectables_table.php │ │ ├── 2024_05_07_200921_create_course_students_table.php │ │ ├── 2024_05_07_201001_create_course_teachers_table.php │ │ ├── 2024_05_07_203101_create_lesson_student_table.php │ │ ├── 2024_07_23_104822_store_last_learning.php │ │ ├── 2024_08_13_073632_create_socialite_providers_table.php │ │ └── 2026_04_25_000001_add_icon_and_image_to_subjects_table.php │ └── seeders/ │ ├── CourseSeed.php │ ├── DatabaseSeeder.php │ ├── RoleSeed.php │ ├── SubjectSeed.php │ ├── TopicSeed.php │ └── UserSeed.php ├── docker/ │ ├── nginx/ │ │ └── default.conf │ └── php/ │ └── local.ini ├── docker-compose.yml ├── package.json ├── phpstan.neon ├── phpunit.xml ├── postcss.config.js ├── public/ │ ├── .htaccess │ ├── index.php │ └── robots.txt ├── resources/ │ ├── css/ │ │ └── app.css │ ├── js/ │ │ ├── Components/ │ │ │ ├── ApplicationLogo.vue │ │ │ ├── Breadcrumb.vue │ │ │ ├── Button.vue │ │ │ ├── Card.vue │ │ │ ├── Checkbox.vue │ │ │ ├── CircleSvg.vue │ │ │ ├── Datatable/ │ │ │ │ └── Table.vue │ │ │ ├── Dropdown.vue │ │ │ ├── DropdownLink.vue │ │ │ ├── Form/ │ │ │ │ ├── Checkbox.vue │ │ │ │ ├── HeroiconPicker.vue │ │ │ │ ├── Input.vue │ │ │ │ ├── Label.vue │ │ │ │ ├── Listbox.examplevue │ │ │ │ └── Select.vue │ │ │ ├── Icon.vue │ │ │ ├── Icons/ │ │ │ │ ├── LoadingIcon.vue │ │ │ │ └── index.ts │ │ │ ├── Modal.vue │ │ │ ├── NavLink.vue │ │ │ ├── Pagination.vue │ │ │ ├── ResponsiveNavLink.vue │ │ │ ├── SocialiteLogins.vue │ │ │ └── Tabs.vue │ │ ├── Composables/ │ │ │ ├── analytics.js │ │ │ ├── common.js │ │ │ ├── useAuth.ts │ │ │ ├── useButtonGroup.ts │ │ │ ├── useFormGroup.ts │ │ │ ├── useSidebarState.ts │ │ │ ├── useUI.ts │ │ │ └── utils.ts │ │ ├── Elements/ │ │ │ ├── Course.vue │ │ │ ├── Description.vue │ │ │ ├── Lesson/ │ │ │ │ ├── Question.vue │ │ │ │ ├── Session.vue │ │ │ │ ├── SingleExam.vue │ │ │ │ ├── SingleLesson.vue │ │ │ │ └── SingleResource.vue │ │ │ ├── admin/ │ │ │ │ ├── AdminFooter.vue │ │ │ │ ├── AdminNavBar.vue │ │ │ │ ├── AdminSidebar.vue │ │ │ │ └── GHButtons.vue │ │ │ ├── course/ │ │ │ │ ├── CourseForm.vue │ │ │ │ ├── CourseLayout.vue │ │ │ │ ├── Exam.vue │ │ │ │ ├── Lesson.vue │ │ │ │ ├── LessonForm.vue │ │ │ │ ├── SectionForm.vue │ │ │ │ └── SingleSection.vue │ │ │ ├── exam/ │ │ │ │ ├── CreateExam.vue │ │ │ │ ├── CreateQuestion.vue │ │ │ │ ├── ExamLayout.vue │ │ │ │ └── QuestionTable.vue │ │ │ └── roles/ │ │ │ └── RolesBadges.vue │ │ ├── Layouts/ │ │ │ ├── AdminLayout.vue │ │ │ ├── AuthenticatedLayout.vue │ │ │ ├── GuestLayout.vue │ │ │ └── MainLayout.vue │ │ ├── Pages/ │ │ │ ├── Auth/ │ │ │ │ ├── ConfirmPassword.vue │ │ │ │ ├── ForgotPassword.vue │ │ │ │ ├── Login.vue │ │ │ │ ├── Register.vue │ │ │ │ ├── ResetPassword.vue │ │ │ │ └── VerifyEmail.vue │ │ │ ├── Course/ │ │ │ │ ├── Course.vue │ │ │ │ └── Partials/ │ │ │ │ ├── Instractor.vue │ │ │ │ └── Pricing.vue │ │ │ ├── Home/ │ │ │ │ ├── Home.vue │ │ │ │ └── Slider.vue │ │ │ ├── Instructor/ │ │ │ │ └── Courses.vue │ │ │ ├── Learning/ │ │ │ │ ├── Courses.vue │ │ │ │ └── SingleResource.vue │ │ │ ├── Profile/ │ │ │ │ ├── Edit.vue │ │ │ │ └── Partials/ │ │ │ │ ├── DeleteUserForm.vue │ │ │ │ ├── UpdatePasswordForm.vue │ │ │ │ └── UpdateProfileInformationForm.vue │ │ │ └── admin/ │ │ │ ├── Dashboard.vue │ │ │ ├── ServerInfo.vue │ │ │ ├── courses/ │ │ │ │ ├── CourseStudent.vue │ │ │ │ ├── CreateCourse.vue │ │ │ │ ├── Index.vue │ │ │ │ └── Sections.vue │ │ │ ├── exams/ │ │ │ │ ├── Create.vue │ │ │ │ ├── CreateQuestion.vue │ │ │ │ ├── Edit.vue │ │ │ │ ├── Index.vue │ │ │ │ └── Questions.vue │ │ │ ├── roles/ │ │ │ │ ├── Create.vue │ │ │ │ ├── Edit.vue │ │ │ │ └── Index.vue │ │ │ ├── subjects/ │ │ │ │ ├── Create.vue │ │ │ │ ├── Edit.vue │ │ │ │ └── Index.vue │ │ │ ├── topics/ │ │ │ │ ├── Create.vue │ │ │ │ ├── Edit.vue │ │ │ │ └── Index.vue │ │ │ └── users/ │ │ │ ├── Create.vue │ │ │ ├── Edit.vue │ │ │ └── Index.vue │ │ ├── app.ts │ │ ├── bootstrap.ts │ │ ├── types/ │ │ │ ├── forms.d.ts │ │ │ ├── global.d.ts │ │ │ ├── icons.d.ts │ │ │ ├── index.d.ts │ │ │ ├── resources.d.ts │ │ │ ├── vite-env.d.ts │ │ │ └── vuex.d.ts │ │ └── ui.config/ │ │ ├── buttonGroup.ts │ │ └── index.ts │ └── views/ │ ├── app.blade.php │ ├── errors/ │ │ ├── 401.blade.php │ │ ├── 403.blade.php │ │ ├── 500.blade.php │ │ ├── 503.blade.php │ │ └── layout.blade.php │ └── socialite/ │ ├── callback.blade.php │ └── denied.blade.php ├── routes/ │ ├── api.php │ ├── auth.php │ ├── console.php │ └── web.php ├── storage/ │ ├── app/ │ │ └── .gitignore │ ├── framework/ │ │ ├── .gitignore │ │ ├── cache/ │ │ │ └── .gitignore │ │ ├── sessions/ │ │ │ └── .gitignore │ │ ├── testing/ │ │ │ └── .gitignore │ │ └── views/ │ │ └── .gitignore │ └── logs/ │ └── .gitignore ├── tailwind.config.js ├── tests/ │ ├── Feature/ │ │ ├── Auth/ │ │ │ ├── AuthenticationTest.php │ │ │ ├── EmailVerificationTest.php │ │ │ ├── PasswordConfirmationTest.php │ │ │ ├── PasswordResetTest.php │ │ │ ├── PasswordUpdateTest.php │ │ │ └── RegistrationTest.php │ │ ├── ExampleTest.php │ │ ├── Http/ │ │ │ └── Controllers/ │ │ │ └── Admin/ │ │ │ └── UsersControllerTest.php │ │ └── ProfileTest.php │ ├── TestCase.php │ └── Unit/ │ └── ExampleTest.php ├── tsconfig.json └── vite.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ # Git .git .gitignore .gitattributes # Environment .env .env.* !.env.example # IDE .idea .vscode *.sublime* # OS .DS_Store Thumbs.db # Build artifacts node_modules npm-debug.log yarn-error.log # Laravel /vendor /storage/*.key /public/hot /public/storage /public/build # Testing /coverage /.phpunit.result.cache # Documentation README.md CHANGELOG.md /doc # Docker docker-compose*.yml Dockerfile .dockerignore ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 end_of_line = lf indent_size = 4 indent_style = space insert_final_newline = true trim_trailing_whitespace = true [*.md] trim_trailing_whitespace = false [*.{yml,yaml}] indent_size = 2 [docker-compose.yml] indent_size = 4 ================================================ FILE: .gitattributes ================================================ * text=auto eol=lf *.blade.php diff=html *.css diff=css *.html diff=html *.md diff=markdown *.php diff=php /.github export-ignore CHANGELOG.md export-ignore .styleci.yml export-ignore ================================================ FILE: .github/workflows/laravel.yml ================================================ name: Laravel on: push: branches: [ "main" ] pull_request: branches: [ "main" ] jobs: laravel-tests: runs-on: ubuntu-latest services: mysql: image: mysql:latest env: MYSQL_DATABASE: posts-test MYSQL_ROOT_PASSWORD: 123456 ports: - 3306:3306 options: --health-cmd="mysqladmin ping" steps: - uses: shivammathur/setup-php@15c43e89cdef867065b0213be354c2841860869e with: php-version: '8.2' - uses: actions/checkout@v4 - name: Copy .env run: php -r "file_exists('.env') || copy('.env.example', '.env');" - name: Install Dependencies run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist - name: Generate key run: php artisan key:generate - name: Directory Permissions run: chmod -R 777 storage bootstrap/cache - name: phpstan run: ./vendor/bin/phpstan analyse --memory-limit=1G - name: Create Database run: | mkdir -p database touch database/database.sqlite - name: Execute tests (Unit and Feature tests) via PHPUnit/Pest env: DB_CONNECTION: sqlite DB_DATABASE: database/database.sqlite run: php artisan test ================================================ FILE: .gitignore ================================================ /.phpunit.cache /node_modules /public/build /public/hot /public/storage /storage/*.key /vendor .env .env.backup .env.production .phpunit.result.cache Homestead.json Homestead.yaml auth.json npm-debug.log yarn-error.log /.fleet /.idea /.vscode /.composer /.npm /.config .DS_Store ================================================ FILE: .styleci.yml ================================================ php: preset: laravel disabled: - no_unused_imports finder: not-name: - index.php js: true css: true ================================================ FILE: CHANGELOG.md ================================================ # Release Notes ## [Unreleased](https://github.com/laravel/laravel/compare/v11.0.7...11.x) ## [v11.0.7](https://github.com/laravel/laravel/compare/v11.0.6...v11.0.7) - 2024-05-03 * Remove obsolete driver option by [@u01jmg3](https://github.com/u01jmg3) in https://github.com/laravel/laravel/pull/6395 ## [v11.0.6](https://github.com/laravel/laravel/compare/v11.0.5...v11.0.6) - 2024-04-09 * Fix PHPUnit constraint by [@szepeviktor](https://github.com/szepeviktor) in https://github.com/laravel/laravel/pull/6389 * [11.x] Add missing roundrobin transport driver config by [@u01jmg3](https://github.com/u01jmg3) in https://github.com/laravel/laravel/pull/6392 ## [v11.0.5](https://github.com/laravel/laravel/compare/v11.0.4...v11.0.5) - 2024-03-26 * [11.x] Use PHPUnit v11 by [@philbates35](https://github.com/philbates35) in https://github.com/laravel/laravel/pull/6385 ## [v11.0.4](https://github.com/laravel/laravel/compare/v11.0.3...v11.0.4) - 2024-03-15 * [11.x] Removed useless null parameter for env helper (cache.php) by [@siarheipashkevich](https://github.com/siarheipashkevich) in https://github.com/laravel/laravel/pull/6374 * [11.x] Removed useless null parameter for env helper (queue.php) by [@siarheipashkevich](https://github.com/siarheipashkevich) in https://github.com/laravel/laravel/pull/6373 * [11.x] Fix retry_after to be an integer by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/laravel/pull/6377 * [11.x] Fix on hover animation and ring by [@michaelnabil230](https://github.com/michaelnabil230) in https://github.com/laravel/laravel/pull/6376 ## [v11.0.3](https://github.com/laravel/laravel/compare/v11.0.2...v11.0.3) - 2024-03-14 * [11.x] Revert collation change by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/laravel/pull/6372 ## [v11.0.2](https://github.com/laravel/laravel/compare/v11.0.1...v11.0.2) - 2024-03-13 * [11.x] Remove branch alias from composer.json by [@zepfietje](https://github.com/zepfietje) in https://github.com/laravel/laravel/pull/6366 * [11.x] Fixes typo in welcome page by [@jrd-lewis](https://github.com/jrd-lewis) in https://github.com/laravel/laravel/pull/6363 * change mariadb default by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/laravel/commit/79969c99c6456a6d6edfbe78d241575fe1f65594 ## [v11.0.1](https://github.com/laravel/laravel/compare/v11.0.0...v11.0.1) - 2024-03-12 * [11.x] Fixes SQLite driver missing by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/laravel/pull/6361 ## [v11.0.0 (2023-02-17)](https://github.com/laravel/laravel/compare/v10.3.2...v11.0.0) Laravel 11 includes a variety of changes to the application skeleton. Please consult the diff to see what's new. ================================================ FILE: DOCKER.md ================================================ # Docker Setup for Examinee Application ## Quick Start ### 1. Build and start the containers ```bash docker compose up -d ``` ### 2. Install dependencies and build assets ```bash # Install PHP dependencies (if not already done) docker compose exec app composer install # Install Node dependencies and build assets docker compose run --rm node sh -c "npm install && npm run build" # Or use the build profile docker compose --profile build up node ``` ### 3. Setup the application ```bash # Copy environment file cp .env.example .env # Generate application key docker compose exec app php artisan key:generate # Run migrations docker compose exec app php artisan migrate # Run seeders (optional) docker compose exec app php artisan db:seed # Create storage link docker compose exec app php artisan storage:link # Clear and cache config docker compose exec app php artisan config:cache docker compose exec app php artisan route:cache docker compose exec app php artisan view:cache ``` ### 4. Access the application - **Application**: http://localhost:8000 - **Database**: localhost:3307 - Database: examinee_db - User: examinee_user - Password: examinee_password - Root Password: root_password - **Redis**: localhost:6380 ## Services - **app**: PHP 8.2-FPM application container - **nginx**: Nginx web server (port 8000) - **db**: MySQL 8.0 database (port 3307) - **redis**: Redis cache and queue backend (port 6380) - **queue**: Laravel queue worker - **node**: Node.js for asset compilation (run on-demand) ## Common Commands ### Container Management ```bash # Start containers docker compose up -d # Stop containers docker compose down # View logs docker compose logs -f # View specific service logs docker compose logs -f app docker compose logs -f nginx docker compose logs -f queue # Restart a service docker compose restart app ``` ### Laravel Commands ```bash # Run artisan commands docker compose exec app php artisan [command] # Access Laravel tinker docker compose exec app php artisan tinker # Clear caches docker compose exec app php artisan cache:clear docker compose exec app php artisan config:clear docker compose exec app php artisan route:clear docker compose exec app php artisan view:clear # Run migrations docker compose exec app php artisan migrate docker compose exec app php artisan migrate:fresh --seed ``` ### Asset Building ```bash # Development build docker compose run --rm node npm run dev # Production build docker compose run --rm node npm run build # Watch mode (for development) docker compose run --rm node npm run watch ``` ### Database Access ```bash # Access MySQL CLI docker compose exec db mysql -u examinee_user -pexaminee_password examinee_db # Import database docker compose exec -T db mysql -u examinee_user -pexaminee_password examinee_db < backup.sql # Export database docker compose exec db mysqldump -u examinee_user -pexaminee_password examinee_db > backup.sql ``` ### Queue Management ```bash # View queue logs docker compose logs -f queue # Restart queue worker docker compose restart queue # Run queue manually (for testing) docker compose exec app php artisan queue:work ``` ### Shell Access ```bash # Access app container shell docker compose exec app bash # Access as root docker compose exec -u root app bash ``` ## Environment Variables Update your `.env` file with these Docker settings: ```env DB_CONNECTION=mysql DB_HOST=db DB_PORT=3306 DB_DATABASE=examinee_db DB_USERNAME=examinee_user DB_PASSWORD=examinee_password REDIS_HOST=redis REDIS_PORT=6379 CACHE_DRIVER=redis SESSION_DRIVER=redis QUEUE_CONNECTION=redis ``` ## Troubleshooting ### Permission Issues ```bash # Fix storage permissions docker compose exec -u root app chown -R www-data:www-data /var/www/html/storage docker compose exec -u root app chmod -R 755 /var/www/html/storage ``` ### Reset Everything ```bash # Stop and remove containers, volumes docker compose down -v # Rebuild from scratch docker compose build --no-cache docker compose up -d ``` ### Database Connection Issues - Ensure the `db` service is running: `docker compose ps` - Check database logs: `docker compose logs db` - Verify credentials in `.env` match `docker-compose.yml` ## Production Considerations 1. **Change default passwords** in `docker-compose.yml` 2. **Use environment-specific files**: Create `docker-compose.prod.yml` 3. **Enable HTTPS**: Add SSL certificates and update nginx config 4. **Set proper APP_ENV**: Change to `production` in `.env` 5. **Disable debug mode**: Set `APP_DEBUG=false` 6. **Use proper storage**: Mount volumes for persistent data 7. **Implement backups**: Regular database and file backups 8. **Monitoring**: Add logging and monitoring solutions ## Development vs Production For development with hot reload: ```bash # Run Vite dev server on host machine npm install npm run dev # Access via http://localhost:5173 ``` Update `.env`: ```env VITE_DEV_SERVER_URL=http://localhost:5173 ``` ================================================ FILE: Dockerfile ================================================ FROM php:8.3-fpm # Set working directory WORKDIR /var/www/html # Install system dependencies RUN apt-get update && apt-get install -y \ git \ curl \ libpng-dev \ libonig-dev \ libxml2-dev \ libzip-dev \ libpq-dev \ zip \ unzip \ libfreetype6-dev \ libjpeg62-turbo-dev \ libwebp-dev \ && docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp \ && docker-php-ext-install pdo_mysql pdo_pgsql mbstring exif pcntl bcmath gd zip # Install Redis extension RUN pecl install redis && docker-php-ext-enable redis # Clear cache RUN apt-get clean && rm -rf /var/lib/apt/lists/* # Install Composer COPY --from=composer:latest /usr/bin/composer /usr/bin/composer # Copy application files COPY . /var/www/html # Copy php.ini configuration RUN cp "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \ && echo "upload_max_filesize = 50M" >> "$PHP_INI_DIR/php.ini" \ && echo "post_max_size = 50M" >> "$PHP_INI_DIR/php.ini" \ && echo "memory_limit = 512M" >> "$PHP_INI_DIR/php.ini" # Expose port 9000 for PHP-FPM EXPOSE 9000 CMD ["php-fpm"] ================================================ FILE: Makefile ================================================ .PHONY: help build up down restart logs shell composer artisan migrate fresh test clean # Colors for output BLUE=\033[0;34m GREEN=\033[0;32m RED=\033[0;31m NC=\033[0m # No Color help: ## Show this help message @echo '${BLUE}Available commands:${NC}' @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " ${GREEN}%-15s${NC} %s\n", $$1, $$2}' build: ## Build Docker containers @echo "${BLUE}Building Docker containers...${NC}" docker compose build up: ## Start all containers @echo "${BLUE}Starting containers...${NC}" docker compose up -d @echo "${GREEN}Containers started! Access the app at http://localhost:8000${NC}" down: ## Stop all containers @echo "${BLUE}Stopping containers...${NC}" docker compose down restart: ## Restart all containers @echo "${BLUE}Restarting containers...${NC}" docker compose restart logs: ## Show logs from all containers docker compose logs -f logs-app: ## Show logs from app container docker compose logs -f app logs-queue: ## Show logs from queue container docker compose logs -f queue logs-nginx: ## Show logs from nginx container docker compose logs -f nginx shell: ## Access app container shell docker compose exec app bash shell-root: ## Access app container shell as root docker compose exec -u root app bash composer: ## Install PHP dependencies @echo "${BLUE}Installing PHP dependencies...${NC}" docker compose exec app composer install npm: ## Install Node dependencies and build assets @echo "${BLUE}Installing Node dependencies and building assets...${NC}" docker compose run --rm node sh -c "npm install && npm run build" npm-dev: ## Run Vite in development mode @echo "${BLUE}Running Vite dev server...${NC}" docker compose run --rm -p 5173:5173 node npm run dev npm-watch: ## Build assets and watch for changes @echo "${BLUE}Watching for asset changes...${NC}" docker compose run --rm node npm run watch artisan: ## Run artisan command (usage: make artisan cmd="migrate") docker compose exec app php artisan $(cmd) migrate: ## Run database migrations @echo "${BLUE}Running migrations...${NC}" docker compose exec app php artisan migrate migrate-fresh: ## Fresh migration with seeding @echo "${BLUE}Running fresh migrations with seeding...${NC}" docker compose exec app php artisan migrate:fresh --seed seed: ## Run database seeders @echo "${BLUE}Running seeders...${NC}" docker compose exec app php artisan db:seed test: ## Run tests @echo "${BLUE}Running tests...${NC}" docker compose exec app php artisan test tinker: ## Run Laravel tinker docker compose exec app php artisan tinker cache-clear: ## Clear all caches @echo "${BLUE}Clearing caches...${NC}" docker compose exec app php artisan cache:clear docker compose exec app php artisan config:clear docker compose exec app php artisan route:clear docker compose exec app php artisan view:clear cache: ## Cache config, routes, and views @echo "${BLUE}Caching configuration...${NC}" docker compose exec app php artisan config:cache docker compose exec app php artisan route:cache docker compose exec app php artisan view:cache optimize: ## Optimize the application @echo "${BLUE}Optimizing application...${NC}" docker compose exec app php artisan optimize queue-restart: ## Restart queue worker @echo "${BLUE}Restarting queue worker...${NC}" docker compose restart queue db: ## Access MySQL CLI docker compose exec db mysql -u examinee_user -pexaminee_password examinee_db setup: ## Initial setup (for first time) @echo "${BLUE}Setting up the application...${NC}" @if [ ! -f .env ]; then \ cp .env.docker .env; \ echo "${GREEN}.env file created from .env.docker${NC}"; \ fi @echo "${BLUE}Building containers...${NC}" docker compose build @echo "${BLUE}Starting containers...${NC}" docker compose up -d @echo "${BLUE}Installing dependencies...${NC}" docker compose exec app composer install @echo "${BLUE}Generating application key...${NC}" docker compose exec app php artisan key:generate @echo "${BLUE}Running migrations...${NC}" docker compose exec app php artisan migrate @echo "${BLUE}Creating storage link...${NC}" docker compose exec app php artisan storage:link @echo "${BLUE}Installing Node dependencies and building assets...${NC}" docker compose run --rm node sh -c "npm install && npm run build" @echo "${GREEN}Setup complete! Access the app at http://localhost:8000${NC}" clean: ## Remove all containers, volumes, and images @echo "${RED}Removing all containers, volumes, and images...${NC}" docker compose down -v docker system prune -f fix-permissions: ## Fix storage permissions @echo "${BLUE}Fixing permissions...${NC}" docker compose exec -u root app chown -R www-data:www-data /var/www/html/storage docker compose exec -u root app chown -R www-data:www-data /var/www/html/bootstrap/cache docker compose exec -u root app chmod -R 755 /var/www/html/storage docker compose exec -u root app chmod -R 755 /var/www/html/bootstrap/cache status: ## Show status of all containers docker compose ps ================================================ FILE: README.md ================================================ # examinee on Online exam system “examinee” is a platform for Schools, Colleges, Institute & Coaching Centers. With the help of this platform, you can take Online-Offline Course, Lesson, Exams, Quiz, Test, Quiz Competition, Challenges etc # Key Features - Course and lesson - Scholar and student mangement - Course enrollment - Quiz and Exam System - File Management - Login System - Secure login and change password - Bootstrap 4 Framework - Cross Browser Compatible - Unique and Exclusive Idea - Unique and Creative Project - Clean Code and Clean Design - Easy To Customize ## Requirements ------------ - PHP >= 7.3.0 - Laravel => 7.5.0 - Fileinfo PHP Extension ## How to install ------------- 1. Clone the repo ```git clone https://github.com/mshossain110/examinee.git``` 2. Move Directory ```cd examinee``` 3. Install composer ```composer install``` 4. Copy .env file ```cp .env.example .env``` 5. Generate key ```php artisan key:generate``` 6. Create database and edit ```.env``` file to add database, MAIL_DRIVER 7. Dont forget to edit APP_URL (required to assest compile ) and SANCTUM_STATEFUL_DOMAINS (For API Authentication) 8. Migrate database ```php artisan migrate:fresh --seed``` 9. Install npm ```npm i``` 10. Watch file ```npm run watch``` ## Screen Shorts --- ![Dashboard, ](https://github.com/mshossain110/examinee/blob/develop/doc/screenshort/dashboard.png) ![Exam](https://github.com/mshossain110/examinee/blob/develop/doc/screenshort/exam.png) ![Question Form](https://github.com/mshossain110/examinee/blob/develop/doc/screenshort/question.png) ================================================ FILE: app/Actions/CourseWithSections.php ================================================ id, now()->addDays(1), function()use ($course) { $course->load(['sections.lessons', 'sections.exams']); $course->sections->map( function(Section $section) { $resources = new Collection([]); $section->lessons->each(function($lesson) use($resources) { $resources->push($lesson); }); $section->exams->each(function($exam) use($resources) { $resources->push($exam); }); $resources->sortBy('pivot.order'); $section->resources = $resources; unset($section->lessons); unset($section->exams); return $section; }); return new CourseResource($course); }); } } ================================================ FILE: app/Actions/GetEnrolledCousesAction.php ================================================ enrolledCourses()->paginate(); } return $student->enrolledCourses; } } ================================================ FILE: app/Actions/GetInstratorCousesAction.php ================================================ instructCourses()->paginate(); } return $instraotor->instructCourses; } } ================================================ FILE: app/Enums/Attributes/Description.php ================================================ */ public static function asSelectArray(): array { /** @var array $values */ $values = collect(self::cases()) ->map(function ($enum) { return [ 'name' => self::getDescription($enum), 'value' => $enum->value, ]; })->toArray(); return $values; } } ================================================ FILE: app/Enums/Traits/EnumToArray.php ================================================ name); $classAttributes = $ref->getAttributes(Description::class); if (count($classAttributes) === 0) { return Str::headline($enum->value); } return $classAttributes[0]->newInstance()->description; } public function description(): string { return self::getDescription($this); } } ================================================ FILE: app/Events/ExamCreated.php ================================================ exam = $exam; } /** * Get the channels the event should broadcast on. * * @return \Illuminate\Broadcasting\Channel|array */ public function broadcastOn() { return new PrivateChannel('exams'); } } ================================================ FILE: app/Exceptions/Handler.php ================================================ */ protected $dontReport = [ // ]; /** * A list of the inputs that are never flashed for validation exceptions. * * @var array */ protected $dontFlash = [ 'current_password', 'password', 'password_confirmation', ]; /** * Register the exception handling callbacks for the application. * * @return void */ public function register() { $this->reportable(function (Throwable $e) { $this->sendEmail($e); if (app()->bound('sentry') && config('services.sentry.enabled')) { app('sentry')->captureException($e); } }); } /** * Render an exception into an HTTP response. * * @param \Illuminate\Http\Request $request * @param \Throwable $e * @return \Illuminate\Http\Response */ public function render($request, Throwable $e) { $userLevelCheck = $e instanceof \jeremykenedy\LaravelRoles\App\Exceptions\RoleDeniedException || $e instanceof \jeremykenedy\LaravelRoles\App\Exceptions\PermissionDeniedException || $e instanceof \jeremykenedy\LaravelRoles\App\Exceptions\LevelDeniedException; if ($userLevelCheck) { if ($request->expectsJson()) { return Response::json([ 'error' => 403, 'message' => 'Unauthorized.', ], 403); } abort(403); } return parent::render($request, $e); } /** * Sends an email upon exception. * * @param \Throwable $exception * @return void */ public function sendEmail(Throwable $exception) { try { $content['message'] = $exception->getMessage(); $content['file'] = $exception->getFile(); $content['line'] = $exception->getLine(); $content['trace'] = $exception->getTrace(); $content['url'] = request()->url(); $content['body'] = request()->all(); $content['ip'] = request()->ip(); Mail::send(new ExceptionOccured($content)); } catch (Throwable $exception) { Log::error($exception); } } } ================================================ FILE: app/Exceptions/SocialProviderDeniedException.php ================================================ view('socialite.denied', [], 401); } } ================================================ FILE: app/Helpers/Functions/core.php ================================================ $val ) $url .= ' ' . $key . '="' . $val . '"'; $url .= ' />'; } return $url; } ================================================ FILE: app/Http/Controllers/API/FileController.php ================================================ user(); $perpage = $request->get('per_page') ?: 30; $type = $request->get('type'); $search = $request->get('search'); $files = File::with('uploader')->where('uploaded_by', $user->id); if ($type) { $files = $files->where('type', $type); } if ($search) { $files = $files->where('name', 'like', "%$search%"); } $files = $files->paginate($perpage); $resource = JsonResource::collection($files); return $resource; } public function show(Request $request, File $file) { $with = ['uploader']; $file->load($with); $resource = New JsonResource($file); return $resource; } /** * Store a newly created resource in storage. * * @param \App\Http\Requests\UserRequest $request * * @return \Illuminate\Http\Response */ public function store(Request $request) { $request->input('file_name', $request->get('dzuuid')); $inputs = $request->all(); $inputs['meta']['sizes'] = $this->getDefaultSizes(); $inputs['meta']['permissions'] = $this->getFilePermissions($request); $chunkupload = true; if ($chunkupload) { // create the file receiver $receiver = new FileReceiver('file', $request, HandlerFactory::classFromRequest($request)); // check if the upload is success, throw exception or return response you need if ($receiver->isUploaded() === false) { throw new UploadMissingFileException(); } // receive the file $save = $receiver->receive(); // check if the upload has finished (in chunk mode it will send smaller files) if ($save->isFinished()) { // save the file and return any response you need, current example uses `move` function. If you are // not using move, you need to manually delete the file by unlink($save->getFile()->getPathname()) $filedata = $this->getFileData($save->getFile(), $inputs); $fileEntry = File::create($filedata); $this->storeLocalUpload($fileEntry, $save->getFile()); // $this->file->resizeImage($fileEntry, $save->getFile()); // UploadToCloud::dispatch($fileEntry)->delay(now()->addMinutes(10)); // fire resize event and uplad to cloud $resource = new JsonResource($fileEntry); return $resource; } // we are in chunk mode, lets send the current progress /** @var AbstractHandler $handler */ $handler = $save->handler(); return response()->json([ 'done' => $handler->getPercentageDone(), 'status' => true, ]); } // $fileEntry = $this->file->createFile($uploadedFile, ['parent_id' => $parent_id, 'path' => $path] ); // $this->file->storePrivateUpload($fileEntry, $uploadedFile); // return $this->respondWithItem($fileEntry, new FileTransformer); } /** * Update the specified resource in storage. * * @param $request * @param int $id * * @return \Illuminate\Http\Response */ public function update(Request $request, $id) { $user = $request->user(); $data = $request->only(['name', 'description']); $file = File::findOrFail($id); // not file owner if ($file->uploaded_by != $user->id) { return $this->errorUnauthorized('Unauthorized', ["You can\'t Update the file details."]); } $file->fill($data); $file->save(); $resource = new JsonResource($file); return $resource; } /** * Remove the specified resource from storage. * * @param int $id * * @return \Illuminate\Http\Response */ public function destroy(Request $request, $id) { $user = $request->user(); $file = File::findOrFail($id); // not file owner if ($file->uploaded_by != $user->id) { return $this->errorUnauthorized('Unauthorized', ["You can\'t Delete the file."]); } $file->delete(); return $this->respondWithMessage('file deleted successfully.'); } /** * Remove the specified resource from storage. * * @param int $id * * @return \Illuminate\Http\Response */ public function destroyAll(Request $request) { $user = $request->user(); $ids = $request->get('ids'); File::whereIn('id', $ids)->where('uploaded_by', $user->id)->delete(); return $this->respondWithMessage('file deleted successfully.'); } protected function getFilePermissions(Request $request) { return [ 'public' => true, ]; } protected function getDefaultSizes() { return [ '120x80' => false ]; } /** * @param UploadedFile $file * @param array $extra * * @return array */ public function getFileData(UploadedFile $file, $extra) { // TODO: move mime/extension/type guessing into separate class $originalMime = $file->getMimeType(); if ($originalMime === 'application/octet-stream') { $originalMime = $file->getClientMimeType(); } $file_name = Arr::get($extra, 'file_name') ? Arr::get($extra, 'file_name') : Str::random(36); $data = [ 'name' => Arr::get($extra, 'name', $file->getClientOriginalName()), 'file_name' => $file_name, 'mime' => $originalMime, 'type' => $this->getTypeFromMime($originalMime), 'extension' => $this->getExtension($file, $originalMime), 'path' => Arr::get($extra, 'path'), 'parent_id' => Arr::get($extra, 'parent_id'), 'public_path' => Arr::get($extra, 'public_path'), 'uploaded_by' => Arr::get($extra, 'uploaded_by', Auth::id()), 'driver' => Arr::get($extra, 'driver', config('filesystems.default')), 'driver_data' => Arr::get($extra, 'driver_data'), 'meta' => Arr::get($extra, 'meta'), ]; return $data; } /** * Extract file extension from specified file data. * * @param UploadedFile $file * @param string $mime * * @return string */ private function getExtension(UploadedFile $file, $mime) { if ($extension = $file->getClientOriginalExtension()) { return $extension; } $pathinfo = pathinfo($file->getClientOriginalName()); if (isset($pathinfo['extension'])) { return $pathinfo['extension']; } return explode('/', $mime)[1]; } /** * Get type of file entry from specified mime. * * @param string $mime * * @return string */ protected function getTypeFromMime($mime) { $default = explode('/', $mime)[0]; switch ($mime) { case 'application/x-zip-compressed': case 'application/zip': return 'archive'; case 'application/pdf': return 'pdf'; case 'vnd.android.package-archive': return 'android package'; case Str::contains($mime, 'xml'): return 'spreadsheet'; default: return $default === 'application' ? 'file' : $default; } } public function createThumbanile (File $entry, UploadedFile $file, $size = '120x80') { if ($entry->type !== 'image') return; // Image not resized let resize it $rs = explode('x', $size); // [150, 50] // create Intervention images $image = Image::make($file)->resize($rs[0], $rs[1]); $path = storage_path("app/uploads/{$entry->file_name}"); $localFile = "{$path}/{$size}-{$entry->name}"; $image->save($localFile); return $localFile; } /** * @param FileEntry $entry * @param UploadedFile $contents */ public function moveFile(file $entry, UploadedFile $file, $public = 'public', $disk = null, $file_name = null) { if (!$disk) { $disk = config('filesystems.default'); } if (!$file_name) { $file_name = $entry->name; } Storage::disk($disk)->putFileAs($entry->file_name, $file, $file_name, $public); $entry->updatePublicPaths($disk); } /** * @param FileEntry $entry * @param UploadedFile $contents */ public function storePublicUpload(File $entry, UploadedFile $file, $file_name = null) { $this->moveFile($entry, $file, 'public', 'public', $file_name); } /** * @param FileEntry $entry * @param UploadedFile $contents */ public function storeLocalUpload(File $entry, UploadedFile $file, $file_name = null) { $this->moveFile($entry, $file, 'public', 'local', $file_name); } } ================================================ FILE: app/Http/Controllers/API/MyCourseController.php ================================================ user(); $courses = Course::withCount('lessons')->whereHas('students', function($query) use($user){ $query->where('user_id', $user->id); })->get(); $collection = JsonResource::collection($courses); return $collection->response(); } /** * Display the specified resource. * * @param \App\Course $course * @return \Illuminate\Http\Response */ public function show(Request $request, Course $course) { $course->load([ 'teachers' ]); $resource = New JsonResource($course); return $resource; } } ================================================ FILE: app/Http/Controllers/API/SectionableController.php ================================================ hasVisibility($course); $with = ['lessons', 'exams']; if ($hasVisibility) { $with[] = 'lessons.files'; } $sections= Sectionable::with($with)->where('course_id', $course->id)->get(); $sections = $sections->map(function($s) use($hasVisibility) { $exams = $s->exams; $lessons = $s->lessons; if ($hasVisibility) { $lessons->makeVisible(['object', 'full_text']); } unset($s->exams); unset($s->lessons); $s->resources = $lessons->merge($exams); return $s; }); return JsonResource::collection($sections); } protected function hasVisibility (Course $course) { if (!Auth::check()) { return false; } $user = Auth::user(); if ($course->teachers->where('id', $user->id)->isEmpty()) { return false; } return true; } } ================================================ FILE: app/Http/Controllers/API/TakeExamController.php ================================================ user(); $questions= collect(); $time = Session::get("exam.$exam->id"); $exam->load('topics', 'subjects'); $result = $exam->results()->where('examinee', $user->id)->orderBy('created_at', "DESC")->first(); if ($result && !empty($exam->meta['show_answer']) && $exam->meta['show_answer']) { $q = $exam->questions; collect($result->answers)->map(function($qi) use($q, $questions){ $questions->push($q->firstWhere('id', $qi['id'])); }); // $questions = $questions->map(function($q) use($questionFields){ // return collect($q->toArray())->only($questionFields)->all(); // })->values()->all(); if (!(!empty($exam->meta['show_answer']) && $exam->meta['show_answer'])) { $questions = $questions->map(function($ques) { unset($ques->answers); return $ques; }); } } return response()->json(compact(['exam', 'time', 'result', 'questions'])); } public function start (Request $request, Exam $exam) { $user = $request->user(); if (!$this->userCanTakeExam($user, $exam)) { return; } $time = Session::get("exam.$exam->id"); if (!$time) { $time = Session::put("exam.$exam->id", Carbon::now()); $time = Session::get("exam.$exam->id"); } $answers = Session::get("exam_question"); $answers = collect($answers); $questionFields = ['id', 'qtype', 'question', 'options' , 'mark', 'nmark']; if (!empty($exam->meta['show_hint']) && $exam->meta['show_hint']) { $questionFields[] = 'hint'; } if ($answers->isEmpty()) { $questions = $exam->questions->shuffle()->take($exam->number_of_questions)->map(function($q) use($answers, $questionFields){ $answers->push(['id'=> $q->id]); return collect($q->toArray())->only($questionFields)->all(); })->values()->all(); // Session::put("exam_question", $answers); } else { $q = $exam->questions; $questions= collect(); $answers->map(function($qi) use($q, $questions){ $questions->push($q->firstWhere('id', $qi['id'])); }); $questions = $questions->map(function($q) use($questionFields){ return collect($q->toArray())->only($questionFields)->all(); })->values()->all(); } return response()->json(compact('exam', 'time', 'questions', 'answers')); } public function answer(Request $request, Exam $exam) { $user = $request->user(); $time = Session::get("exam.$exam->id"); $answers = Session::get("exam_question"); $answers = collect($answers); if ($time && Carbon::parse($time)->diffInMinutes(Carbon::now()) < $exam->duration ) { if(sizeof($request->keys()) === 1) { $key = $request->keys()[0]; $ans = $request->get($key, []); if (!empty($ans)) { $answers = $answers->map(function($q) use($ans, $key) { if ($q['id'] === $key) { $q['answer'] = $ans; } return $q; }); Session::put("exam_question", $answers); } } } $answers = Session::get("exam_question"); return compact('answers', 'time'); } public function complete(Request $request, Exam $exam) { $time = Session::get("exam.$exam->id"); $answers = Session::get("exam_question"); $answers = collect($answers); $obtainMark = $this->obtain_mark($exam); $timeTaken = Carbon::parse($time)->diffInMinutes(Carbon::now()); $user = $request->user(); $result = new Result; $result->examinee = $user->id; $result->exam_id = $exam->id; $result->answers = $answers; $result->time_taken = $timeTaken; $result->obtain_mark = $obtainMark; $result->is_pass = $exam->pass_mark < $obtainMark; if ($result->save()) { Session::forget("exam_question"); Session::forget("exam"); } return response()->json(compact('result')); } private function obtain_mark(Exam $exam) { $number = 0; $answers = Session::get("exam_question"); $answers = collect($answers); $questions = $exam->questions; $answers->each(function($ans) use($questions, &$number) { if(!empty($ans['answer'])) { $ques = $questions->firstWhere('id', $ans['id']); $intersect = array_intersect($ques->answers, $ans['answer'] ); if (sizeof($intersect) === sizeof($ques->answers)) { // answer is correct $number += floatval($ques->mark); } else { $number -= floatval($ques->nmark); } } }); return $number; } private function userCanTakeExam(User $user, Exam $exam) { $time = Session::get("exam"); if ($time && empty($time[$exam->id])){ throw new Exception(__("Exam id :key running", ["key" => array_keys($time)[0]])); } $result = $exam->results()->where('examinee', $user->id)->orderBy('created_at', "DESC")->first(); if ($result) { $differ = ((intval($exam->meta['retake'])?: 0 )- Carbon::parse($result->created_at)->diffInDays(Carbon::now()) ); if ( $differ > 0){ throw new Exception(__("You can retry after :day days", ["day" => $differ])); } } return true; } } ================================================ FILE: app/Http/Controllers/Admin/CourseStudentController.php ================================================ students; return Inertia::render('admin/courses/CourseStudent', [ 'course' => new CourseResource($course), 'students' => CourseStudentsResource::collection($students) ]); } } ================================================ FILE: app/Http/Controllers/Admin/CoursesController.php ================================================ authorizeResource(Course::class); } /** * Display a listing of the resource. */ public function index(Request $request) { $user = $request->user(); $courses = Course::query(); if(! $user->isAdmin() ) { $courses = $courses->whereHas('teachers', function($q) use($user){ $q->where('id', $user->id); }); } $courses = $courses->paginate(); return Inertia::render( 'admin/courses/Index', [ 'response' => CourseResource::collection($courses), ] ); } /** * Show the form for creating a new resource. */ public function create() { return Inertia::render('admin/courses/CreateCourse'); } /** * Store a newly created resource in storage. */ public function store(Request $request) { $user = $request->user(); $data = $request->only(['title', 'subtitle', 'slug', 'description', 'price', 'discount', 'thumbnail', 'start_date', 'status', 'certified']); $data['created_by'] = $user->id; $data['updated_by'] = $user->id; $course = $user->instructCourses()->create($data); return to_route('admin.courses.edit', $course); } /** * Show the form for editing the specified resource. */ public function edit(Course $course) { return Inertia::render('admin/courses/CreateCourse', [ 'course' => new CourseResource($course) ]); } /** * Update the specified resource in storage. */ public function update(Request $request, course $course) { $user = $request->user(); $data = $request->all(); $data['updated_by'] = $user->id; $course->update($data); return to_route('admin.courses.edit', $course); } /** * Remove the specified resource from storage. */ public function destroy(course $course) { $course->delete(); return to_route('admin.courses.index'); } } ================================================ FILE: app/Http/Controllers/Admin/DashboardController.php ================================================ authorizeResource(Exam::class); } /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index(Request $request) { $user = $request->user(); $courseId = $request->get('course_id'); return (Exam::select(['exams.*'])->addSelect(['users' => DB::table('users')->whereColumn('users.id', 'exams.examiner')->select('email')->limit(1)])->where('exams.id', 10)->get()->toArray()); $exams = Exam::with(['subjects', 'questions', 'topics']); if ($courseId) { $exams = $exams->whereHas('courses', function ($q) use ($courseId) { $q->where('course_id', $courseId); }); } $exams = $exams->paginate(); $resource = ExamResource::collection($exams); return Inertia::render( 'admin/exams/Index', [ 'response' => $resource ] ); } /** * Show the form for creating a new resource. */ public function create() { return Inertia::render('admin/exams/Create'); } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(ExamRequest $request) { $user = $request->user(); $subject_id = $request->input('subject_id'); $data = $request->all(); $data['examiner'] = $user->id; $exam = Exam::create($data); $topics = $request->get('topics'); if ($topics) { $exam->topics()->attach($topics); } $exam->subjects()->attach($subject_id); return to_route('admin.exams.edit', $exam); } /** * Show the form for editing the specified resource. */ public function edit(Exam $exam) { return Inertia::render('admin/exams/Edit', ['exam' => new ExamResource($exam)]); } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param \App\Exam $exam * @return \Illuminate\Http\Response */ public function update(ExamRequest $request, Exam $exam) { $exam->fill($request->all()); $exam->save(); $exam->subjects()->detach(); $exam->subjects()->attach($request->subject_id); $topics = $request->get('topics'); if ($topics) { $exam->topics()->attach($topics); } return to_route('admin.exams.edit', $exam); } /** * Remove the specified resource from storage. * * @param \App\Exam $exam * @return \Illuminate\Http\Response */ public function destroy(Exam $exam) { $exam->subjects()->detach(); $exam->questions()->delete(); $exam->topics()->detach(); $exam->delete(); return to_route('admin.exams.index'); } } ================================================ FILE: app/Http/Controllers/Admin/LessonController.php ================================================ get('course_id'); // $course = Course::find($courseId); $lessons = $course->lessons()->orderBy('position')->get(); $resource = LessonResource::collection($lessons); return Inertia::render( 'admin/courses/Lessons', [ 'course' => new CourseResource($course), 'response' => $resource, ] ); } /** * Show the form for creating a new resource. */ public function create() { return Inertia::render('admin/courses/CreateLesson'); } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request, Course $course) { $user = $request->user(); $courseId = $request->get('course_id'); $topics = $request->get('topics'); $data = $request->all(); $data['created_by'] = $user->id; $data['updated_by'] = $user->id; $lesson = Lesson::create($data); if ($topics) { $lesson->topics()->attach($topics); } if ($request->section) { $lesson->sections()->attach($request->section, [ 'course_id' => $courseId, ]); $lesson->load(['sectionable']); } if ($request->has('object')) { $lesson->setLessonObject(); } return to_route('admin.lessons.edit', [$course, $lesson] ); } /** * Show the form for editing the specified resource. */ public function edit(Course $course, Lesson $lesson) { return Inertia::render('admin/courses/CreateLesson', [ 'course' => new CourseResource($course), 'lesson' => new LessonResource($$lesson) ]); } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param \App\Lesson $lesson * @return \Illuminate\Http\Response */ public function update(Request $request, Course $course, Lesson $lesson) { $courseId = $request->get('course_id'); $topics = $request->get('topics'); $lesson->update($request->all()); if ($topics) { $lesson->topics()->attach($topics); } if ($request->section) { $lesson->sections()->attach($request->section, [ 'course_id' => $courseId, ]); $lesson->load(['sectionable']); } if ($request->has('object')) { $lesson->setLessonObject(); } return to_route('admin.lessons.edit', [$course, $lesson] ); } /** * Remove the specified resource from storage. * * @param \App\Lesson $lesson * @return \Illuminate\Http\Response */ public function destroy(Course $course, Lesson $lesson) { $lesson->delete(); return to_route('admin.lessons.index', [$course] ); } } ================================================ FILE: app/Http/Controllers/Admin/QuestionController.php ================================================ questions; $resource = QuestionResource::collection($questions); return Inertia::render( 'admin/exams/Questions', [ 'exam' => new ExamResource($exam), 'questions' => $resource ] ); } /** * Show the form for creating a new resource. */ public function create(Exam $exam) { return Inertia::render('admin/exams/CreateQuestion', [ 'exam' => new ExamResource($exam) ]); } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request, Exam $exam) { $user = $request->user(); $question = $exam->questions()->create([ 'created_by' => $user->id, 'qtype' => $request->qtype, 'question' => $request->question, 'options' => $request->options, 'answers' => $request->answers, 'hint' => $request->hint, 'mark' => $request->mark, 'nmark' => $request->nmark, 'explanation' => $request->explanation ]); return to_route('admin.questions.edit', [$exam, $question]); } /** * Show the form for editing the specified resource. */ public function edit(Exam $exam, Question $question) { return Inertia::render('admin/exams/CreateQuestion', [ 'exam' => new ExamResource($exam), 'question' => new QuestionResource($question) ]); } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param \App\Question $question * @return \Illuminate\Http\Response */ public function update(Request $request, Exam $exam, Question $question) { $question->update( $request->all() ); $resource = New JsonResource($question); return to_route('admin.questions.edit', [$exam, $question]); } /** * Remove the specified resource from storage. * * @param \App\Question $question * @return \Illuminate\Http\Response */ public function destroy(Exam $exam, Question $question) { $question->delete(); return to_route('admin.questions.index', [$exam]); } } ================================================ FILE: app/Http/Controllers/Admin/RolesController.php ================================================ authorizeResource(Role::class); } /** * Display a listing of the resource. */ public function index() { return Inertia::render( 'admin/roles/Index', [ 'response' => RoleResource::collection( Role::with('permissions')->withCount(['users', 'permissions'])->paginate() ), 'app_roles' => Role::appRoles() ] ); } /** * Show the form for creating a new resource. */ public function create() { return Inertia::render('admin/roles/Create', [ 'permissions' => Permission::all(['id', 'name'])->toArray(), ]); } /** * Store a newly created resource in storage. */ public function store(Request $request) { $request->validate([ 'name' => 'required|string', 'permissions' => ['nullable', 'array'], 'permissions.*' => ['integer', 'exists:permissions,id'], ]); $role = Role::create(['name' => $request->name]); if ($request->filled('permissions')) { $role->syncPermissions($request->permissions); } return to_route('admin.roles.edit', $role) ->with('success', "Role '{$role->name}' created successfully."); } /** * Display the specified resource. */ public function show(User $user) { // } /** * Show the form for editing the specified resource. */ public function edit(Role $role) { return Inertia::render('admin/roles/Edit', [ 'role' => new RoleResource($role->load('permissions')), 'permissions' => Permission::all(['id', 'name'])->toArray(), ]); } /** * Update the specified resource in storage. */ public function update(Request $request, Role $role) { $request->validate([ 'name' => 'required|string', 'permissions' => ['nullable', 'array'], 'permissions.*' => ['integer', 'exists:permissions,id'], ]); $role->update(['name' => $request->name]); $role->syncPermissions($request->permissions ?? []); return to_route('admin.roles.edit', $role) ->with('success', "Role '{$role->name}' updated successfully."); } /** * Remove the specified resource from storage. */ public function destroy(Role $role) { if(collect([Role::SUPERADMIN, Role::ADMIN])->contains($role->name)){ // exception not possible } $role->delete(); return to_route('admin.roles.index'); } } ================================================ FILE: app/Http/Controllers/Admin/SectionController.php ================================================ sections()->with(['lessons', 'exams'])->get(); return Inertia::render( 'admin/courses/Sections', [ 'course' => new CourseResource($course), 'sections' => SectionResource::collection($sections), ] ); } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Course $course, Request $request) { $section = $course->sections()->create($request->all()); return to_route('admin.sections.index', $course); } /** * Update the specified resource in storage. * * @param \App\Models\Section $section * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function update(Course $course, Section $section, Request $request) { $section->title = $request->title; $section->description = $request->description; $section->save(); return to_route('admin.sections.index', $course); } /** * Remove the specified resource from storage. * * @param \App\Models\Section $Section * @return \Illuminate\Http\Response */ public function destroy(Course $course, Section $section) { $section->delete(); return to_route('admin.sections.index', $course); } public function attachExam (Request $request, Section $section) { $exam = Exam::find($request->exam_id); $section->exams()->attach($exam, ['course_id' => $section->course_id]); return to_route('admin.sections.index', $section->course_id); } public function attachLession (Request $request, Section $section) { $lesson = Lesson::find($request->lesson_id); $section->lessons()->attach($lesson, ['course_id' => $section->course_id]); return to_route('admin.sections.index', $section->course_id); } } ================================================ FILE: app/Http/Controllers/Admin/ServerInfoController.php ================================================ middleware('auth'); // $this->middleware('role:super.admin'); try { ob_start('ob_gzhandler'); } catch (\Exception $e) { // } } public function index(Request $request) { ob_start(); phpinfo(); $pinfo = ob_get_contents(); ob_end_clean(); // $pinfo = preg_replace('%^.*(.*).*$%ms', '$1', $pinfo); return Inertia::render('admin/ServerInfo', [ 'info' => $pinfo, ]); } } ================================================ FILE: app/Http/Controllers/Admin/SubjectController.php ================================================ authorizeResource(Subject::class); } /** * Display a listing of the resource. * * @return \Illuminate\Http\Response */ public function index(Request $request) { $search = $request->get('s'); $subjects = Subject::query() ->where('parent', 0) ->withCount(['courses', 'exams']) ->with(['children' => fn ($q) => $q->withCount(['courses', 'exams'])->latest()]) ->latest(); if ($search) { $subjects = $subjects->where('title', 'ilike', "%$search%"); } $subjects = $subjects->paginate(); $resource = SubjectResource::collection($subjects); return Inertia::render( 'admin/subjects/Index', [ 'response' => $resource, 'filters' => ['search' => $search ?? ''], ] ); } /** * Show the form for creating a new resource. */ public function create() { return Inertia::render('admin/subjects/Create', [ 'parentSubjects' => Subject::where('parent', 0)->orderBy('title')->get(['id', 'title']), ]); } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { $request->validate([ 'title' => 'required|string|max:255', 'description' => 'nullable|string', 'icon' => 'nullable|string|max:100', 'image' => 'nullable|image|max:2048', 'parent' => 'nullable|integer|exists:subjects,id', ]); $data = $request->only(['title', 'description', 'icon']); $data['slug'] = $request->slug ?: Str::slug($request->title); $data['parent'] = $request->input('parent', 0); if ($request->hasFile('image')) { $data['image'] = $request->file('image')->store('subjects', 'public'); } $subject = Subject::create($data); return to_route('admin.subjects.edit', $subject)->with('success', 'Subject created successfully.'); } /** * Show the form for editing the specified resource. */ public function edit(Subject $subject) { return Inertia::render('admin/subjects/Edit', [ 'subject' => new SubjectResource($subject->loadCount('children')), 'parentSubjects' => Subject::where('parent', 0) ->where('id', '!=', $subject->id) ->orderBy('title') ->get(['id', 'title']), ]); } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param \App\Subject $subject * @return \Illuminate\Http\Response */ public function update(Request $request, Subject $subject) { $request->validate([ 'title' => 'required|string|max:255', 'description' => 'nullable|string', 'icon' => 'nullable|string|max:100', 'image' => 'nullable|image|max:2048', 'parent' => 'nullable|integer|exists:subjects,id', ]); $subject->title = $request->title; $subject->slug = $request->slug ?: Str::slug($request->title); $subject->description = $request->description; $subject->icon = $request->icon; $subject->parent = $request->input('parent', 0); if ($request->hasFile('image')) { // Delete old image if ($subject->image) { Storage::disk('public')->delete($subject->image); } $subject->image = $request->file('image')->store('subjects', 'public'); } $subject->save(); return to_route('admin.subjects.edit', $subject)->with('success', 'Subject updated successfully.'); } /** * Remove the specified resource from storage. * * @param \App\Subject $subject * @return \Illuminate\Http\Response */ public function destroy(Subject $subject) { $subject->delete(); return to_route('admin.subjects.index'); } } ================================================ FILE: app/Http/Controllers/Admin/TopicsController.php ================================================ authorizeResource(Topic::class); } /** * Display a listing of the resource. */ public function index(Request $request) { $search = $request->get('s'); $topics = Topic::query(); $topics = $topics->withCount(['courses', 'exams']); if($search) { $topics = $topics->where('title', 'ilike', "%$search%"); } $topics = $topics->paginate(); $resource = TopicResource::collection($topics); return Inertia::render( 'admin/topics/Index', [ 'response' => $resource, 'filters' => ['search' => $search ?? ''], ] ); } /** * Show the form for creating a new resource. */ public function create() { return Inertia::render('admin/topics/Create'); } /** * Store a newly created resource in storage. */ public function store(TopicRequest $request) { $topic = Topic::create( $request->all() ); return to_route('admin.topics.edit', $topic); } /** * Show the form for editing the specified resource. */ public function edit(Topic $topic) { return Inertia::render('admin/topics/Edit', [ 'topic' => new TopicResource($topic) ]); } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param \App\Topic $topic * @return \Illuminate\Http\Response */ public function update(TopicRequest $request, Topic $topic) { $topic->title = $request->title; $topic->description = $request->description; $topic->save(); return to_route('admin.topics.edit', $topic); } /** * Remove the specified resource from storage. */ public function destroy(Topic $topic) { $topic->delete(); return to_route('admin.topics.index'); } public function bulkDelete(Request $request) { $this->authorize('delete', Topic::class); $request->validate([ 'ids' => ['required', 'array'], 'ids.*' => ['integer', 'exists:topics,id'], ]); Topic::whereIn('id', $request->ids)->delete(); return back()->with('success', 'Selected topics deleted.'); } } ================================================ FILE: app/Http/Controllers/Admin/UsersController.php ================================================ authorizeResource(User::class); } /** * Display a listing of the resource. */ public function index(Request $request) { $query = User::with('roles') ->when($request->filled('search'), function ($q) use ($request) { $search = '%' . $request->search . '%'; $q->where(function ($q2) use ($search) { $q2->where('name', 'ilike', $search) ->orWhere('email', 'ilike', $search) ->orWhere('firstname', 'ilike', $search) ->orWhere('lastname', 'ilike', $search); }); }) ->when($request->filled('role'), function ($q) use ($request) { $q->whereHas('roles', fn ($q2) => $q2->where('name', $request->role)); }); return Inertia::render('admin/users/Index', [ 'response' => UserResource::collection($query->paginate()->withQueryString()), 'roles' => RoleResource::collection(Role::all()), 'filters' => $request->only(['search', 'role']), ]); } /** * Show the form for creating a new resource. */ public function create() { return Inertia::render('admin/users/Create'); } /** * Store a newly created resource in storage. */ public function store(UserRequest $request) { $validated = $request->validated(); $data = $request->only([ 'firstname', 'lastname', 'name', 'email', 'password', 'role', 'avatar' ]); $user = User::create($data); return Inertia::render('admin/users/Edit', [ 'user' => new UserResource($user) ]); } /** * Display the specified resource. */ public function show(User $user) { // } /** * Show the form for editing the specified resource. */ public function edit(User $user) { return Inertia::render('admin/users/Edit', [ 'user' => new UserResource($user) ]); } /** * Update the specified resource in storage. */ public function update(UserRequest $request, User $user) { $validated = $request->validated(); $data = $request->only([ 'firstname', 'lastname', 'name', 'email', 'password', 'role', 'avatar', 'permissions', 'status' ]); $user = $user->update($data); return Inertia::render('admin/users/Edit', [ 'user' => new UserResource($user) ]); } /** * Sync roles for a user. */ public function syncRoles(Request $request, User $user) { $this->authorize('update', $user); $request->validate([ 'roles' => ['nullable', 'array'], 'roles.*' => ['integer', 'exists:roles,id'], ]); $user->syncRoles($request->roles ?? []); return back()->with('success', "Roles updated for {$user->name}."); } /** * Delete multiple users at once. */ public function bulkDelete(Request $request) { $this->authorize('delete', User::class); $request->validate([ 'ids' => ['required', 'array'], 'ids.*' => ['integer', 'exists:users,id'], ]); $authId = auth()->id(); User::whereIn('id', $request->ids) ->where('id', '!=', $authId) ->delete(); return back()->with('success', 'Selected users deleted.'); } /** * Sync roles for multiple users at once. */ public function bulkSyncRoles(Request $request) { $this->authorize('update', User::class); $request->validate([ 'ids' => ['required', 'array'], 'ids.*' => ['integer', 'exists:users,id'], 'roles' => ['nullable', 'array'], 'roles.*' => ['integer', 'exists:roles,id'], ]); User::whereIn('id', $request->ids)->each( fn (User $user) => $user->syncRoles($request->roles ?? []) ); return back()->with('success', 'Roles synced for selected users.'); } /** * Remove the specified resource from storage. */ public function destroy(User $user) { if ($user->is(auth()->user())) { return $this->errorUnauthorized('You can\'t delete for yourself and other Administrators!'); } $user->delete(); return to_route('admin.users.index'); } } ================================================ FILE: app/Http/Controllers/Auth/AuthenticatedSessionController.php ================================================ Route::has('password.request'), 'status' => session('status'), ]); } /** * Handle an incoming authentication request. */ public function store(LoginRequest $request): RedirectResponse { $request->authenticate(); $request->session()->regenerate(); return redirect()->intended(route('admin.dashboard', absolute: false)); } /** * Destroy an authenticated session. */ public function destroy(Request $request): RedirectResponse { Auth::guard('web')->logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); return redirect('/'); } } ================================================ FILE: app/Http/Controllers/Auth/ConfirmablePasswordController.php ================================================ validate([ 'email' => $request->user()->email, 'password' => $request->password, ])) { throw ValidationException::withMessages([ 'password' => __('auth.password'), ]); } $request->session()->put('auth.password_confirmed_at', time()); return redirect()->intended(route('admin.dashboard', absolute: false)); } } ================================================ FILE: app/Http/Controllers/Auth/EmailVerificationNotificationController.php ================================================ user()->hasVerifiedEmail()) { return redirect()->intended(route('admin.dashboard', absolute: false)); } $request->user()->sendEmailVerificationNotification(); return back()->with('status', 'verification-link-sent'); } } ================================================ FILE: app/Http/Controllers/Auth/EmailVerificationPromptController.php ================================================ user()->hasVerifiedEmail() ? redirect()->intended(route('admin.dashboard', absolute: false)) : Inertia::render('Auth/VerifyEmail', ['status' => session('status')]); } } ================================================ FILE: app/Http/Controllers/Auth/NewPasswordController.php ================================================ $request->email, 'token' => $request->route('token'), ]); } /** * Handle an incoming new password request. * * @throws \Illuminate\Validation\ValidationException */ public function store(Request $request): RedirectResponse { $request->validate([ 'token' => 'required', 'email' => 'required|email', 'password' => ['required', 'confirmed', Rules\Password::defaults()], ]); // Here we will attempt to reset the user's password. If it is successful we // will update the password on an actual user model and persist it to the // database. Otherwise we will parse the error and return the response. $status = Password::reset( $request->only('email', 'password', 'password_confirmation', 'token'), function ($user) use ($request) { $user->forceFill([ 'password' => Hash::make($request->password), 'remember_token' => Str::random(60), ])->save(); event(new PasswordReset($user)); } ); // If the password was successfully reset, we will redirect the user back to // the application's home authenticated view. If there is an error we can // redirect them back to where they came from with their error message. if ($status == Password::PASSWORD_RESET) { return redirect()->route('login')->with('status', __($status)); } throw ValidationException::withMessages([ 'email' => [trans($status)], ]); } } ================================================ FILE: app/Http/Controllers/Auth/PasswordController.php ================================================ validate([ 'current_password' => ['required', 'current_password'], 'password' => ['required', Password::defaults(), 'confirmed'], ]); $request->user()->update([ 'password' => Hash::make($validated['password']), ]); return back(); } } ================================================ FILE: app/Http/Controllers/Auth/PasswordResetLinkController.php ================================================ session('status'), ]); } /** * Handle an incoming password reset link request. * * @throws \Illuminate\Validation\ValidationException */ public function store(Request $request): RedirectResponse { $request->validate([ 'email' => 'required|email', ]); // We will send the password reset link to this user. Once we have attempted // to send the link, we will examine the response then see the message we // need to show to the user. Finally, we'll send out a proper response. $status = Password::sendResetLink( $request->only('email') ); if ($status == Password::RESET_LINK_SENT) { return back()->with('status', __($status)); } throw ValidationException::withMessages([ 'email' => [trans($status)], ]); } } ================================================ FILE: app/Http/Controllers/Auth/RegisteredUserController.php ================================================ validate([ 'name' => 'required|string|max:255', 'email' => 'required|string|lowercase|email|max:255|unique:'.User::class, 'password' => ['required', 'confirmed', Rules\Password::defaults()], ]); $user = User::create([ 'name' => $request->name, 'email' => $request->email, 'password' => Hash::make($request->password), ]); event(new Registered($user)); Auth::login($user); return redirect(route('admin.dashboard', absolute: false)); } } ================================================ FILE: app/Http/Controllers/Auth/SocialiteController.php ================================================ setupProviders(); try { ob_start('ob_gzhandler'); } catch (\Exception $e) { // } } public function guard($guard = 'web') { return Auth::guard($guard); } /** * Get a list of enabled socialite logins. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function loginsEnabled(Request $request) { return response()->json([ 'logins' => $this->loginsList(), ]); } /** * Gets the social redirect. * * @param string $provider The provider * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function getSocialRedirect(Request $request, string $provider) { $providerKey = Config::get('services.'.$provider); if (empty($providerKey)) { abort(419); } $url = null; $token = null; $state = null; $user = null; $scopes = []; $with = []; if (auth('sanctum')->check()) { $user = auth('sanctum')->user(); } if ($user) { $token = $user->createToken($provider.'-user-token')->plainTextToken; $state = Crypt::encrypt($token); } else { $state = Crypt::encrypt(config('app.key')); } $with = ['state' => $state]; // https://developers.facebook.com/docs/instagram-basic-display-api/guides/getting-access-tokens-and-permissions/ if ($provider == 'instagram') { $scopes = ['user_profile']; $with += ['response_type' => 'code']; } // https://docs.snap.com/snap-kit/login-kit/Tutorials/web#understand-scopes if ($provider == 'snapchat') { $scopes = [ 'https://auth.snapchat.com/oauth2/api/user.display_name', 'https://auth.snapchat.com/oauth2/api/user.external_id', 'https://auth.snapchat.com/oauth2/api/user.bitmoji.avatar', ]; $with += ['response_type' => 'code']; } if ($provider == 'tiktok') { $scopes = [ 'user.info.basic', ]; $with += ['response_type' => 'code']; } if ($provider == 'twitter') { $url = $this->twitterUserAuthenticationUrl($state); } else { $url = Socialite::driver($provider) ->stateless() ->with($with) ->scopes($scopes) ->redirect() ->getTargetUrl(); } if ($provider == 'stackexchange') { $url = $this->cacheStatePutKeyInUrl($url, $state); } return response()->json([ 'url' => $url, ]); } /** * Gets the social handle information from the provider. * * @param string $provider The provider * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function handleSocialCallback(Request $request, string $provider) { $denied = $request->denied ? $request->denied : null; if ($denied != null || $denied != '') { throw new SocialProviderDeniedException; } if ($provider == 'twitter') { $socialUser = $this->twitterUserAuthentication($request); $state = $request->state ? Crypt::decrypt(Cache::pull($request->state)) : null; } elseif ($provider == 'stackexchange') { $socialUser = Socialite::driver($provider)->stateless()->user(); $state = $request->state ? Crypt::decrypt(Cache::pull($request->state)) : null; } else { $socialUser = Socialite::driver($provider)->stateless()->user(); $state = $request->state ? Crypt::decrypt($request->state) : null; } $userData = $this->findOrCreateUser($provider, $socialUser, $state); $user = $userData['user']; $token = $userData['token']; if ($user && $token) { auth()->login($user); } else { $token = 'cannot_add'; } return view('socialite/callback', [ 'token' => $token, 'token_type' => 'bearer', ]); } /** * Get Twitter Oauth1.0 Url built with identifier if user is present. * * @param \Illuminate\Http\Request $request * @return array */ public function twitterUserAuthenticationUrl($state = null) { $consumerKey = config('services.twitter.client_id'); $consumerSecret = config('services.twitter.client_secret'); $consumerRedirect = config('services.twitter.redirect'); $connection = new TwitterOAuth($consumerKey, $consumerSecret); $tempId = $this->generateTempId(); $requestToken = $connection->oauth('oauth/request_token', [ 'oauth_callback' => $consumerRedirect.'?state='.$tempId, ]); $this->tempStoreStateInCache($tempId, $state); $url = $connection->url('oauth/authorize', [ 'oauth_token' => $requestToken['oauth_token'], ]); return $url; } /** * Get Twitter user credentials from Oauth1.0. * * @param \Illuminate\Http\Request $request * @return array */ public function twitterUserAuthentication(Request $request) { $consumerKey = config('services.twitter.client_id'); $consumerSecret = config('services.twitter.client_secret'); $consumerRedirect = config('services.twitter.redirect'); $connection = new TwitterOAuth($consumerKey, $consumerSecret, $request->oauth_token); $access_token = $connection->oauth('oauth/access_token', [ 'oauth_verifier' => $request->oauth_verifier, 'oauth_token' => $request->oauth_token, ]); $connection = new TwitterOAuth($consumerKey, $consumerSecret, $access_token['oauth_token'], $access_token['oauth_token_secret']); return $connection->get('account/verify_credentials', [ 'include_email' => true, 'skip_status' => true, 'include_entities' => false, ]); } /** * Revoke a social media login provider for * a user from the app and from the provider. * * @param \App\Models\SocialiteProvider $provider * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function revokeSocialProvider(SocialiteProvider $provider, Request $request) { $user = auth('sanctum')->user(); if ($provider->user_id != $user->id) { abort(403); } $provider->delete(); return response()->json([ 'status' => 'success', 'provider' => $provider, 'user' => $user, ]); } } ================================================ FILE: app/Http/Controllers/Auth/VerifyEmailController.php ================================================ user()->hasVerifiedEmail()) { return redirect()->intended(route('admin.dashboard', absolute: false).'?verified=1'); } if ($request->user()->markEmailAsVerified()) { event(new Verified($request->user())); } return redirect()->intended(route('admin.dashboard', absolute: false).'?verified=1'); } } ================================================ FILE: app/Http/Controllers/Controller.php ================================================ loadCount(['lessons', 'exams', 'sections', 'students']); $course->load(['subjects', 'topics', 'teachers']); return Inertia::render('Course/Course', [ 'course' => new CourseResource($course) ]); } public function subscribe(Request $request, Course $course) { $user = $request->user(); $enrolled = $user->enrolledCourses->firstWhere('id', $course->id); if($enrolled) { return response()->json([],200); } $user->enrolledCourses()->attach($course); $lessons = $course->lessons->pluck('id'); $user->enrolledLessons()->attach($lessons); return response()->json([],200); } } ================================================ FILE: app/Http/Controllers/DownloadController.php ================================================ request = $request; $this->file = $file; $this->downloadResponse = $downloadResponse; } public function download(Request $request) { $ids = $request->get('ids'); if (sizeof($ids) > 1) { return $this->downloadResponse->multipleDownload($ids); } else { return $this->downloadResponse->singleDownload($ids[0]); } } } ================================================ FILE: app/Http/Controllers/HomeController.php ================================================ with(['courses', 'exams']) ->whereHas('courses') ->get(); $featuredCourses = Course::with(['teachers', 'subjects']) ->withCount(['lessons', 'students']) ->latest() ->limit(8) ->get(); return Inertia::render('Home/Home', [ 'subjects' => SubjectResource::collection($subjects), 'featuredCourses' => CourseResource::collection($featuredCourses), ]); } } ================================================ FILE: app/Http/Controllers/InstructorController.php ================================================ user) { $user = User::where('name', $request->user)->first(); }else { $user = $request->user(); } if(!$user) { return to_route('home'); } return Inertia::render('Instructor/Courses', [ 'courses' => GetInstratorCousesAction::getCourses($user), 'canModify' => $request->user()->id == $user->id ]); } } ================================================ FILE: app/Http/Controllers/LearningController.php ================================================ user(); return Inertia::render('Learning/Courses', [ 'courses' => GetEnrolledCousesAction::getCourses($user), 'canModify' => $request->user()->id == $user->id ]); } public function startCourse(Course $course) { $last = DB::table('course_students')->where('course_id', $course->id)->first()->last_lesson; if (!$last) { $course = CourseWithSections::getCourses($course); $firstResource = $course->sections?->first()?->resources?->first(); if ($firstResource) { if (is_a($firstResource, Lesson::class)) { $type = 'lesson'; $slug = $firstResource->slug; } else { $type = 'exam'; $slug = $firstResource->slug; } } return to_route('start.resource', [$course->slug, $type, $slug]); } } public function singleResource(Course $course, $type, $slug) { if ($type == 'lesson') { $resource = new LessonResource(Lesson::where('slug', $slug)->first()) ; } else { $resource = new ExamResource(Exam::where('slug', $slug)->first()) ; } return Inertia::render('Learning/SingleResource', [ 'course' => CourseWithSections::getCourses($course), 'resource' => $resource ]); } } ================================================ FILE: app/Http/Controllers/ProfileController.php ================================================ $request->user() instanceof MustVerifyEmail, 'status' => session('status'), ]); } /** * Update the user's profile information. */ public function update(ProfileUpdateRequest $request): RedirectResponse { $request->user()->fill($request->validated()); if ($request->user()->isDirty('email')) { $request->user()->email_verified_at = null; } $request->user()->save(); return Redirect::route('profile.edit'); } /** * Delete the user's account. */ public function destroy(Request $request): RedirectResponse { $request->validate([ 'password' => ['required', 'current_password'], ]); $user = $request->user(); Auth::logout(); $user->delete(); $request->session()->invalidate(); $request->session()->regenerateToken(); return Redirect::to('/'); } } ================================================ FILE: app/Http/Controllers/UploadController.php ================================================ first(); return $fileResponse->create($entry); } } ================================================ FILE: app/Http/Middleware/HandleInertiaRequests.php ================================================ */ public function share(Request $request): array { return [ ...parent::share($request), 'auth' => [ 'user' => $request->user(), ], 'appEnv' => app()->environment(), 'flash' => [ 'success' => $request->session()->get('success'), 'error' => $request->session()->get('error'), ], ]; } } ================================================ FILE: app/Http/Requests/Admin/StoreCoursesRequest.php ================================================ 'exists:users,id', 'title' => 'required', 'start_date' => 'date_format:'.config('app.date_format'), ]; } } ================================================ FILE: app/Http/Requests/Admin/UpdateCoursesRequest.php ================================================ 'exists:users,id', 'title' => 'required', 'start_date' => 'date_format:'.config('app.date_format'), ]; } } ================================================ FILE: app/Http/Requests/Admin/UserRequest.php ================================================ |string> */ public function rules(): array { return [ // ]; } } ================================================ FILE: app/Http/Requests/Auth/LoginRequest.php ================================================ */ public function rules(): array { return [ 'email' => ['required', 'string', 'email'], 'password' => ['required', 'string'], ]; } /** * Attempt to authenticate the request's credentials. * * @throws \Illuminate\Validation\ValidationException */ public function authenticate(): void { $this->ensureIsNotRateLimited(); if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) { RateLimiter::hit($this->throttleKey()); throw ValidationException::withMessages([ 'email' => trans('auth.failed'), ]); } RateLimiter::clear($this->throttleKey()); } /** * Ensure the login request is not rate limited. * * @throws \Illuminate\Validation\ValidationException */ public function ensureIsNotRateLimited(): void { if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) { return; } event(new Lockout($this)); $seconds = RateLimiter::availableIn($this->throttleKey()); throw ValidationException::withMessages([ 'email' => trans('auth.throttle', [ 'seconds' => $seconds, 'minutes' => ceil($seconds / 60), ]), ]); } /** * Get the rate limiting throttle key for the request. */ public function throttleKey(): string { return Str::transliterate(Str::lower($this->string('email')).'|'.$this->ip()); } } ================================================ FILE: app/Http/Requests/ExamRequest.php ================================================ 'required|min:5|max:255' ]; } } ================================================ FILE: app/Http/Requests/ProfileUpdateRequest.php ================================================ */ public function rules(): array { return [ 'name' => ['required', 'string', 'max:255'], 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)], ]; } } ================================================ FILE: app/Http/Requests/QuestionRequest.php ================================================ 'required', ]; } } ================================================ FILE: app/Http/Requests/SettingRequest.php ================================================ 'required', 'value' => 'required', ]; } } ================================================ FILE: app/Http/Requests/SubjectRequest.php ================================================ 'required' ]; } } ================================================ FILE: app/Http/Requests/TopicRequest.php ================================================ 'required' ]; } } ================================================ FILE: app/Http/Resources/CourseResource.php ================================================ */ public function toArray(Request $request): array { return [ 'id' => $this->id, 'title' => $this->title, 'subtitle' => $this->subtitle, 'slug' => $this->slug, 'description' => $this->description, 'requirements' => $this->requirements, 'price' => $this->price, 'discount' => $this->discount, 'status' => $this->status, 'thumbnail' => $this->thumbnail, 'start_date' => $this->start_date, 'features' => $this->features, 'permalink' => $this->permalink, 'rating' => $this->rating, 'certified' => $this->certified, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, 'teachers' => UserResource::collection($this->whenLoaded('teachers')), 'students' => UserResource::collection($this->whenLoaded('students')), 'sections' => SectionResource::collection($this->whenLoaded('sections')), 'lessons' => LessonResource::collection($this->whenLoaded('lessons')), 'exams' => ExamResource::collection($this->whenLoaded('exams')), 'topics' => TopicResource::collection($this->whenLoaded('topics')), 'subjects' => SubjectResource::collection($this->whenLoaded('subjects')), 'lessons_count' => $this->whenCounted('lessons'), 'students_count' => $this->whenCounted('students'), 'sections_count' => $this->whenCounted('sections'), 'exams_count' => $this->whenCounted('exams'), ]; } } ================================================ FILE: app/Http/Resources/CourseStudentsResource.php ================================================ */ public function toArray(Request $request): array { return [ 'id' => $this->id, 'name' => $this->name, 'full_name' => $this->full_name, 'avatar' => $this->avatar, 'rating' => $this->pivot->rating, 'progress' => $this->pivot->progress ]; } } ================================================ FILE: app/Http/Resources/ExamResource.php ================================================ */ public function toArray(Request $request): array { return [ 'id' => $this->id, 'title' => $this->title, 'description' => $this->description, 'permalink' => $this->permalink, 'status' => $this->status, 'price' => $this->price, 'duration' => $this->duration, 'pass_mark' => $this->pass_mark, 'meta' => $this->meta, 'number_of_questions' => $this->number_of_questions, 'random_questions' => (boolean) $this->random_questions, 'certification' => (boolean) $this->certification, 'difficulty' => $this->difficulty, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, 'subjects' => SubjectResource::collection($this->whenLoaded('subjects')), 'courses' => CourseResource::collection($this->whenLoaded('courses')), 'questions' => QuestionResource::collection($this->whenLoaded('questions')), 'examiner' => new UserResource($this->whenLoaded('examiner')), 'topics' => TopicResource::collection($this->whenLoaded('topics')), 'results' => ResultResource::collection($this->whenLoaded('results')), 'pivot' => $this->when($this->pivot, [ 'sectionable_type' => $this->pivot->sectionable_type, 'order' => $this->pivot->order ]) ]; } } ================================================ FILE: app/Http/Resources/FileResource.php ================================================ */ public function toArray(Request $request): array { return [ 'id' => $this->id, 'name' => $this->name, 'description' => $this->description, 'path' => $this->path, 'file_name' => $this->file_name, 'extension' => $this->extension, 'mime' => $this->mime, 'type' => $this->type, 'public_path' => $this->public_path, 'parent_id' => $this->parent_id, 'uploaded_by' => $this->uploaded_by, ]; } } ================================================ FILE: app/Http/Resources/LessonResource.php ================================================ */ public function toArray(Request $request): array { $lesson = [ 'id' => $this->id, 'title' => $this->title, 'slug' => $this->slug, 'thumbnail' => $this->thumbnail, 'type' => $this->type, 'object' => $this->object, 'short_text' => $this->short_text, 'full_text' => $this->full_text, 'position' => $this->position, 'status' => $this->status, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, 'courses' => CourseResource::collection($this->whenLoaded('courses')), 'students' => UserResource::collection($this->whenLoaded('students')), 'sections' => SectionResource::collection($this->whenLoaded('sections')), ]; if ($this->pivot) { $lesson['pivot'] = [ 'sectionable_type' => $this->pivot->sectionable_type, 'order' => $this->pivot->order ]; } return $lesson; } } ================================================ FILE: app/Http/Resources/QuestionResource.php ================================================ */ public function toArray(Request $request): array { return [ 'id' => $this->id, 'qtype' => $this->qtype, 'question' => $this->question, 'options' => $this->options, 'answers' => $this->answers, 'hint' => $this->hint, 'mark' => $this->mark, 'nmark' => $this->nmark, 'explanation' => $this->explanation, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, 'topics' => TopicResource::collection($this->whenLoaded('topics')), 'exam' => new ExamResource($this->whenLoaded('exam')), ]; } } ================================================ FILE: app/Http/Resources/ResultResource.php ================================================ */ public function toArray(Request $request): array { return [ 'id' => $this->id, 'answers' => $this->answers, 'obtain_mark' => $this->obtain_mark, 'is_pass' => $this->is_pass, 'time_taken' => $this->time_taken, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, 'examinee' => new UserResource($this->whenLoaded('examinee')), 'exam' => new ExamResource($this->whenLoaded('exam')), ]; } } ================================================ FILE: app/Http/Resources/RoleResource.php ================================================ */ public function toArray(Request $request): array { return [ 'id' => $this->id, 'name' => $this->name, 'guard_name' => $this->guard_name, 'permissions' => $this->permissions->toArray(), 'users_count' => $this->users_count ?? 0, 'permissions_count' => $this->permissions_count ?? 0, ]; } } ================================================ FILE: app/Http/Resources/SectionResource.php ================================================ */ public function toArray(Request $request): array { return [ 'id' => $this->id, 'title' => $this->title, 'description' => $this->description, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, 'lessons' => LessonResource::collection($this->whenLoaded('lessons')), 'exams' => ExamResource::collection($this->whenLoaded('exams')), 'resources' => $this->transform($this->resources, function($resources){ return $resources->map(function($resource) { if (is_a($resource, Lesson::class)) { return new LessonResource($resource); } else { return new ExamResource($resource); } }); }) ]; } } ================================================ FILE: app/Http/Resources/SubjectResource.php ================================================ */ public function toArray(Request $request): array { return [ 'id' => $this->id, 'title' => $this->title, 'slug' => $this->slug, 'description' => $this->description, 'icon' => $this->icon, 'image' => $this->image, 'image_url' => $this->image_url, 'parent' => $this->parent, 'children_count' => $this->resource->children_count ?? 0, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, 'courses_count' => $this->when($this->courses_count, $this->courses_count), 'exams_count' => $this->when($this->exams_count, $this->exams_count), 'courses' => CourseResource::collection($this->whenLoaded('courses')), 'exams' => CourseResource::collection($this->whenLoaded('exams')), 'children' => SubjectResource::collection($this->whenLoaded('children')), ]; } } ================================================ FILE: app/Http/Resources/TopicResource.php ================================================ */ public function toArray(Request $request): array { return [ 'id' => $this->id, 'title' => $this->title, 'description' => $this->description, 'created_at' => $this->created_at, 'updated_at' => $this->updated_at, 'courses_count' => $this->when($this->courses_count, $this->courses_count), 'exams_count' => $this->when($this->exams_count, $this->exams_count), 'courses' => CourseResource::collection($this->whenLoaded('courses')), 'exams' => CourseResource::collection($this->whenLoaded('exams')), ]; } } ================================================ FILE: app/Http/Resources/UserResource.php ================================================ */ public function toArray(Request $request): array { return [ 'id' => $this->id, 'name' => $this->name, 'firstname' => $this->firstname, 'lastname' => $this->lastname, 'full_name' => $this->full_name, 'email' => $this->email, 'avatar' => $this->avatar, 'email_verified_at' => $this->email_verified_at?->format('Y-m-d H:i:s'), 'created_at' => $this->created_at->format('Y-m-d H:i:s'), 'updated_at' => $this->updated_at->format('Y-m-d H:i:s'), 'deleted_at' => $this->deleted_at?->format('Y-m-d H:i:s'), 'roles' => RoleResource::collection($this->whenLoaded('roles')), 'results' => ResultResource::collection($this->whenLoaded('results')), 'instructCourses' => CourseResource::collection($this->whenLoaded('instructCourses')), 'enrolledCourses' => CourseResource::collection($this->whenLoaded('enrolledCourses')), ]; } } ================================================ FILE: app/Jobs/ResizedImage.php ================================================ file = $file; } /** * Execute the job. */ public function handle() { // we cant anything without image if ($this->file->type !== 'image') { return; } // get sizes of images $meta = $this->file->meta; $sizes = $meta['sizes']; // there are not any sizes; if (!is_array($sizes)) { return; } /* * is file in cloude then, * Download from cloud to local then resize it. */ if ($this->file->driver == config('filesystems.cloud')) { if (!Storage::disk(config('filesystems.cloud'))->exists($this->file->getStoragePath())) { throw new Exception(); } $s3file = Storage::disk(config('filesystems.cloud'))->get($this->file->getStoragePath()); Storage::disk('local')->put("{$this->file->file_name}/{$this->file->name}", $s3file); } $path = storage_path("app/uploads/{$this->file->file_name}"); $file = new FileAdapdar("{$path}/{$this->file->name}"); $public = 'public'; foreach ($sizes as $key => $value) { // already we resized this size if ($value == true) { continue; } // Image not resized let resize it $rs = explode('x', $key); // [150, 50] // create Intervention images $image = Image::make($file)->resize($rs[0], $rs[1]); $localFile = "{$path}/{$key}-{$this->file->name}"; $image->save($localFile); if ($this->file->driver == config('filesystems.cloud')) { Storage::disk(config('filesystems.cloud'))->putFileAs($this->file->file_name, new FileAdapdar($localFile), "{$key}-{$this->file->name}", $public); } $sizes[$key] = true; } // save meta values; $meta['sizes'] = $sizes; $this->file->meta = $meta; if ($this->file->isDirty()) { $this->file->save(); } if ($this->file->driver == config('filesystems.cloud')) { Storage::deleteDirectory($path); } } } ================================================ FILE: app/Jobs/UploadToCloud.php ================================================ file = $file; } /** * Execute the job. */ public function handle() { $disk = config('filesystems.cloud'); // is already uploaded if ($this->file->driver == $disk) { return; } $path = storage_path('app/uploads/'); $public = 'public'; $localfiles = Storage::disk('local')->allFiles($this->file->file_name); foreach ($localfiles as $lfile) { Storage::disk($disk)->putFileAs($this->file->file_name, new FileAdapdar("{$path}/{$lfile}"), $this->file->name, $public); } // save to path; $this->file->updatePublicPaths($disk); // wait 5 second to delete directorr so all local request is complete and new request to cloud sleep(5); Storage::disk('local')->deleteDirectory($this->file->file_name); } } ================================================ FILE: app/Models/Course.php ================================================ $teachers * @property Collection $students * @property Collection $sections * @property Collection $lessons * @property Collection $publishedLessons * @property Collection $exams * @property Collection $topics * @property Collection $subjects */ class Course extends Model { use SoftDeletes, Fileable, HasFactory; protected $fillable = [ 'title', 'subtitle', 'slug', 'description', 'requirements', 'price', 'discount', 'thumbnail', 'start_date', 'status', 'features', 'certified', 'created_by', 'updated_by' ]; /* |-------------------------------------------------------------------------- | ACCESORS Variables |-------------------------------------------------------------------------- */ protected $with = [ 'thumbnail' ]; protected $appends = [ 'permalink', ]; protected $casts = [ 'features' => 'array' ]; /* |-------------------------------------------------------------------------- | Booting |-------------------------------------------------------------------------- */ /* |-------------------------------------------------------------------------- | ACCESORS |-------------------------------------------------------------------------- */ public function getRatingAttribute() { return number_format(DB::table('course_students')->where('course_id', $this->attributes['id'])->average('rating'), 2); } public function getPermalinkAttribute() { return route('course.show', ['course' => $this->slug]); } /** * Set attribute to money format * @param $input */ public function setPriceAttribute($input) { $this->attributes['price'] = $input ? $input : null; } /* |-------------------------------------------------------------------------- | Scopes |-------------------------------------------------------------------------- */ public function scopeOfTeacher($query) { return $query->whereHas('teachers', function ($q) { $q->where('user_id', Auth::user()->id); }); } /* |-------------------------------------------------------------------------- | RELATIONS |-------------------------------------------------------------------------- */ public function teachers() { return $this->belongsToMany(User::class, 'course_teachers'); } public function students() { return $this->belongsToMany(User::class, 'course_students')->withTimestamps()->withPivot(['rating', 'progress']); } public function sections() { return $this->hasMany(Section::class); } public function lessons() { return $this->belongsToMany(Lesson::class, 'sectionables', 'course_id', 'sectionable_id')->wherePivot('sectionable_type', Lesson::class); } public function publishedLessons() { return $this->hasMany(Lesson::class)->orderBy('position')->where('status', 1); } public function exams() { return $this->belongsToMany(Exam::class, 'sectionables', 'course_id', 'sectionable_id')->wherePivot('sectionable_type', Exam::class); } public function topics() { return $this->morphToMany(Topic::class, 'topicable'); } public function subjects() { return $this->morphToMany(Subject::class, 'subjectables'); } public function thumbnail() { return $this->belongsTo(File::class, 'thumbnail'); } /* |-------------------------------------------------------------------------- | FUNCTIONS |-------------------------------------------------------------------------- */ } ================================================ FILE: app/Models/Exam.php ================================================ $courses * @property Collection $exams * @property User $examiner */ class Exam extends Model { use SoftDeletes, HasFactory; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'title', 'description', 'examiner', 'price', 'status', 'duration', 'pass_mark', 'meta', 'number_of_questions', 'random_questions', 'certification', 'difficulty', ]; protected $casts = [ 'meta' => 'array', ]; public function subjects() { return $this->morphToMany(Subject::class, 'subjectables'); } public function courses() { return $this->belongsToMany(Course::class, 'sectionables', 'sectionable_id', 'course_id')->wherePivot('sectionable_type', Exam::class); } public function questions() { return $this->hasMany(Question::class); } public function topics() { return $this->morphToMany(Topic::class, 'topicable'); } public function results() { return $this->hasMany(Result::class, 'exam_id', 'id'); } public function examiner() { return $this->belongsTo(User::class, 'examiner'); } public static function calculateTotalMark($marks) { $count = 0; foreach ($marks as $mark) { $count = $count + $mark->mark; } return $count; } public function sectionable() { return $this->morphOne(Sectionable::class, 'sectionable'); } } ================================================ FILE: app/Models/File.php ================================================ $exams */ class File extends Model { use HashesId, FileStorage, SoftDeletes; protected $fillable = [ 'name', 'description', 'path', 'file_name', 'extension', 'mime', 'type', 'public_path', 'parent_id', 'driver', 'driver_data', 'uploaded_by', 'meta', ]; protected $casts = [ 'id' => 'integer', 'file_size' => 'integer', 'user_id' => 'integer', 'parent_id' => 'integer', 'meta' => 'array', 'permissions' => 'array', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'meta', 'file_name' ]; /** * the accessor that should append with response. * * @var array */ protected $appends = [ 'sizes', 'hash' ]; /* |-------------------------------------------------------------------------- | Booting |-------------------------------------------------------------------------- */ public static function boot() { parent::boot(); File::observe(FileObserver::class); } /* |-------------------------------------------------------------------------- | ACCESORS Variables |-------------------------------------------------------------------------- */ /* |-------------------------------------------------------------------------- | ACCESORS & Mutation |-------------------------------------------------------------------------- */ public function getSizesAttribute() { if ($this->type == 'image') { return $this->getImageSizes(); } return []; } /* |-------------------------------------------------------------------------- | Scopes |-------------------------------------------------------------------------- */ /* |-------------------------------------------------------------------------- | RELATIONS |-------------------------------------------------------------------------- */ /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function children() { return $this->hasMany(static::class, 'folder_id'); } /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function parent() { return $this->belongsTo(static::class, 'folder_id'); } public function uploader() { return $this->hasOne(User::class, 'id', 'uploaded_by'); } /* |-------------------------------------------------------------------------- | FUNCTIONS |-------------------------------------------------------------------------- */ public function setPermission($permission, $value) { $meta = $this->meta; $permissions = $meta['permissions']; $permissions[$permission] = $value; $meta['permissions'] = $permissions; $this->meta = $meta; return $this; } public function getPermission($permission = null) { $permissions = $this->meta['permissions']; if ($permission !== null) { if (array_key_exists($permission, $permissions)) { return $permissions[$permission]; } else { return false; } } return $permissions; } public function hasPermission($permission) { return $this->getPermission($permission) === true; } public function setImageSize($size) { $sizes = $this->meta['sizes']; $sizes[$size] = false; $this->meta['sizes'] = $sizes; return $this; } public function getImageSizes() { return $this->meta['sizes']; } public function updatePublicPaths($driver = null) { if ($this->driver !== $driver) { $this->driver = $driver; } if ($this->hasPermission('public')) { $this->public_path = Storage::disk($this->driver)->url($this->getStoragePath()); } else { $this->public_path = Storage::disk('local')->url($this->getStoragePath()); } $this->save(); } public function getStoragePath($prefix = null) { return "$this->file_name/$this->name"; } /** * set the image resize attribute;. * * @param array $sizes */ public function setImageSizes(array $sizes) { $meta = $this->meta; $exiting = $meta['sizes']; $hasnew = false; foreach ($sizes as $size) { if (is_array($size) && is_array($exiting) && array_key_exists(implode('x', $size), $exiting)) { continue; } $exiting[implode('x', $size)] = false; $hasnew = true; } if ($hasnew) { $meta['sizes'] = $exiting; $this->meta = $meta; $this->save(); ResizedImage::dispatch($this); } return $this; } } ================================================ FILE: app/Models/Lesson.php ================================================ $courses * @property Collection $students * @property Collection
$sections * @property Sectionable $sectionable * @property File $objectFile */ class Lesson extends Model { use Topicable, Fileable, SoftDeletes, HasFactory; protected $fillable = [ 'title', 'slug', 'thumbnail', 'short_text', 'full_text', 'position', 'type', 'object', 'status', 'created_by', 'updated_by' ]; /* |-------------------------------------------------------------------------- | ACCESORS Variables |-------------------------------------------------------------------------- */ // protected $casts = [ // 'object' => 'array' // ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = ['object', 'full_text']; public static $types = [ 'text' => 1, 'video' => 2, 'audio' => 3, 'pdf' => 4 ]; public static $statuses = [ 'free' => 1, 'subscriber' => 2, 'paid' => 3, ]; /* |-------------------------------------------------------------------------- | ACCESORS |-------------------------------------------------------------------------- */ public function getTypeAttribute($value) { $key = array_search($value, self::$types); if ($key) { return ucfirst($key); } } public function setTypeAttribute($value) { $value = strtolower($value); $key = array_search($value, self::$types); if ($key) { $this->attributes['type'] = $value; } else { $this->attributes['type'] = self::$types[$value]; } } public function getStatusAttribute($value) { $key = array_search($value, self::$statuses); if ($key) { return ucfirst($key); } } public function setStatusAttribute($value) { $value = strtolower($value); $key = array_search($value, self::$statuses); if ($key) { $this->attributes['status'] = $value; } else { $this->attributes['status'] = self::$statuses[$value]; } } public function getObjectAttribute($value) { return $this->files->where('id', $value)->first(); } /* |-------------------------------------------------------------------------- | Scopes |-------------------------------------------------------------------------- */ public function scopePublished($query) { return $query->where('status', 1); } /* |-------------------------------------------------------------------------- | RELATIONS |-------------------------------------------------------------------------- */ public function courses() { return $this->belongsToMany(Course::class, 'sectionables', 'sectionable_id', 'course_id')->wherePivot('sectionable_type', Lesson::class); } public function students() { return $this->belongsToMany(User::class, 'lesson_student')->withTimestamps(); } public function sectionable() { return $this->morphOne(Sectionable::class, 'sectionable'); } public function sections() { return $this->morphToMany(Section::class, 'sectionable'); } public function objectFile() { return $this->belongsTo(File::class, 'object'); } /* |-------------------------------------------------------------------------- | OVERWRITE FUNCTIONS |-------------------------------------------------------------------------- */ /* |-------------------------------------------------------------------------- | FUNCTIONS |-------------------------------------------------------------------------- */ public function setLessonObject(File $file = null) { if ($file && $file->id !== $this->object) { $this->object = $file->id; $this->save(); } else { $file = File::find($this->object); } if (!$file) { return; } $this->files()->sync($file); $file->setPermission('public', false)->setPermission('lesson', true)->save(); } } ================================================ FILE: app/Models/Question.php ================================================ $topics * @property Exam $exam */ class Question extends Model { use SoftDeletes, HasFactory; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'qtype', 'question', 'options', 'answers', 'hint', 'mark', 'nmark', 'explanation', 'created_by' ]; protected $casts = [ 'answers' => 'array', 'options' => 'array' ]; /* |-------------------------------------------------------------------------- | ACCESORS Variables |-------------------------------------------------------------------------- */ public static $qtypes = [ 'Objective' => 0, 'TrueFalse' => 1, ]; /* |-------------------------------------------------------------------------- | ACCESORS |-------------------------------------------------------------------------- */ public function getQtypeAttribute($value) { $key = array_search($value, self::$qtypes); if ($key) { return ucfirst($key); } } public function setQtypeAttribute($value) { $key = array_search($value, self::$qtypes); if ($key) { $this->attributes['qtype'] = $value; } else { $this->attributes['qtype'] = self::$qtypes[$value]; } } public function topics() { return $this->morphToMany(Topic::class, 'topicable'); } public function exam() { return $this->belongsTo(Exam::class); } public function setAnswerAttribute($value) { $this->attributes['answers'] = json_encode($value); } } ================================================ FILE: app/Models/Result.php ================================================ $courses * @property Exam $exam * @property User $examinee */ class Result extends Model { use HasFactory; protected $fillable = ['answers', 'obtain']; protected $casts = [ 'answers' => 'array', 'is_pass' => 'bool' ]; public function exam() { return $this->belongsTo(Exam::class, 'exam_id'); } public function examinee() { return $this->belongsTo(User::class, 'examinee'); } public static function calculateMark($answer) { $count = 0; if (!empty($answer)) { foreach ($answer as $resultKey => $resultValue) { $question = Question::where('id', $resultKey)->first(); if (!empty($question)) { foreach ($question->answers as $key => $value) { if ($resultValue == $value) { $count = $count + $question->mark; } } } } } return $count; } } ================================================ FILE: app/Models/Role.php ================================================ appRoles()->include($this->name); } } ================================================ FILE: app/Models/Section.php ================================================ $sectionables * @property Collection $lessons * @property Exam $exams */ class Section extends Model { use HasFactory; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'title', 'description', 'course_id' ]; public function sectionables() { return $this->hasMany(Sectionable::class, 'section_id')->orderBy('order', 'asc'); } public function lessons() { return $this->morphedByMany(Lesson::class, 'sectionable')->orderBy('order', 'asc')->withPivot(['order']); } public function exams() { return $this->morphedByMany(Exam::class, 'sectionable')->orderBy('order', 'asc')->withPivot(['order']); } } ================================================ FILE: app/Models/Sectionable.php ================================================ $sectionable * @property Section $section */ class Sectionable extends Model { use HasFactory; /** * The attributes that are mass assignable. * * @var array */ protected $guarded = []; public function sectionable() { return $this->morphTo(); } public function section() { return $this->belongsTo(Section::class, 'section_id'); } } ================================================ FILE: app/Models/Setting.php ================================================ */ protected $guarded = [ 'id', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'access_token', 'refresh_token', ]; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'user_id', 'provider', 'provider_user_id', 'access_token', 'refresh_token', 'avatar', ]; /** * The attributes that should be cast to native types. * * @var array */ protected $casts = [ 'id' => 'integer', 'user_id' => 'integer', 'provider' => 'string', 'provider_user_id' => 'string', 'access_token' => 'string', 'refresh_token' => 'string', 'avatar' => 'string', 'created_at' => 'datetime', 'updated_at' => 'datetime', ]; /** * Get the user that owns the socialite provider. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function user() { return $this->belongsTo(User::class); } } ================================================ FILE: app/Models/Subject.php ================================================ $courses * @property Collection $exams */ class Subject extends Model { use HasFactory; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'title', 'slug', 'description', 'icon', 'image' ]; protected $appends = ['image_url']; public function getImageUrlAttribute(): ?string { if (! $this->image) { return null; } return Storage::disk('public')->url($this->image); } /** * Get all of the courses that are assigned this tag. */ public function courses() { return $this->morphedByMany(Course::class, 'subjectables'); } /** * Get all of the exams that are assigned this tag. */ public function exams() { return $this->morphedByMany(Exam::class, 'subjectables'); } /** * Get child subjects. */ public function children() { return $this->hasMany(Subject::class, 'parent'); } } ================================================ FILE: app/Models/Topic.php ================================================ $courses * @property Collection $exams */ class Topic extends Model { use HasFactory; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'title', 'description', ]; /** * Get all of the courses that are assigned this tag. */ public function courses() { return $this->morphedByMany(Course::class, 'topicable'); } /** * Get all of the exams that are assigned this tag. */ public function exams() { return $this->morphedByMany(Exam::class, 'topicable'); } } ================================================ FILE: app/Models/Traits/FileStorage.php ================================================ hash; } Storage::disk($disk)->putFileAs($this->file_name, $file, $file_name, $public); $entry->updatePublicPaths($disk); } /** * @param File $entry * @param UploadedFile $contents */ public function storePublicUpload(UploadedFile $file, $file_name = null) { $this->moveFile($file, 'public', 'public', $file_name); } /** * @param File $entry * @param UploadedFile $contents */ public function storeLocalUpload(UploadedFile $file, $file_name = null) { $this->moveFile($file, 'public', 'local', $file_name); } } ================================================ FILE: app/Models/Traits/Fileable.php ================================================ morphToMany(File::class, 'fileable'); } } ================================================ FILE: app/Models/Traits/HashesId.php ================================================ getOriginal('id').'|', 10, 'padding')), '='); } public function scopeWhereHash(Builder $query, $value) { $id = $this->decodeHash($value); return $query->where('id', $id); } public function decodeHash($hash) { if ((int) $hash !== 0) { return $hash; } return (int) explode('|', base64_decode($hash))[0]; } } ================================================ FILE: app/Models/Traits/Topicable.php ================================================ morphToMany(Topic::class, 'topicable'); } } ================================================ FILE: app/Models/User.php ================================================ $roles * @property Collection $results * @property Collection $instructCourses * @property Collection $enrolledCourses * @property Collection $enrolledLessons */ class User extends Authenticatable { use Notifiable, HasRoles, SoftDeletes, HasApiTokens, HasFactory, Notifiable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'firstname', 'lastname', 'name', 'email', 'password', 'avatar' ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; /** * The attributes that should be append for arrays. * * @var array */ protected $appends = [ 'full_name' ]; /** * Get the attributes that should be cast. * * @return array */ protected function casts(): array { return [ 'email_verified_at' => 'datetime:Y-m-d', 'password' => 'hashed', 'created_at' => 'datetime:Y-m-d', 'updated_at' => 'datetime:Y-m-d', 'deleted_at' => 'datetime:Y-m-d h:i:s' ]; } /** --------------------Attributes--------------------------- */ public function getFullNameAttribute() { return "$this->firstname $this->lastname"; } public function getAvatarAttribute($value) { if (!is_array($value)) { $value = json_decode($value); } if (empty($value) && empty($value->avatar)) { $value = new \STDClass(); $value->avatar = $this->get_gravatar($this->email, 200); } return $value; } /** --------------------Relations--------------------------- */ public function results() { return $this->hasMany(Result::class, 'examinee'); } public function instructCourses() { return $this->belongsToMany(Course::class, 'course_teachers'); } public function enrolledCourses() { return $this->belongsToMany(Course::class, 'course_students')->using(StudentCasting::class)->withPivot(['rating', 'progress', 'created_at', 'updated_at']); } public function enrolledLessons() { return $this->belongsToMany(Lesson::class, 'lesson_student')->using(StudentCasting::class)->withPivot(['status', 'created_at', 'updated_at']); } /** --------------------Methods--------------------------- */ protected function get_gravatar($email, $s = 40, $d = 'mp', $r = 'g', $img = false, $atts = array()) { $url = 'https://www.gravatar.com/avatar/'; $url .= md5(strtolower(trim($email))); $url .= "?s=$s&d=$d&r=$r"; if ($img) { $url = ' $val) { $url .= ' ' . $key . '="' . $val . '"'; } $url .= ' />'; } return $url; } public function isSuperAdmin(): bool { return $this->hasRole(Role::SUPERADMIN); } public function isAdmin(): bool { return $this->hasAnyRole(Role::SUPERADMIN, Role::ADMIN); } } ================================================ FILE: app/Observers/FileObserver.php ================================================ uplaoded_by = $id; } /** * Handle the file "updated" event. * * @param App\Models\File $file */ public function updated(File $file) { } /** * Handle the File "deleted" event. * * @param App\Models\File $file */ public function deleting(File $file) { $id = Auth::id(); $file->deleted_by = $id; } /** * Handle the file "deleted" event. * * @param App\Models\File $file */ public function deleted(File $file) { } /** * Handle the file "restored" event. * * @param App\Models\File $file */ public function restored(File $file) { } /** * Handle the file "force deleted" event. * * @param App\Models\File $file */ public function forceDeleted(File $file) { } } ================================================ FILE: app/Pivots/StudentCasting.php ================================================ isAdmin()) { return true; } } /** * Determine whether the user can view any models. */ public function viewAny(User $user): bool { return false; } /** * Determine whether the user can view the model. */ public function view(User $user, Course $model): bool { return false; } /** * Determine whether the user can create models. */ public function create(User $user): bool { return false; } /** * Determine whether the user can update the model. */ public function update(User $user, Course $model): bool { return false; } /** * Determine whether the user can delete the model. */ public function delete(User $user, Course $model): bool { return false; } /** * Determine whether the user can restore the model. */ public function restore(User $user, Course $model): bool { return false; } /** * Determine whether the user can permanently delete the model. */ public function forceDelete(User $user, Course $model): bool { return false; } } ================================================ FILE: app/Policies/ExamPolicy.php ================================================ isAdmin()) { return true; } } /** * Determine whether the user can view any models. */ public function viewAny(User $user): bool { return false; } /** * Determine whether the user can view the model. */ public function view(User $user, Exam $model): bool { return false; } /** * Determine whether the user can create models. */ public function create(User $user): bool { return false; } /** * Determine whether the user can update the model. */ public function update(User $user, Exam $model): bool { return false; } /** * Determine whether the user can delete the model. */ public function delete(User $user, Exam $model): bool { return false; } /** * Determine whether the user can restore the model. */ public function restore(User $user, Exam $model): bool { return false; } /** * Determine whether the user can permanently delete the model. */ public function forceDelete(User $user, Exam $model): bool { return false; } } ================================================ FILE: app/Policies/RolePolicy.php ================================================ isAdmin()) { return true; } } /** * Determine whether the user can view any models. */ public function viewAny(User $user): bool { return false; } /** * Determine whether the user can view the model. */ public function view(User $user, Role $model): bool { return false; } /** * Determine whether the user can create models. */ public function create(User $user): bool { return false; } /** * Determine whether the user can update the model. */ public function update(User $user, Role $model): bool { return false; } /** * Determine whether the user can delete the model. */ public function delete(User $user, Role $model): bool { return false; } /** * Determine whether the user can restore the model. */ public function restore(User $user, Role $model): bool { return false; } /** * Determine whether the user can permanently delete the model. */ public function forceDelete(User $user, Role $model): bool { return false; } } ================================================ FILE: app/Policies/SubjectPolicy.php ================================================ isAdmin()) { return true; } } /** * Determine whether the user can view any models. */ public function viewAny(User $user): bool { return false; } /** * Determine whether the user can view the model. */ public function view(User $user, Subject $model): bool { return false; } /** * Determine whether the user can create models. */ public function create(User $user): bool { return false; } /** * Determine whether the user can update the model. */ public function update(User $user, Subject $model): bool { return false; } /** * Determine whether the user can delete the model. */ public function delete(User $user, Subject $model): bool { return false; } /** * Determine whether the user can restore the model. */ public function restore(User $user, Subject $model): bool { return false; } /** * Determine whether the user can permanently delete the model. */ public function forceDelete(User $user, Subject $model): bool { return false; } } ================================================ FILE: app/Policies/TopicPolicy.php ================================================ isAdmin()) { return true; } } /** * Determine whether the user can view any models. */ public function viewAny(User $user): bool { return false; } /** * Determine whether the user can view the model. */ public function view(User $user, Topic $model): bool { return false; } /** * Determine whether the user can create models. */ public function create(User $user): bool { return false; } /** * Determine whether the user can update the model. */ public function update(User $user, Topic $model): bool { return false; } /** * Determine whether the user can delete the model. */ public function delete(User $user, Topic $model): bool { return false; } /** * Determine whether the user can restore the model. */ public function restore(User $user, Topic $model): bool { return false; } /** * Determine whether the user can permanently delete the model. */ public function forceDelete(User $user, Topic $model): bool { return false; } } ================================================ FILE: app/Policies/UserPolicy.php ================================================ isAdmin()) { return true; } } /** * Determine whether the user can view any models. */ public function viewAny(User $user): bool { return false; } /** * Determine whether the user can view the model. */ public function view(User $user, User $model): bool { return false; } /** * Determine whether the user can create models. */ public function create(User $user): bool { return false; } /** * Determine whether the user can update the model. */ public function update(User $user, User $model): bool { return false; } /** * Determine whether the user can delete the model. */ public function delete(User $user, User $model): bool { return false; } /** * Determine whether the user can restore the model. */ public function restore(User $user, User $model): bool { return false; } /** * Determine whether the user can permanently delete the model. */ public function forceDelete(User $user, User $model): bool { return false; } } ================================================ FILE: app/Providers/AppServiceProvider.php ================================================ driver); $size = $disk->size($upload->getStoragePath()); $time = date('r', $disk->lastModified($upload->getStoragePath())); $fm = $disk->getDriver()->readStream($upload->getStoragePath()); $begin = 0; $end = $size - 1; if (isset($_SERVER['HTTP_RANGE'])) { if (preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)) { $begin = intval($matches[1]); if (!empty($matches[2])) { $end = intval($matches[2]); } } } if (isset($_SERVER['HTTP_RANGE'])) { header('HTTP/1.1 206 Partial Content'); } else { header('HTTP/1.1 200 OK'); } header("Content-Type: $upload->mime"); header('Cache-Control: public, must-revalidate, max-age=0'); header('Pragma: no-cache'); header('Accept-Ranges: bytes'); header('Content-Length:'.(($end - $begin) + 1)); if (isset($_SERVER['HTTP_RANGE'])) { header("Content-Range: bytes $begin-$end/$size"); } header("Content-Disposition: inline; filename=$upload->file_name"); header('Content-Transfer-Encoding: binary'); header("Last-Modified: $time"); $cur = $begin; fseek($fm, $begin, 0); while (!feof($fm) && $cur <= $end && (connection_status() == 0)) { echo fread($fm, min(1024 * 16, ($end - $cur) + 1)); $cur += 1024 * 16; } } } ================================================ FILE: app/Response/DownloadResponse.php ================================================ file = $file; // $this->zipper = $zipper; } /** * Return download or preview response for given file. * * @param File $upload * * @return mixed */ public function singleDownload($id) { $upload = $this->file->whereHash($id)->firstOrFail(); if ($upload->type == 'folder') { return $this->folderDownload($upload); } $headers = [ 'Content-Type' => $upload->mime, ]; $filename = Str::slug(Str::before($upload->name, '.'), '-'); $filename = "$filename.$upload->extension"; $filepath = Storage::drive('uploads_local')->path($upload->getStoragePath()); return response()->download($filepath, $filename, $headers); } public function folderDownload($upload) { $zip_name = Str::random(10); $zip_name = config('app.name').'-'.$zip_name; $this->zipper->make(storage_path("public/$zip_name.zip")); $this->fileRecussive($upload); $this->zipper->close(); $headers = [ 'Content-Type' => 'application/zip', ]; return response()->download(storage_path("public/$zip_name.zip"), "$zip_name.zip", $headers)->deleteFileAfterSend(true); } public function fileRecussive($file, $folderName = '') { if ($file->type == 'folder') { $folderName = "$folderName/$file->name"; foreach ($this->file->where('parent_id', $file->id)->cursor() as $chield) { $this->fileRecussive($chield, $folderName); } } else { $filename = Str::slug(Str::before($file->name, '.'), '-'); $filename = "$filename.$file->extension"; $this->zipper->folder($folderName)->addString($filename, Storage::drive('uploads_local')->get($file->getStoragePath())); } } public function multipleDownload($ids) { $zip_name = Str::random(10); $zip_name = config('app.name').'-'.$zip_name; $this->zipper->make(storage_path("public/$zip_name.zip")); if (is_array($ids)) { foreach ($this->file->whereIn('id', $ids)->cursor() as $chield) { $this->fileRecussive($chield); } } $this->zipper->close(); $headers = [ 'Content-Type' => 'application/zip', ]; return response()->download(storage_path("public/$zip_name.zip"), "$zip_name.zip", $headers)->deleteFileAfterSend(true); } } ================================================ FILE: app/Response/FileBuilder.php ================================================ getMimeType(); if ($originalMime === 'application/octet-stream') { $originalMime = $file->getClientMimeType(); } $file_name = Arr::get($extra, 'file_name') ? Arr::get($extra, 'file_name') : Str::random(36); $data = [ 'name' => Arr::get($extra, 'name', $file->getClientOriginalName()), 'file_name' => $file_name, 'mime' => $originalMime, // 'type' => $this->getTypeFromMime($originalMime), // 'extension' => $this->getExtension($file, $originalMime), 'path' => Arr::get($extra, 'path'), 'parent_id' => Arr::get($extra, 'parent_id'), 'public_path' => Arr::get($extra, 'public_path'), 'uploaded_by' => Arr::get($extra, 'uploaded_by', Auth::user()->id), 'driver' => Arr::get($extra, 'driver', config('filesystems.default')), 'driver_data' => Arr::get($extra, 'driver_data'), 'meta' => Arr::get($extra, 'meta'), ]; return $data; } } ================================================ FILE: app/Response/FileContentResponseCreator.php ================================================ imageResponse = $imageResponse; $this->audioVideoResponse = $audioVideoResponse; } /** * Return download or preview response for given file. * * @param File $upload * * @return mixed */ public function create(File $upload) { if (!Storage::drive($upload->driver)->exists($upload->getStoragePath())) { abort(404); } list($mime, $type) = $this->getTypeFromModel($upload); if ($type === 'image') { return $this->imageResponse->create($upload); } elseif ($this->shouldStream($mime, $type)) { return $this->audioVideoResponse->create($upload); } else { return $this->createBasicResponse($upload); } } /** * Create a basic response for specified upload content. * * @param File $upload * * @return Response */ private function createBasicResponse(File $upload) { return response(Storage::drive($upload->driver)->get($upload->getStoragePath()), 200, ['Content-Type' => $upload->mime]); } /** * Extract file type from model. * * @param File $fileModel * * @return array */ private function getTypeFromModel(File $fileModel) { $mime = $fileModel->mime; $type = explode('/', $mime)[0]; return array($mime, $type); } /** * Should file with given mime be streamed. * * @param string $mime * @param string $type * * @return bool */ private function shouldStream($mime, $type) { return $type === 'video' || $type === 'audio' || $mime === 'application/ogg'; } } ================================================ FILE: app/Response/ImageResponse.php ================================================ driver)->get($upload->getStoragePath()); return response($content, 200, ['Content-Type' => $upload->mime]); } } ================================================ FILE: app/Traits/AppSettingsTrait.php ================================================ key == 'enableSentryMonitoring') { $this->setEnv('SENTRY_IO_ENABLED', $setting->val, config('services.sentry.enabled')); putenv('SENTRY_IO_ENABLED='.$setting->val); config(['services.sentry.enabled' => $setting->val]); } if ($setting->key == 'sentryIoDSN') { $this->setEnv('SENTRY_LARAVEL_DSN', $setting->val, config('sentry.dsn')); putenv('SENTRY_LARAVEL_DSN='.$setting->val); config(['sentry.dsn' => $setting->val]); } if ($setting->key == 'enableSentryMonitoringFeedbakForm') { $this->setEnv('SENTRY_IO_USER_FEEDBACK_ENABLED', $setting->val, config('services.sentry.feedback-enabled')); putenv('SENTRY_IO_USER_FEEDBACK_ENABLED='.$setting->val); config(['services.sentry.feedback-enabled' => $setting->val]); } if ($setting->key == 'appApplePrivateKey') { $this->setEnv('APPLE_PRIVATE_KEY', $setting->val, config('services.apple.private_key')); putenv('APPLE_PRIVATE_KEY='.$setting->val); config(['services.apple.private_key' => $setting->val]); } if ($setting->key == 'appAppleTeamId') { $this->setEnv('APPLE_TEAM_ID', $setting->val, config('services.apple.team_id')); putenv('APPLE_TEAM_ID='.$setting->val); config(['services.apple.team_id' => $setting->val]); } if ($setting->key == 'appAppleKeyId') { $this->setEnv('APPLE_KEY_ID', $setting->val, config('services.apple.key_id')); putenv('APPLE_KEY_ID='.$setting->val); config(['services.apple.key_id' => $setting->val]); } if ($setting->key == 'appAppleId') { $this->setEnv('APPLE_CLIENT_ID', $setting->val, config('services.apple.client_id')); putenv('APPLE_CLIENT_ID='.$setting->val); config(['services.apple.client_id' => $setting->val]); } if ($setting->key == 'appAppleSecret') { $this->setEnv('APPLE_CLIENT_SECRET', $setting->val, config('services.apple.client_secret')); putenv('APPLE_CLIENT_SECRET='.$setting->val); config(['services.apple.client_secret' => $setting->val]); } if ($setting->key == 'appAppleRedirect') { $this->setEnv('APPLE_REDIRECT_URI', $setting->val, config('services.apple.redirect')); putenv('APPLE_REDIRECT_URI='.$setting->val); config(['services.apple.redirect' => $setting->val]); } if ($setting->key == 'appStackExchangeSite') { $this->setEnv('STACKEXCHANGE_CLIENT_SITE', $setting->val, config('services.stackexchange.site')); putenv('STACKEXCHANGE_CLIENT_SITE='.$setting->val); config(['services.stackexchange.site' => $setting->val]); } if ($setting->key == 'appStackExchangeKey') { $this->setEnv('STACKEXCHANGE_CLIENT_KEY', $setting->val, config('services.stackexchange.key')); putenv('STACKEXCHANGE_CLIENT_KEY='.$setting->val); config(['services.stackexchange.key' => $setting->val]); } if ($setting->key == 'appStackExchangeId') { $this->setEnv('STACKEXCHANGE_CLIENT_ID', $setting->val, config('services.stackexchange.client_id')); putenv('STACKEXCHANGE_CLIENT_ID='.$setting->val); config(['services.stackexchange.client_id' => $setting->val]); } if ($setting->key == 'appStackExchangeSecret') { $this->setEnv('STACKEXCHANGE_CLIENT_SECRET', $setting->val, config('services.stackexchange.client_secret')); putenv('STACKEXCHANGE_CLIENT_SECRET='.$setting->val); config(['services.stackexchange.client_secret' => $setting->val]); } if ($setting->key == 'appStackExchangeRedirect') { $this->setEnv('STACKEXCHANGE_REDIRECT_URI', $setting->val, config('services.stackexchange.redirect')); putenv('STACKEXCHANGE_REDIRECT_URI='.$setting->val); config(['services.stackexchange.redirect' => $setting->val]); } if ($setting->key == 'appGitLabId') { $this->setEnv('GITLAB_CLIENT_ID', $setting->val, config('services.gitlab.client_id')); putenv('GITLAB_CLIENT_ID='.$setting->val); config(['services.gitlab.client_id' => $setting->val]); } if ($setting->key == 'appGitLabSecret') { $this->setEnv('GITLAB_CLIENT_SECRET', $setting->val, config('services.gitlab.client_secret')); putenv('GITLAB_CLIENT_SECRET='.$setting->val); config(['services.gitlab.client_secret' => $setting->val]); } if ($setting->key == 'appGitLabRedirect') { $this->setEnv('GITLAB_REDIRECT_URI', $setting->val, config('services.gitlab.redirect')); putenv('GITLAB_REDIRECT_URI='.$setting->val); config(['services.gitlab.redirect' => $setting->val]); } if ($setting->key == 'appRedditId') { $this->setEnv('REDDIT_CLIENT_ID', $setting->val, config('services.reddit.client_id')); putenv('REDDIT_CLIENT_ID='.$setting->val); config(['services.reddit.client_id' => $setting->val]); } if ($setting->key == 'appRedditSecret') { $this->setEnv('REDDIT_CLIENT_SECRET', $setting->val, config('services.reddit.client_secret')); putenv('REDDIT_CLIENT_SECRET='.$setting->val); config(['services.reddit.client_secret' => $setting->val]); } if ($setting->key == 'appRedditResponseType') { $this->setEnv('REDDIT_RESPONSE_TYPE', $setting->val, config('services.reddit.response_type')); putenv('REDDIT_RESPONSE_TYPE='.$setting->val); config(['services.reddit.response_type' => $setting->val]); } if ($setting->key == 'appRedditState') { $this->setEnv('REDDIT_STATE', $setting->val, config('services.reddit.state')); putenv('REDDIT_STATE='.$setting->val); config(['services.reddit.state' => $setting->val]); } if ($setting->key == 'appRedditRedirect') { $this->setEnv('REDDIT_REDIRECT_URI', $setting->val, config('services.reddit.redirect')); putenv('REDDIT_REDIRECT_URI='.$setting->val); config(['services.reddit.redirect' => $setting->val]); } if ($setting->key == 'appSnapchatId') { $this->setEnv('SNAPCHAT_CLIENT_ID', $setting->val, config('services.snapchat.client_id')); putenv('SNAPCHAT_CLIENT_ID='.$setting->val); config(['services.snapchat.client_id' => $setting->val]); } if ($setting->key == 'appSnapchatSecret') { $this->setEnv('SNAPCHAT_CLIENT_SECRET', $setting->val, config('services.snapchat.client_secret')); putenv('SNAPCHAT_CLIENT_SECRET='.$setting->val); config(['services.snapchat.client_secret' => $setting->val]); } if ($setting->key == 'appSnapchatRedirect') { $this->setEnv('SNAPCHAT_REDIRECT_URI', $setting->val, config('services.snapchat.redirect')); putenv('SNAPCHAT_REDIRECT_URI='.$setting->val); config(['services.snapchat.redirect' => $setting->val]); } if ($setting->key == 'appMeetupId') { $this->setEnv('MEETUP_CLIENT_ID', $setting->val, config('services.meetup.client_id')); putenv('MEETUP_CLIENT_ID='.$setting->val); config(['services.meetup.client_id' => $setting->val]); } if ($setting->key == 'appMeetupSecret') { $this->setEnv('MEETUP_CLIENT_SECRET', $setting->val, config('services.meetup.client_secret')); putenv('MEETUP_CLIENT_SECRET='.$setting->val); config(['services.meetup.client_secret' => $setting->val]); } if ($setting->key == 'appMeetupRedirect') { $this->setEnv('MEETUP_REDIRECT_URI', $setting->val, config('services.meetup.redirect')); putenv('MEETUP_REDIRECT_URI='.$setting->val); config(['services.meetup.redirect' => $setting->val]); } if ($setting->key == 'appAtlassianId') { $this->setEnv('ATLASSIAN_CLIENT_ID', $setting->val, config('services.atlassian.client_id')); putenv('ATLASSIAN_CLIENT_ID='.$setting->val); config(['services.atlassian.client_id' => $setting->val]); } if ($setting->key == 'appAtlassianSecret') { $this->setEnv('ATLASSIAN_CLIENT_SECRET', $setting->val, config('services.atlassian.client_secret')); putenv('ATLASSIAN_CLIENT_SECRET='.$setting->val); config(['services.atlassian.client_secret' => $setting->val]); } if ($setting->key == 'appAtlassianRedirect') { $this->setEnv('ATLASSIAN_REDIRECT_URI', $setting->val, config('services.atlassian.redirect')); putenv('ATLASSIAN_REDIRECT_URI='.$setting->val); config(['services.atlassian.redirect' => $setting->val]); } if ($setting->key == 'appName') { $this->setEnv('APP_NAME', "'".$setting->val."'", "'".config('app.name')."'"); putenv('APP_NAME='.$setting->val); config(['app.name' => $setting->val]); } if ($setting->key == 'author') { $this->setEnv('APP_AUTHOR', "'".$setting->val."'", "'".config('settings.author')."'"); putenv('APP_AUTHOR='.$setting->val); config(['settings.author' => $setting->val]); } if ($setting->key == 'description') { $this->setEnv('APP_DESC', "'".$setting->val."'", "'".config('settings.description')."'"); putenv('APP_DESC='.$setting->val); config(['settings.description' => $setting->val]); } if ($setting->key == 'keywords') { $this->setEnv('APP_KEYWORDS', "'".$setting->val."'", "'".config('settings.keywords')."'"); putenv('APP_KEYWORDS='.$setting->val); config(['settings.keywords' => $setting->val]); } if ($setting->key == 'enableKonamiAsteroids') { $this->setEnv('KONAMI_ASTEROIDS_ENABLED', $setting->val, config('settings.enableKonamiAsteroids')); putenv('KONAMI_ASTEROIDS_ENABLED='.$setting->val); config(['settings.enableKonamiAsteroids' => $setting->val]); } if ($setting->key == 'enableKonamiToasty') { $this->setEnv('KONAMI_TOASTY_ENABLED', $setting->val, config('settings.enableKonamiToasty')); putenv('KONAMI_TOASTY_ENABLED='.$setting->val); config(['settings.enableKonamiToasty' => $setting->val]); } Artisan::call('config:cache'); // NEW_PROVIDER_PLUG :: Put New Provider HERE } /** * Set .env variables directly to the file. * * @param string $key * @param string|null $value * @param string|null $configed */ public function setEnv(string $key, $value, $configed = null) { $term = $configed != null ? $configed : env($key); $path = app()->environmentFilePath(); $escaped = preg_quote('='.$term, '/'); file_put_contents($path, preg_replace( "/^{$key}{$escaped}/m", "{$key}={$value}", file_get_contents($path) )); if (file_exists(\App::getCachedConfigPath())) { \Artisan::call('config:cache'); } } } ================================================ FILE: app/Traits/SocialiteProvidersTrait.php ================================================ setupProviders(); } /** * Set the private vars and the apps config dynamically. */ protected function setupProviders() { $this->setProviderSettings(); $this->setProviderConfigs(); $this->setAppProvidersConfigs(); } /** * Set the private var $providerSettings. */ protected function setProviderSettings() { $this->providerSettings = Setting::where('group', 'auth') ->where(function ($query) { $query->where('key', 'enableFbLogin') ->orWhere('key', 'appFbId') ->orWhere('key', 'appFbSecret') ->orWhere('key', 'appFbRedirect') ->orWhere('key', 'enableTwitterLogin') ->orWhere('key', 'appTwitterId') ->orWhere('key', 'appTwitterSecret') ->orWhere('key', 'appTwitterRedirect') ->orWhere('key', 'enableGoogleLogin') ->orWhere('key', 'appGoogleId') ->orWhere('key', 'appGoogleSecret') ->orWhere('key', 'appGoogleRedirect') ->orWhere('key', 'enableGitHubLogin') ->orWhere('key', 'appGitHubId') ->orWhere('key', 'appGitHubSecret') ->orWhere('key', 'appGitHubRedirect') ->orWhere('key', 'enableTwitchLogin') ->orWhere('key', 'appTwitchId') ->orWhere('key', 'appTwitchSecret') ->orWhere('key', 'appTwitchRedirect') ->orWhere('key', 'enableInstagramLogin') ->orWhere('key', 'appInstagramId') ->orWhere('key', 'appInstagramSecret') ->orWhere('key', 'appInstagramRedirect') ->orWhere('key', 'enableYouTubeLogin') ->orWhere('key', 'appYouTubeId') ->orWhere('key', 'appYouTubeSecret') ->orWhere('key', 'appYouTubeRedirect') ->orWhere('key', 'enableLinkedInLogin') ->orWhere('key', 'appLinkedInId') ->orWhere('key', 'appLinkedInSecret') ->orWhere('key', 'appLinkedInRedirect') ->orWhere('key', 'enableAppleLogin') ->orWhere('key', 'appAppleId') ->orWhere('key', 'appAppleSecret') ->orWhere('key', 'appAppleRedirect') ->orWhere('key', 'appApplePrivateKey') ->orWhere('key', 'appAppleTeamId') ->orWhere('key', 'appAppleKeyId') ->orWhere('key', 'enableMicrosoftLogin') ->orWhere('key', 'appMicrosoftId') ->orWhere('key', 'appMicrosoftSecret') ->orWhere('key', 'appMicrosoftRedirect') ->orWhere('key', 'enableTikTokLogin') ->orWhere('key', 'appTikTokId') ->orWhere('key', 'appTikTokSecret') ->orWhere('key', 'appTikTokRedirect') ->orWhere('key', 'enableZoHoLogin') ->orWhere('key', 'appZoHoId') ->orWhere('key', 'appZoHoSecret') ->orWhere('key', 'appZoHoRedirect') ->orWhere('key', 'enableStackExchangeLogin') ->orWhere('key', 'appStackExchangeId') ->orWhere('key', 'appStackExchangeKey') ->orWhere('key', 'appStackExchangeSite') ->orWhere('key', 'appStackExchangeSecret') ->orWhere('key', 'appStackExchangeRedirect') ->orWhere('key', 'enableGitLabLogin') ->orWhere('key', 'appGitLabId') ->orWhere('key', 'appGitLabSecret') ->orWhere('key', 'appGitLabRedirect') ->orWhere('key', 'enableRedditLogin') ->orWhere('key', 'appRedditId') ->orWhere('key', 'appRedditSecret') ->orWhere('key', 'appRedditResponseType') ->orWhere('key', 'appRedditState') ->orWhere('key', 'appRedditRedirect') ->orWhere('key', 'enableSnapchatLogin') ->orWhere('key', 'appSnapchatId') ->orWhere('key', 'appSnapchatSecret') ->orWhere('key', 'appSnapchatRedirect') ->orWhere('key', 'enableMeetupLogin') ->orWhere('key', 'appMeetupId') ->orWhere('key', 'appMeetupSecret') ->orWhere('key', 'appMeetupRedirect') ->orWhere('key', 'enableAtlassianLogin') ->orWhere('key', 'appAtlassianId') ->orWhere('key', 'appAtlassianSecret') ->orWhere('key', 'appAtlassianRedirect'); // NEW_PROVIDER_PLUG :: Put New Provider HERE })->get(); } /** * Set the private var $providerConfigs. */ protected function setProviderConfigs() { $appGitHubId = $this->providerSettings->where('key', 'appGitHubId')->first(); $appGitHubId = $appGitHubId ? $appGitHubId->val : null; $appGitHubSecret = $this->providerSettings->where('key', 'appGitHubSecret')->first(); $appGitHubSecret = $appGitHubSecret ? $appGitHubSecret->val : null; $appGitHubRedirect = $this->providerSettings->where('key', 'appGitHubRedirect')->first(); $appGitHubRedirect = $appGitHubRedirect ? $appGitHubRedirect->val : null; $appFbId = $this->providerSettings->where('key', 'appFbId')->first(); $appFbId = $appFbId ? $appFbId->val : null; $appFbSecret = $this->providerSettings->where('key', 'appFbSecret')->first(); $appFbSecret = $appFbSecret ? $appFbSecret->val : null; $appFbRedirect = $this->providerSettings->where('key', 'appFbRedirect')->first(); $appFbRedirect = $appFbRedirect ? $appFbRedirect->val : null; $appTwitterId = $this->providerSettings->where('key', 'appTwitterId')->first(); $appTwitterId = $appTwitterId ? $appTwitterId->val : null; $appTwitterSecret = $this->providerSettings->where('key', 'appTwitterSecret')->first(); $appTwitterSecret = $appTwitterSecret ? $appTwitterSecret->val : null; $appTwitterRedirect = $this->providerSettings->where('key', 'appTwitterRedirect')->first(); $appTwitterRedirect = $appTwitterRedirect ? $appTwitterRedirect->val : null; $appGoogleId = $this->providerSettings->where('key', 'appGoogleId')->first(); $appGoogleId = $appGoogleId ? $appGoogleId->val : null; $appGoogleSecret = $this->providerSettings->where('key', 'appGoogleSecret')->first(); $appGoogleSecret = $appGoogleSecret ? $appGoogleSecret->val : null; $appGoogleRedirect = $this->providerSettings->where('key', 'appGoogleRedirect')->first(); $appGoogleRedirect = $appGoogleRedirect ? $appGoogleRedirect->val : null; $appYouTubeId = $this->providerSettings->where('key', 'appYouTubeId')->first(); $appYouTubeId = $appYouTubeId ? $appYouTubeId->val : null; $appYouTubeSecret = $this->providerSettings->where('key', 'appYouTubeSecret')->first(); $appYouTubeSecret = $appYouTubeSecret ? $appYouTubeSecret->val : null; $appYouTubeRedirect = $this->providerSettings->where('key', 'appYouTubeRedirect')->first(); $appYouTubeRedirect = $appYouTubeRedirect ? $appYouTubeRedirect->val : null; $appTwitchId = $this->providerSettings->where('key', 'appTwitchId')->first(); $appTwitchId = $appTwitchId ? $appTwitchId->val : null; $appTwitchSecret = $this->providerSettings->where('key', 'appTwitchSecret')->first(); $appTwitchSecret = $appTwitchSecret ? $appTwitchSecret->val : null; $appTwitchRedirect = $this->providerSettings->where('key', 'appTwitchRedirect')->first(); $appTwitchRedirect = $appTwitchRedirect ? $appTwitchRedirect->val : null; $appInstagramId = $this->providerSettings->where('key', 'appInstagramId')->first(); $appInstagramId = $appInstagramId ? $appInstagramId->val : null; $appInstagramSecret = $this->providerSettings->where('key', 'appInstagramSecret')->first(); $appInstagramSecret = $appInstagramSecret ? $appInstagramSecret->val : null; $appInstagramRedirect = $this->providerSettings->where('key', 'appInstagramRedirect')->first(); $appInstagramRedirect = $appInstagramRedirect ? $appInstagramRedirect->val : null; $appLinkedInId = $this->providerSettings->where('key', 'appLinkedInId')->first(); $appLinkedInId = $appLinkedInId ? $appLinkedInId->val : null; $appLinkedInSecret = $this->providerSettings->where('key', 'appLinkedInSecret')->first(); $appLinkedInSecret = $appLinkedInSecret ? $appLinkedInSecret->val : null; $appLinkedInRedirect = $this->providerSettings->where('key', 'appLinkedInRedirect')->first(); $appLinkedInRedirect = $appLinkedInRedirect ? $appLinkedInRedirect->val : null; $appAppleId = $this->providerSettings->where('key', 'appAppleId')->first(); $appAppleId = $appAppleId ? $appAppleId->val : null; $appAppleSecret = $this->providerSettings->where('key', 'appAppleSecret')->first(); $appAppleSecret = $appAppleSecret ? $appAppleSecret->val : null; $appAppleRedirect = $this->providerSettings->where('key', 'appAppleRedirect')->first(); $appAppleRedirect = $appAppleRedirect ? $appAppleRedirect->val : null; $appApplePrivateKey = $this->providerSettings->where('key', 'appApplePrivateKey')->first(); $appApplePrivateKey = $appApplePrivateKey ? $appApplePrivateKey->val : null; $appAppleTeamId = $this->providerSettings->where('key', 'appAppleTeamId')->first(); $appAppleTeamId = $appAppleTeamId ? $appAppleTeamId->val : null; $appAppleKeyId = $this->providerSettings->where('key', 'appAppleKeyId')->first(); $appAppleKeyId = $appAppleKeyId ? $appAppleKeyId->val : null; $appMicrosoftId = $this->providerSettings->where('key', 'appMicrosoftId')->first(); $appMicrosoftId = $appMicrosoftId ? $appMicrosoftId->val : null; $appMicrosoftSecret = $this->providerSettings->where('key', 'appMicrosoftSecret')->first(); $appMicrosoftSecret = $appMicrosoftSecret ? $appMicrosoftSecret->val : null; $appMicrosoftRedirect = $this->providerSettings->where('key', 'appMicrosoftRedirect')->first(); $appMicrosoftRedirect = $appMicrosoftRedirect ? $appMicrosoftRedirect->val : null; $appTikTokId = $this->providerSettings->where('key', 'appTikTokId')->first(); $appTikTokId = $appTikTokId ? $appTikTokId->val : null; $appTikTokSecret = $this->providerSettings->where('key', 'appTikTokSecret')->first(); $appTikTokSecret = $appTikTokSecret ? $appTikTokSecret->val : null; $appTikTokRedirect = $this->providerSettings->where('key', 'appTikTokRedirect')->first(); $appTikTokRedirect = $appTikTokRedirect ? $appTikTokRedirect->val : null; $appZoHoId = $this->providerSettings->where('key', 'appZoHoId')->first(); $appZoHoId = $appZoHoId ? $appZoHoId->val : null; $appZoHoSecret = $this->providerSettings->where('key', 'appZoHoSecret')->first(); $appZoHoSecret = $appZoHoSecret ? $appZoHoSecret->val : null; $appZoHoRedirect = $this->providerSettings->where('key', 'appZoHoRedirect')->first(); $appZoHoRedirect = $appZoHoRedirect ? $appZoHoRedirect->val : null; $appStackExchangeId = $this->providerSettings->where('key', 'appStackExchangeId')->first(); $appStackExchangeId = $appStackExchangeId ? $appStackExchangeId->val : null; $appStackExchangeKey = $this->providerSettings->where('key', 'appStackExchangeKey')->first(); $appStackExchangeKey = $appStackExchangeKey ? $appStackExchangeKey->val : null; $appStackExchangeSite = $this->providerSettings->where('key', 'appStackExchangeSite')->first(); $appStackExchangeSite = $appStackExchangeSite ? $appStackExchangeSite->val : null; $appStackExchangeSecret = $this->providerSettings->where('key', 'appStackExchangeSecret')->first(); $appStackExchangeSecret = $appStackExchangeSecret ? $appStackExchangeSecret->val : null; $appStackExchangeRedirect = $this->providerSettings->where('key', 'appStackExchangeRedirect')->first(); $appStackExchangeRedirect = $appStackExchangeRedirect ? $appStackExchangeRedirect->val : null; $appGitLabId = $this->providerSettings->where('key', 'appGitLabId')->first(); $appGitLabId = $appGitLabId ? $appGitLabId->val : null; $appGitLabSecret = $this->providerSettings->where('key', 'appGitLabSecret')->first(); $appGitLabSecret = $appGitLabSecret ? $appGitLabSecret->val : null; $appGitLabRedirect = $this->providerSettings->where('key', 'appGitLabRedirect')->first(); $appGitLabRedirect = $appGitLabRedirect ? $appGitLabRedirect->val : null; $appRedditId = $this->providerSettings->where('key', 'appRedditId')->first(); $appRedditId = $appRedditId ? $appRedditId->val : null; $appRedditSecret = $this->providerSettings->where('key', 'appRedditSecret')->first(); $appRedditSecret = $appRedditSecret ? $appRedditSecret->val : null; $appRedditRedirect = $this->providerSettings->where('key', 'appRedditRedirect')->first(); $appRedditRedirect = $appRedditRedirect ? $appRedditRedirect->val : null; $appRedditResponseType = $this->providerSettings->where('key', 'appRedditResponseType')->first(); $appRedditResponseType = $appRedditResponseType ? $appRedditResponseType->val : null; $appRedditState = $this->providerSettings->where('key', 'appRedditState')->first(); $appRedditState = $appRedditState ? $appRedditState->val : null; $appSnapchatId = $this->providerSettings->where('key', 'appSnapchatId')->first(); $appSnapchatId = $appSnapchatId ? $appSnapchatId->val : null; $appSnapchatSecret = $this->providerSettings->where('key', 'appSnapchatSecret')->first(); $appSnapchatSecret = $appSnapchatSecret ? $appSnapchatSecret->val : null; $appSnapchatRedirect = $this->providerSettings->where('key', 'appSnapchatRedirect')->first(); $appSnapchatRedirect = $appSnapchatRedirect ? $appSnapchatRedirect->val : null; $appMeetupId = $this->providerSettings->where('key', 'appMeetupId')->first(); $appMeetupId = $appMeetupId ? $appMeetupId->val : null; $appMeetupSecret = $this->providerSettings->where('key', 'appMeetupSecret')->first(); $appMeetupSecret = $appMeetupSecret ? $appMeetupSecret->val : null; $appMeetupRedirect = $this->providerSettings->where('key', 'appMeetupRedirect')->first(); $appMeetupRedirect = $appMeetupRedirect ? $appMeetupRedirect->val : null; $appAtlassianId = $this->providerSettings->where('key', 'appAtlassianId')->first(); $appAtlassianId = $appAtlassianId ? $appAtlassianId->val : null; $appAtlassianSecret = $this->providerSettings->where('key', 'appAtlassianSecret')->first(); $appAtlassianSecret = $appAtlassianSecret ? $appAtlassianSecret->val : null; $appAtlassianRedirect = $this->providerSettings->where('key', 'appAtlassianRedirect')->first(); $appAtlassianRedirect = $appAtlassianRedirect ? $appAtlassianRedirect->val : null; // NEW_PROVIDER_PLUG :: Put New Provider HERE $providerConfigs = [ 'services.github' => [ 'client_id' => $appGitHubId, 'client_secret' => $appGitHubSecret, 'redirect' => $appGitHubRedirect, ], 'services.facebook' => [ 'client_id' => $appFbId, 'client_secret' => $appFbSecret, 'redirect' => $appFbRedirect, ], 'services.twitter' => [ 'client_id' => $appTwitterId, 'client_secret' => $appTwitterSecret, 'redirect' => $appTwitterRedirect, ], 'services.google' => [ 'client_id' => $appGoogleId, 'client_secret' => $appGoogleSecret, 'redirect' => $appGoogleRedirect, ], 'services.youtube' => [ 'client_id' => $appYouTubeId, 'client_secret' => $appYouTubeSecret, 'redirect' => $appYouTubeRedirect, ], 'services.twitch' => [ 'client_id' => $appTwitchId, 'client_secret' => $appTwitchSecret, 'redirect' => $appTwitchRedirect, ], 'services.instagram' => [ 'client_id' => $appInstagramId, 'client_secret' => $appInstagramSecret, 'redirect' => $appInstagramRedirect, ], 'services.linkedin' => [ 'client_id' => $appLinkedInId, 'client_secret' => $appLinkedInSecret, 'redirect' => $appLinkedInRedirect, ], 'services.apple' => [ 'client_id' => $appAppleId, 'client_secret' => $appAppleSecret, 'redirect' => $appAppleRedirect, 'team_id' => $appAppleTeamId, 'key_id' => $appAppleKeyId, 'private_key' => $appApplePrivateKey, ], 'services.microsoft' => [ 'client_id' => $appMicrosoftId, 'client_secret' => $appMicrosoftSecret, 'redirect' => $appMicrosoftRedirect, ], 'services.tiktok' => [ 'client_id' => $appTikTokId, 'client_secret' => $appTikTokSecret, 'redirect' => $appTikTokRedirect, ], 'services.zoho' => [ 'client_id' => $appZoHoId, 'client_secret' => $appZoHoSecret, 'redirect' => $appZoHoRedirect, ], 'services.stackexchange' => [ 'client_id' => $appStackExchangeId, 'client_secret' => $appStackExchangeSecret, 'redirect' => $appStackExchangeRedirect, 'key' => $appStackExchangeKey, 'site' => $appStackExchangeSite, ], 'services.gitlab' => [ 'client_id' => $appGitLabId, 'client_secret' => $appGitLabSecret, 'redirect' => $appGitLabRedirect, ], 'services.reddit' => [ 'client_id' => $appRedditId, 'client_secret' => $appRedditSecret, 'response_type' => $appRedditResponseType, 'state' => $appRedditState, 'redirect' => $appRedditRedirect, ], 'services.snapchat' => [ 'client_id' => $appSnapchatId, 'client_secret' => $appSnapchatSecret, 'redirect' => $appSnapchatRedirect, ], 'services.meetup' => [ 'client_id' => $appMeetupId, 'client_secret' => $appMeetupSecret, 'redirect' => $appMeetupRedirect, ], 'services.atlassian' => [ 'client_id' => $appAtlassianId, 'client_secret' => $appAtlassianSecret, 'redirect' => $appAtlassianRedirect, ], // NEW_PROVIDER_PLUG :: Put New Provider HERE ]; $this->providerConfigs = $providerConfigs; } /** * Set provider configs on the systems config dynamically. */ protected function setAppProvidersConfigs() { config($this->providerConfigs); } /** * This is the list of logins enabled for the front end of the app. * * @return array */ protected function loginsList() { $this->setupProviders(); $ps = $this->providerSettings; $enableFbLogin = $ps->firstWhere('key', 'enableFbLogin')->val; $enableTwitterLogin = $ps->firstWhere('key', 'enableTwitterLogin')->val; $enableGoogleLogin = $ps->firstWhere('key', 'enableGoogleLogin')->val; $enableGitHubLogin = $ps->firstWhere('key', 'enableGitHubLogin')->val; $enableTwitchLogin = $ps->firstWhere('key', 'enableTwitchLogin')->val; $enableInstagramLogin = $ps->firstWhere('key', 'enableInstagramLogin')->val; $enableYouTubeLogin = $ps->firstWhere('key', 'enableYouTubeLogin')->val; $enableLinkedInLogin = $ps->firstWhere('key', 'enableLinkedInLogin')->val; $enableAppleLogin = $ps->firstWhere('key', 'enableAppleLogin')->val; $enableMicrosoftLogin = $ps->firstWhere('key', 'enableMicrosoftLogin')->val; $enableTikTokLogin = $ps->firstWhere('key', 'enableTikTokLogin')->val; $enableZoHoLogin = $ps->firstWhere('key', 'enableZoHoLogin')->val; $enableStackExchangeLogin = $ps->firstWhere('key', 'enableStackExchangeLogin')->val; $enableGitLabLogin = $ps->firstWhere('key', 'enableGitLabLogin')->val; $enableRedditLogin = $ps->firstWhere('key', 'enableRedditLogin')->val; $enableSnapchatLogin = $ps->firstWhere('key', 'enableSnapchatLogin')->val; $enableMeetupLogin = $ps->firstWhere('key', 'enableMeetupLogin')->val; $enableAtlassianLogin = $ps->firstWhere('key', 'enableAtlassianLogin')->val; // NEW_PROVIDER_PLUG :: Put New Provider HERE return [ 'facebook' => $enableFbLogin, 'twitter' => $enableTwitterLogin, 'google' => $enableGoogleLogin, 'instagram' => $enableInstagramLogin, 'github' => $enableGitHubLogin, 'youtube' => $enableYouTubeLogin, 'linkedin' => $enableLinkedInLogin, 'twitch' => $enableTwitchLogin, 'apple' => $enableAppleLogin, 'microsoft' => $enableMicrosoftLogin, 'tiktok' => $enableTikTokLogin, 'zoho' => $enableZoHoLogin, 'stackexchange' => $enableStackExchangeLogin, 'gitlab' => $enableGitLabLogin, 'reddit' => $enableRedditLogin, 'snapchat' => $enableSnapchatLogin, 'meetup' => $enableMeetupLogin, 'atlassian' => $enableAtlassianLogin, // NEW_PROVIDER_PLUG :: Put New Provider HERE ]; } /** * Find or create a user. * * @param string $provider * @param SocialiteUser $user * @return array */ protected function findOrCreateUser(string $provider, $user, string $state = null): array { $existingUser = null; $token = null; $email = null; if ($provider == 'twitter') { // OK - No error $email = $user->email; $providerId = $user->id; } else { $email = $user->getEmail(); $providerId = $user->getId(); } $oauthProvider = SocialiteProvider::where('provider', $provider) ->where('provider_user_id', $providerId) ->first(); if ($state && $state != config('app.key')) { $token = PersonalAccessToken::findToken($state); if ($token) { $existingUser = $token->tokenable; } if ($existingUser && $existingUser->id && $oauthProvider && $oauthProvider->user_id && ($existingUser->id != $oauthProvider->user_id)) { return [ 'user' => null, 'token' => null, ]; } } if ($oauthProvider) { return [ 'user' => $oauthProvider->user, 'token' => $oauthProvider->user->createToken($provider.'-token')->plainTextToken, ]; } if (! $existingUser) { $existingUser = User::whereEmail($email)->first(); } if (! $existingUser) { $existingUser = auth('sanctum')->user(); } if ($existingUser && $oauthProvider) { if ($provider != 'twitter') { $oauthProvider->update([ 'access_token' => $user->token ? $user->token : null, 'refresh_token' => $user->refreshToken ? $user->refreshToken : null, ]); } return [ 'user' => $oauthProvider->user, 'token' => $oauthProvider->user->createToken($provider.'-token')->plainTextToken, ]; } $user = $this->updateOrCreateUser($provider, $user, $existingUser); $token = $user->createToken($provider.'-token')->plainTextToken; return [ 'user' => $user, 'token' => $token, ]; } /** * Create a new user. * * @param string $provider * @param $sUser * @return \App\Models\User | null */ protected function updateOrCreateUser(string $provider, $sUser, $existingUser = null): User { $user = null; $pid = null; $email = null; $token = null; $refreshToken = null; $avatar = null; $emailValid = true; if ($existingUser) { $user = $existingUser; $email = $user->email; if ($provider == 'twitter') { $pid = $sUser->id; $avatar = $sUser->profile_image_url; } else { $pid = $sUser->getId(); } } else { if ($provider == 'twitter') { $pid = $sUser->id; $email = $sUser->email; $name = $sUser->name; $avatar = $sUser->profile_image_url; } else { $pid = $sUser->getId(); $name = $sUser->getName(); $email = $sUser->getEmail(); $avatar = $sUser->getAvatar(); $token = $sUser->token; $refreshToken = $sUser->refreshToken; } if ($provider == 'reddit') { $name = $sUser->getNickname(); } if (! $email) { $email = 'email_missing_'.Str::random(20).'@'.Str::random(20).'.example.org'; $emailValid = false; } $user = User::create([ 'name' => $name, 'email' => $email, 'password' => bcrypt(Str::random(50)), ]); // $user->attachRole(config('roles.models.role')::whereName('User')->first()); if ($user->email && $emailValid) { event(new Registered($user)); // $user->email_verified_at = Carbon::now(); } $user->save(); } $this->addSocialiteProviderToUser($user, [ 'provider' => $provider, 'provider_user_id' => $pid, 'access_token' => $token, 'refresh_token' => $refreshToken, 'avatar' => $avatar, ]); return $user; } /** * Update or Create a Socialite Provider. * * @param \App\Models\User $user [description] * @param array $data * @return \App\Models\SocialiteProvider */ protected function addSocialiteProviderToUser(User $user, $data): SocialiteProvider { $provider = SocialiteProvider::where('user_id', $user->id) ->where('provider', $data['provider']) ->where('provider_user_id', $data['provider_user_id'])->first(); if ($provider) { return $provider->update([ 'access_token' => $data['access_token'], 'refresh_token' => $data['refresh_token'], 'avatar' => $data['avatar'], ]); } return $user->socialiteProviders()->create([ 'provider' => $data['provider'], 'provider_user_id' => $data['provider_user_id'], 'access_token' => $data['access_token'], 'refresh_token' => $data['refresh_token'], 'avatar' => $data['avatar'], ]); } /** * Generate a random string for temp Id. * * @param int $depth * @return string */ protected function generateTempId($depth = 40) { return Str::random($depth); } /** * Cache the current state and modify the url with a random string to be the key for the return user. * Not all providers return the state and this will allow us to do so.. * * @param sting $url * @param sting $state * @return string */ protected function cacheStatePutKeyInUrl($url = null, $state = null) { $tempId = $this->generateTempId(); $this->tempStoreStateInCache($tempId, $state); return $url.'&state='.$tempId; } /** * Cache the state temporarily to pick up on callback. * * @param string $tempId * @param string $state * @param int $seconds * @return void */ protected function tempStoreStateInCache($tempId, $state, $seconds = 60) { Cache::put($tempId, $state, $seconds); } } ================================================ FILE: artisan ================================================ #!/usr/bin/env php handleCommand(new ArgvInput); exit($status); ================================================ FILE: bootstrap/app.php ================================================ withRouting( web: __DIR__.'/../routes/web.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware) { $middleware->web(append: [ \App\Http\Middleware\HandleInertiaRequests::class, \Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class, ]); // }) ->withExceptions(function (Exceptions $exceptions) { // })->create(); ================================================ FILE: bootstrap/cache/.gitignore ================================================ * !.gitignore ================================================ FILE: bootstrap/providers.php ================================================ env('APP_NAME', 'Laravel'), /* |-------------------------------------------------------------------------- | Application Environment |-------------------------------------------------------------------------- | | This value determines the "environment" your application is currently | running in. This may determine how you prefer to configure various | services the application utilizes. Set this in your ".env" file. | */ 'env' => env('APP_ENV', 'production'), /* |-------------------------------------------------------------------------- | Application Debug Mode |-------------------------------------------------------------------------- | | When your application is in debug mode, detailed error messages with | stack traces will be shown on every error that occurs within your | application. If disabled, a simple generic error page is shown. | */ 'debug' => (bool) env('APP_DEBUG', false), /* |-------------------------------------------------------------------------- | Application URL |-------------------------------------------------------------------------- | | This URL is used by the console to properly generate URLs when using | the Artisan command line tool. You should set this to the root of | the application so that it's available within Artisan commands. | */ 'url' => env('APP_URL', 'http://localhost'), /* |-------------------------------------------------------------------------- | Application Timezone |-------------------------------------------------------------------------- | | Here you may specify the default timezone for your application, which | will be used by the PHP date and date-time functions. The timezone | is set to "UTC" by default as it is suitable for most use cases. | */ 'timezone' => env('APP_TIMEZONE', 'UTC'), /* |-------------------------------------------------------------------------- | Application Locale Configuration |-------------------------------------------------------------------------- | | The application locale determines the default locale that will be used | by Laravel's translation / localization methods. This option can be | set to any locale for which you plan to have translation strings. | */ 'locale' => env('APP_LOCALE', 'en'), 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), /* |-------------------------------------------------------------------------- | Encryption Key |-------------------------------------------------------------------------- | | This key is utilized by Laravel's encryption services and should be set | to a random, 32 character string to ensure that all encrypted values | are secure. You should do this prior to deploying the application. | */ 'cipher' => 'AES-256-CBC', 'key' => env('APP_KEY'), 'previous_keys' => [ ...array_filter( explode(',', env('APP_PREVIOUS_KEYS', '')) ), ], /* |-------------------------------------------------------------------------- | Maintenance Mode Driver |-------------------------------------------------------------------------- | | These configuration options determine the driver used to determine and | manage Laravel's "maintenance mode" status. The "cache" driver will | allow maintenance mode to be controlled across multiple machines. | | Supported drivers: "file", "cache" | */ 'maintenance' => [ 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), 'store' => env('APP_MAINTENANCE_STORE', 'database'), ], ]; ================================================ FILE: config/auth.php ================================================ [ 'guard' => env('AUTH_GUARD', 'web'), 'passwords' => env('AUTH_PASSWORD_BROKER', 'users'), ], /* |-------------------------------------------------------------------------- | Authentication Guards |-------------------------------------------------------------------------- | | Next, you may define every authentication guard for your application. | Of course, a great default configuration has been defined for you | which utilizes session storage plus the Eloquent user provider. | | All authentication guards have a user provider, which defines how the | users are actually retrieved out of your database or other storage | system used by the application. Typically, Eloquent is utilized. | | Supported: "session" | */ 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], ], /* |-------------------------------------------------------------------------- | User Providers |-------------------------------------------------------------------------- | | All authentication guards have a user provider, which defines how the | users are actually retrieved out of your database or other storage | system used by the application. Typically, Eloquent is utilized. | | If you have multiple user tables or models you may configure multiple | providers to represent the model / table. These providers may then | be assigned to any extra authentication guards you have defined. | | Supported: "database", "eloquent" | */ 'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => env('AUTH_MODEL', App\Models\User::class), ], // 'users' => [ // 'driver' => 'database', // 'table' => 'users', // ], ], /* |-------------------------------------------------------------------------- | Resetting Passwords |-------------------------------------------------------------------------- | | These configuration options specify the behavior of Laravel's password | reset functionality, including the table utilized for token storage | and the user provider that is invoked to actually retrieve users. | | The expiry time is the number of minutes that each reset token will be | considered valid. This security feature keeps tokens short-lived so | they have less time to be guessed. You may change this as needed. | | The throttle setting is the number of seconds a user must wait before | generating more password reset tokens. This prevents the user from | quickly generating a very large amount of password reset tokens. | */ 'passwords' => [ 'users' => [ 'provider' => 'users', 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), 'expire' => 60, 'throttle' => 60, ], ], /* |-------------------------------------------------------------------------- | Password Confirmation Timeout |-------------------------------------------------------------------------- | | Here you may define the amount of seconds before a password confirmation | window expires and users are asked to re-enter their password via the | confirmation screen. By default, the timeout lasts for three hours. | */ 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800), ]; ================================================ FILE: config/cache.php ================================================ env('CACHE_STORE', 'database'), /* |-------------------------------------------------------------------------- | Cache Stores |-------------------------------------------------------------------------- | | Here you may define all of the cache "stores" for your application as | well as their drivers. You may even define multiple stores for the | same cache driver to group types of items stored in your caches. | | Supported drivers: "array", "database", "file", "memcached", | "redis", "dynamodb", "octane", "null" | */ 'stores' => [ 'array' => [ 'driver' => 'array', 'serialize' => false, ], 'database' => [ 'driver' => 'database', 'table' => env('DB_CACHE_TABLE', 'cache'), 'connection' => env('DB_CACHE_CONNECTION'), 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'), ], 'file' => [ 'driver' => 'file', 'path' => storage_path('framework/cache/data'), 'lock_path' => storage_path('framework/cache/data'), ], 'memcached' => [ 'driver' => 'memcached', 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), 'sasl' => [ env('MEMCACHED_USERNAME'), env('MEMCACHED_PASSWORD'), ], 'options' => [ // Memcached::OPT_CONNECT_TIMEOUT => 2000, ], 'servers' => [ [ 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 'port' => env('MEMCACHED_PORT', 11211), 'weight' => 100, ], ], ], 'redis' => [ 'driver' => 'redis', 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'), 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'), ], 'dynamodb' => [ 'driver' => 'dynamodb', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), 'endpoint' => env('DYNAMODB_ENDPOINT'), ], 'octane' => [ 'driver' => 'octane', ], ], /* |-------------------------------------------------------------------------- | Cache Key Prefix |-------------------------------------------------------------------------- | | When utilizing the APC, database, memcached, Redis, and DynamoDB cache | stores, there might be other applications using the same cache. For | that reason, you may prefix every cache key to avoid collisions. | */ 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'), ]; ================================================ FILE: config/database.php ================================================ env('DB_CONNECTION', 'sqlite'), /* |-------------------------------------------------------------------------- | Database Connections |-------------------------------------------------------------------------- | | Below are all of the database connections defined for your application. | An example configuration is provided for each database system which | is supported by Laravel. You're free to add / remove connections. | */ 'connections' => [ 'sqlite' => [ 'driver' => 'sqlite', 'url' => env('DB_URL'), 'database' => env('DB_DATABASE', database_path('database.sqlite')), 'prefix' => '', 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), ], 'mysql' => [ 'driver' => 'mysql', 'url' => env('DB_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'laravel'), 'username' => env('DB_USERNAME', 'root'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => env('DB_CHARSET', 'utf8mb4'), 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), 'prefix' => '', 'prefix_indexes' => true, 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], 'mariadb' => [ 'driver' => 'mariadb', 'url' => env('DB_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'laravel'), 'username' => env('DB_USERNAME', 'root'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => env('DB_CHARSET', 'utf8mb4'), 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), 'prefix' => '', 'prefix_indexes' => true, 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], 'pgsql' => [ 'driver' => 'pgsql', 'url' => env('DB_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '5432'), 'database' => env('DB_DATABASE', 'laravel'), 'username' => env('DB_USERNAME', 'root'), 'password' => env('DB_PASSWORD', ''), 'charset' => env('DB_CHARSET', 'utf8'), 'prefix' => '', 'prefix_indexes' => true, 'search_path' => 'public', 'sslmode' => 'prefer', ], 'sqlsrv' => [ 'driver' => 'sqlsrv', 'url' => env('DB_URL'), 'host' => env('DB_HOST', 'localhost'), 'port' => env('DB_PORT', '1433'), 'database' => env('DB_DATABASE', 'laravel'), 'username' => env('DB_USERNAME', 'root'), 'password' => env('DB_PASSWORD', ''), 'charset' => env('DB_CHARSET', 'utf8'), 'prefix' => '', 'prefix_indexes' => true, // 'encrypt' => env('DB_ENCRYPT', 'yes'), // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'), ], ], /* |-------------------------------------------------------------------------- | Migration Repository Table |-------------------------------------------------------------------------- | | This table keeps track of all the migrations that have already run for | your application. Using this information, we can determine which of | the migrations on disk haven't actually been run on the database. | */ 'migrations' => [ 'table' => 'migrations', 'update_date_on_publish' => true, ], /* |-------------------------------------------------------------------------- | Redis Databases |-------------------------------------------------------------------------- | | Redis is an open source, fast, and advanced key-value store that also | provides a richer body of commands than a typical key-value system | such as Memcached. You may define your connection settings here. | */ 'redis' => [ 'client' => env('REDIS_CLIENT', 'phpredis'), 'options' => [ 'cluster' => env('REDIS_CLUSTER', 'redis'), 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), ], 'default' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), 'username' => env('REDIS_USERNAME'), 'password' => env('REDIS_PASSWORD'), 'port' => env('REDIS_PORT', '6379'), 'database' => env('REDIS_DB', '0'), ], 'cache' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), 'username' => env('REDIS_USERNAME'), 'password' => env('REDIS_PASSWORD'), 'port' => env('REDIS_PORT', '6379'), 'database' => env('REDIS_CACHE_DB', '1'), ], ], ]; ================================================ FILE: config/filesystems.php ================================================ env('FILESYSTEM_DISK', 'local'), /* |-------------------------------------------------------------------------- | Filesystem Disks |-------------------------------------------------------------------------- | | Below you may configure as many filesystem disks as necessary, and you | may even configure multiple disks for the same driver. Examples for | most supported storage drivers are configured here for reference. | | Supported Drivers: "local", "ftp", "sftp", "s3" | */ 'disks' => [ 'local' => [ 'driver' => 'local', 'root' => storage_path('app'), 'throw' => false, ], 'public' => [ 'driver' => 'local', 'root' => storage_path('app/public'), 'url' => env('APP_URL').'/storage', 'visibility' => 'public', 'throw' => false, ], 's3' => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET'), 'url' => env('AWS_URL'), 'endpoint' => env('AWS_ENDPOINT'), 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), 'throw' => false, ], ], /* |-------------------------------------------------------------------------- | Symbolic Links |-------------------------------------------------------------------------- | | Here you may configure the symbolic links that will be created when the | `storage:link` Artisan command is executed. The array keys should be | the locations of the links and the values should be their targets. | */ 'links' => [ public_path('storage') => storage_path('app/public'), ], ]; ================================================ FILE: config/logging.php ================================================ env('LOG_CHANNEL', 'stack'), /* |-------------------------------------------------------------------------- | Deprecations Log Channel |-------------------------------------------------------------------------- | | This option controls the log channel that should be used to log warnings | regarding deprecated PHP and library features. This allows you to get | your application ready for upcoming major versions of dependencies. | */ 'deprecations' => [ 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), 'trace' => env('LOG_DEPRECATIONS_TRACE', false), ], /* |-------------------------------------------------------------------------- | Log Channels |-------------------------------------------------------------------------- | | Here you may configure the log channels for your application. Laravel | utilizes the Monolog PHP logging library, which includes a variety | of powerful log handlers and formatters that you're free to use. | | Available Drivers: "single", "daily", "slack", "syslog", | "errorlog", "monolog", "custom", "stack" | */ 'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => explode(',', env('LOG_STACK', 'single')), 'ignore_exceptions' => false, ], 'single' => [ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), 'replace_placeholders' => true, ], 'daily' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => env('LOG_DAILY_DAYS', 14), 'replace_placeholders' => true, ], 'slack' => [ 'driver' => 'slack', 'url' => env('LOG_SLACK_WEBHOOK_URL'), 'username' => env('LOG_SLACK_USERNAME', 'Laravel Log'), 'emoji' => env('LOG_SLACK_EMOJI', ':boom:'), 'level' => env('LOG_LEVEL', 'critical'), 'replace_placeholders' => true, ], 'papertrail' => [ 'driver' => 'monolog', 'level' => env('LOG_LEVEL', 'debug'), 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class), 'handler_with' => [ 'host' => env('PAPERTRAIL_URL'), 'port' => env('PAPERTRAIL_PORT'), 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'), ], 'processors' => [PsrLogMessageProcessor::class], ], 'stderr' => [ 'driver' => 'monolog', 'level' => env('LOG_LEVEL', 'debug'), 'handler' => StreamHandler::class, 'formatter' => env('LOG_STDERR_FORMATTER'), 'with' => [ 'stream' => 'php://stderr', ], 'processors' => [PsrLogMessageProcessor::class], ], 'syslog' => [ 'driver' => 'syslog', 'level' => env('LOG_LEVEL', 'debug'), 'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER), 'replace_placeholders' => true, ], 'errorlog' => [ 'driver' => 'errorlog', 'level' => env('LOG_LEVEL', 'debug'), 'replace_placeholders' => true, ], 'null' => [ 'driver' => 'monolog', 'handler' => NullHandler::class, ], 'emergency' => [ 'path' => storage_path('logs/laravel.log'), ], ], ]; ================================================ FILE: config/mail.php ================================================ env('MAIL_MAILER', 'log'), /* |-------------------------------------------------------------------------- | Mailer Configurations |-------------------------------------------------------------------------- | | Here you may configure all of the mailers used by your application plus | their respective settings. Several examples have been configured for | you and you are free to add your own as your application requires. | | Laravel supports a variety of mail "transport" drivers that can be used | when delivering an email. You may specify which one you're using for | your mailers below. You may also add additional mailers if needed. | | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2", | "postmark", "log", "array", "failover", "roundrobin" | */ 'mailers' => [ 'smtp' => [ 'transport' => 'smtp', 'url' => env('MAIL_URL'), 'host' => env('MAIL_HOST', '127.0.0.1'), 'port' => env('MAIL_PORT', 2525), 'encryption' => env('MAIL_ENCRYPTION', 'tls'), 'username' => env('MAIL_USERNAME'), 'password' => env('MAIL_PASSWORD'), 'timeout' => null, 'local_domain' => env('MAIL_EHLO_DOMAIN'), ], 'ses' => [ 'transport' => 'ses', ], 'postmark' => [ 'transport' => 'postmark', // 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'), // 'client' => [ // 'timeout' => 5, // ], ], 'sendmail' => [ 'transport' => 'sendmail', 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), ], 'log' => [ 'transport' => 'log', 'channel' => env('MAIL_LOG_CHANNEL'), ], 'array' => [ 'transport' => 'array', ], 'failover' => [ 'transport' => 'failover', 'mailers' => [ 'smtp', 'log', ], ], 'roundrobin' => [ 'transport' => 'roundrobin', 'mailers' => [ 'ses', 'postmark', ], ], ], /* |-------------------------------------------------------------------------- | Global "From" Address |-------------------------------------------------------------------------- | | You may wish for all emails sent by your application to be sent from | the same address. Here you may specify a name and address that is | used globally for all emails that are sent by your application. | */ 'from' => [ 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), 'name' => env('MAIL_FROM_NAME', 'Example'), ], ]; ================================================ FILE: config/permission.php ================================================ [ /* * When using the "HasPermissions" trait from this package, we need to know which * Eloquent model should be used to retrieve your permissions. Of course, it * is often just the "Permission" model but you may use whatever you like. * * The model you want to use as a Permission model needs to implement the * `Spatie\Permission\Contracts\Permission` contract. */ 'permission' => Spatie\Permission\Models\Permission::class, /* * When using the "HasRoles" trait from this package, we need to know which * Eloquent model should be used to retrieve your roles. Of course, it * is often just the "Role" model but you may use whatever you like. * * The model you want to use as a Role model needs to implement the * `Spatie\Permission\Contracts\Role` contract. */ 'role' => Spatie\Permission\Models\Role::class, ], 'table_names' => [ /* * When using the "HasRoles" trait from this package, we need to know which * table should be used to retrieve your roles. We have chosen a basic * default value but you may easily change it to any table you like. */ 'roles' => 'roles', /* * When using the "HasPermissions" trait from this package, we need to know which * table should be used to retrieve your permissions. We have chosen a basic * default value but you may easily change it to any table you like. */ 'permissions' => 'permissions', /* * When using the "HasPermissions" trait from this package, we need to know which * table should be used to retrieve your models permissions. We have chosen a * basic default value but you may easily change it to any table you like. */ 'model_has_permissions' => 'model_has_permissions', /* * When using the "HasRoles" trait from this package, we need to know which * table should be used to retrieve your models roles. We have chosen a * basic default value but you may easily change it to any table you like. */ 'model_has_roles' => 'model_has_roles', /* * When using the "HasRoles" trait from this package, we need to know which * table should be used to retrieve your roles permissions. We have chosen a * basic default value but you may easily change it to any table you like. */ 'role_has_permissions' => 'role_has_permissions', ], 'column_names' => [ /* * Change this if you want to name the related pivots other than defaults */ 'role_pivot_key' => null, //default 'role_id', 'permission_pivot_key' => null, //default 'permission_id', /* * Change this if you want to name the related model primary key other than * `model_id`. * * For example, this would be nice if your primary keys are all UUIDs. In * that case, name this `model_uuid`. */ 'model_morph_key' => 'model_id', /* * Change this if you want to use the teams feature and your related model's * foreign key is other than `team_id`. */ 'team_foreign_key' => 'team_id', ], /* * When set to true, the method for checking permissions will be registered on the gate. * Set this to false if you want to implement custom logic for checking permissions. */ 'register_permission_check_method' => true, /* * When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered * this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated * NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it. */ 'register_octane_reset_listener' => false, /* * Teams Feature. * When set to true the package implements teams using the 'team_foreign_key'. * If you want the migrations to register the 'team_foreign_key', you must * set this to true before doing the migration. * If you already did the migration then you must make a new migration to also * add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions' * (view the latest version of this package's migration file) */ 'teams' => false, /* * Passport Client Credentials Grant * When set to true the package will use Passports Client to check permissions */ 'use_passport_client_credentials' => false, /* * When set to true, the required permission names are added to exception messages. * This could be considered an information leak in some contexts, so the default * setting is false here for optimum safety. */ 'display_permission_in_exception' => false, /* * When set to true, the required role names are added to exception messages. * This could be considered an information leak in some contexts, so the default * setting is false here for optimum safety. */ 'display_role_in_exception' => false, /* * By default wildcard permission lookups are disabled. * See documentation to understand supported syntax. */ 'enable_wildcard_permission' => false, /* * The class to use for interpreting wildcard permissions. * If you need to modify delimiters, override the class and specify its name here. */ // 'permission.wildcard_permission' => Spatie\Permission\WildcardPermission::class, /* Cache-specific settings */ 'cache' => [ /* * By default all permissions are cached for 24 hours to speed up performance. * When permissions or roles are updated the cache is flushed automatically. */ 'expiration_time' => \DateInterval::createFromDateString('24 hours'), /* * The cache key used to store all permissions. */ 'key' => 'spatie.permission.cache', /* * You may optionally indicate a specific cache driver to use for permission and * role caching using any of the `store` drivers listed in the cache.php config * file. Using 'default' here means to use the `default` set in cache.php. */ 'store' => 'default', ], ]; ================================================ FILE: config/queue.php ================================================ env('QUEUE_CONNECTION', 'database'), /* |-------------------------------------------------------------------------- | Queue Connections |-------------------------------------------------------------------------- | | Here you may configure the connection options for every queue backend | used by your application. An example configuration is provided for | each backend supported by Laravel. You're also free to add more. | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" | */ 'connections' => [ 'sync' => [ 'driver' => 'sync', ], 'database' => [ 'driver' => 'database', 'connection' => env('DB_QUEUE_CONNECTION'), 'table' => env('DB_QUEUE_TABLE', 'jobs'), 'queue' => env('DB_QUEUE', 'default'), 'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90), 'after_commit' => false, ], 'beanstalkd' => [ 'driver' => 'beanstalkd', 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'), 'queue' => env('BEANSTALKD_QUEUE', 'default'), 'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90), 'block_for' => 0, 'after_commit' => false, ], 'sqs' => [ 'driver' => 'sqs', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 'queue' => env('SQS_QUEUE', 'default'), 'suffix' => env('SQS_SUFFIX'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 'after_commit' => false, ], 'redis' => [ 'driver' => 'redis', 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), 'queue' => env('REDIS_QUEUE', 'default'), 'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90), 'block_for' => null, 'after_commit' => false, ], ], /* |-------------------------------------------------------------------------- | Job Batching |-------------------------------------------------------------------------- | | The following options configure the database and table that store job | batching information. These options can be updated to any database | connection and table which has been defined by your application. | */ 'batching' => [ 'database' => env('DB_CONNECTION', 'sqlite'), 'table' => 'job_batches', ], /* |-------------------------------------------------------------------------- | Failed Queue Jobs |-------------------------------------------------------------------------- | | These options configure the behavior of failed queue job logging so you | can control how and where failed jobs are stored. Laravel ships with | support for storing failed jobs in a simple file or in a database. | | Supported drivers: "database-uuids", "dynamodb", "file", "null" | */ 'failed' => [ 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), 'database' => env('DB_CONNECTION', 'sqlite'), 'table' => 'failed_jobs', ], ]; ================================================ FILE: config/services.php ================================================ [ 'domain' => env('MAILGUN_DOMAIN'), 'secret' => env('MAILGUN_SECRET'), 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), ], 'postmark' => [ 'token' => env('POSTMARK_TOKEN'), ], 'ses' => [ 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), ], 'slack' => [ 'notifications' => [ 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), ], ], 'sentry' => [ 'enabled' => env('SENTRY_IO_ENABLED', 0), 'feedback-enabled' => env('SENTRY_IO_USER_FEEDBACK_ENABLED', 0), ], 'facebook' => [ 'enabled' => env('FACEBOOK_ENABLED', false), 'client_id' => env('FACEBOOK_KEY'), 'client_secret' => env('FACEBOOK_SECRET'), 'redirect' => env('FACEBOOK_REDIRECT_URI'), ], 'twitter' => [ 'enabled' => env('TWITTER_ENABLED', false), 'client_id' => env('TWITTER_CLIENT_ID'), 'client_secret' => env('TWITTER_CLIENT_SECRET'), 'redirect' => env('TWITTER_REDIRECT_URI'), ], 'instagram' => [ 'enabled' => env('INSTAGRAM_ENABLED', false), 'client_id' => env('INSTAGRAMBASIC_CLIENT_ID'), 'client_secret' => env('INSTAGRAMBASIC_CLIENT_SECRET'), 'redirect' => env('INSTAGRAMBASIC_REDIRECT_URI'), ], 'github' => [ 'enabled' => env('GITHUB_ENABLED', false), 'client_id' => env('GITHUB_KEY'), 'client_secret' => env('GITHUB_SECRET'), 'redirect' => env('GITHUB_REDIRECT_URI'), ], 'youtube' => [ 'enabled' => env('YOUTUBE_ENABLED', false), 'client_id' => env('YOUTUBE_KEY'), 'client_secret' => env('YOUTUBE_SECRET'), 'redirect' => env('YOUTUBE_REDIRECT_URI'), ], 'google' => [ 'ga' => env('GOOGLE_ANALYTICS_ID', null), 'gaEnabled' => env('GOOGLE_ANALYTICS_ENABLED', null), 'enabled' => env('GOOGLE_ENABLED', false), 'client_id' => env('GOOGLE_KEY'), 'client_secret' => env('GOOGLE_SECRET'), 'redirect' => env('GOOGLEREDIRECT_URI'), ], 'linkedin' => [ 'enabled' => env('LINKEDIN_ENABLED', false), 'client_id' => env('LINKEDIN_CLIENT_ID'), 'client_secret' => env('LINKEDIN_CLIENT_SECRET'), 'redirect' => env('LINKEDIN_REDIRECT_URI'), ], 'twitch' => [ 'enabled' => env('TWITCH_ENABLED', false), 'client_id' => env('TWITCH_KEY'), 'client_secret' => env('TWITCH_SECRET'), 'redirect' => env('TWITCH_REDIRECT_URI'), ], 'microsoft' => [ 'enabled' => env('MICROSOFT_ENABLED', false), 'client_id' => env('MICROSOFT_CLIENT_ID'), 'client_secret' => env('MICROSOFT_CLIENT_SECRET'), 'redirect' => env('MICROSOFT_REDIRECT_URI'), ], 'tiktok' => [ 'enabled' => env('TIKTOK_ENABLED', false), 'client_id' => env('TIKTOK_KEY'), 'client_secret' => env('TIKTOK_SECRET'), 'redirect' => env('TIKTOK_REDIRECT_URI'), ], 'apple' => [ 'enabled' => env('APPLE_ENABLED', false), 'client_id' => env('APPLE_CLIENT_ID'), 'client_secret' => env('APPLE_CLIENT_SECRET'), 'team_id' => env('APPLE_TEAM_ID'), 'key_id' => env('APPLE_KEY_ID'), 'private_key' => env('APPLE_PRIVATE_KEY'), 'redirect' => env('APPLE_REDIRECT_URI'), ], 'zoho' => [ 'enabled' => env('ZOHO_ENABLED', false), 'client_id' => env('ZOHO_CLIENT_ID'), 'client_secret' => env('ZOHO_CLIENT_SECRET'), 'redirect' => env('ZOHO_REDIRECT_URI'), ], 'stackexchange' => [ 'enabled' => env('STACKEXCHANGE_ENABLED', false), 'client_id' => env('STACKEXCHANGE_CLIENT_ID'), 'client_secret' => env('STACKEXCHANGE_CLIENT_SECRET'), 'redirect' => env('STACKEXCHANGE_REDIRECT_URI'), 'key' => env('STACKEXCHANGE_CLIENT_KEY'), 'site' => env('STACKEXCHANGE_CLIENT_SITE', 'stackoverflow'), ], 'gitlab' => [ 'enabled' => env('GITLAB_ENABLED', false), 'client_id' => env('GITLAB_CLIENT_ID'), 'client_secret' => env('GITLAB_CLIENT_SECRET'), 'redirect' => env('GITLAB_REDIRECT_URI'), ], 'reddit' => [ 'enabled' => env('REDDIT_ENABLED', false), 'client_id' => env('REDDIT_CLIENT_ID'), 'client_secret' => env('REDDIT_CLIENT_SECRET'), 'response_type' => env('REDDIT_RESPONSE_TYPE', 'code'), 'state' => env('REDDIT_STATE', 'r@nd0m5tr1n6'), 'redirect' => env('REDDIT_REDIRECT_URI'), ], 'snapchat' => [ 'enabled' => env('SNAPCHAT_ENABLED', false), 'client_id' => env('SNAPCHAT_CLIENT_ID'), 'client_secret' => env('SNAPCHAT_CLIENT_SECRET'), 'redirect' => env('SNAPCHAT_REDIRECT_URI'), ], 'meetup' => [ 'enabled' => env('MEETUP_ENABLED', false), 'client_id' => env('MEETUP_CLIENT_ID'), 'client_secret' => env('MEETUP_CLIENT_SECRET'), 'redirect' => env('MEETUP_REDIRECT_URI'), ], 'atlassian' => [ 'enabled' => env('ATLASSIAN_ENABLED', false), 'client_id' => env('ATLASSIAN_CLIENT_ID'), 'client_secret' => env('ATLASSIAN_CLIENT_SECRET'), 'redirect' => env('ATLASSIAN_REDIRECT_URI'), ], // NEW_PROVIDER_PLUG :: Put New Provider HERE 'trello' => [ 'enabled' => env('TRELLO_ENABLED', false), 'client_id' => env('TRELLO_CLIENT_ID'), 'client_secret' => env('TRELLO_CLIENT_SECRET'), 'redirect' => env('TRELLO_REDIRECT_URI'), ], 'zoom' => [ 'enabled' => env('ZOOM_ENABLED', false), 'client_id' => env('ZOOM_CLIENT_ID'), 'client_secret' => env('ZOOM_CLIENT_SECRET'), 'redirect' => env('ZOOM_REDIRECT_URI'), ], 'mailchimp' => [ 'enabled' => env('MAILCHIMP_ENABLED', false), 'client_id' => env('MAILCHIMP_CLIENT_ID'), 'client_secret' => env('MAILCHIMP_CLIENT_SECRET'), 'redirect' => env('MAILCHIMP_REDIRECT_URI'), ], 'disqus' => [ 'enabled' => env('DISQUS_ENABLED', false), 'client_id' => env('DISQUS_CLIENT_ID'), 'client_secret' => env('DISQUS_CLIENT_SECRET'), 'redirect' => env('DISQUS_REDIRECT_URI'), ], 'patreon' => [ 'enabled' => env('PATREON_ENABLED', false), 'client_id' => env('PATREON_CLIENT_ID'), 'client_secret' => env('PATREON_CLIENT_SECRET'), 'redirect' => env('PATREON_REDIRECT_URI'), ], 'paypal' => [ 'enabled' => env('PAYPAL_ENABLED', false), 'client_id' => env('PAYPAL_CLIENT_ID'), 'client_secret' => env('PAYPAL_CLIENT_SECRET'), 'redirect' => env('PAYPAL_REDIRECT_URI'), ], 'stripe' => [ 'enabled' => env('STRIPE_ENABLED', false), 'client_id' => env('STRIPE_CLIENT_ID'), 'client_secret' => env('STRIPE_CLIENT_SECRET'), 'redirect' => env('STRIPE_REDIRECT_URI'), ], 'venmo' => [ 'enabled' => env('VENMO_ENABLED', false), 'client_id' => env('VENMO_CLIENT_ID'), 'client_secret' => env('VENMO_CLIENT_SECRET'), 'redirect' => env('VENMO_REDIRECT_URI'), ], 'soundcloud' => [ 'enabled' => env('SOUNDCLOUD_ENABLED', false), 'client_id' => env('SOUNDCLOUD_CLIENT_ID'), 'client_secret' => env('SOUNDCLOUD_CLIENT_SECRET'), 'redirect' => env('SOUNDCLOUD_REDIRECT_URI'), ], 'spotify' => [ 'enabled' => env('SPOTIFY_ENABLED', false), 'client_id' => env('SPOTIFY_CLIENT_ID'), 'client_secret' => env('SPOTIFY_CLIENT_SECRET'), 'redirect' => env('SPOTIFY_REDIRECT_URI'), ], 'arcgis' => [ 'enabled' => env('ARCGIS_ENABLED', false), 'client_id' => env('ARCGIS_CLIENT_ID'), 'client_secret' => env('ARCGIS_CLIENT_SECRET'), 'redirect' => env('ARCGIS_REDIRECT_URI'), ], 'fitbit' => [ 'enabled' => env('FITBIT_ENABLED', false), 'client_id' => env('FITBIT_CLIENT_ID'), 'client_secret' => env('FITBIT_CLIENT_SECRET'), 'redirect' => env('FITBIT_REDIRECT_URI'), ], 'uber' => [ 'enabled' => env('UBER_ENABLED', false), 'client_id' => env('UBER_CLIENT_ID'), 'client_secret' => env('UBER_CLIENT_SECRET'), 'redirect' => env('UBER_REDIRECT_URI'), ], 'amazon' => [ 'enabled' => env('AMAZON_ENABLED', false), 'client_id' => env('AMAZON_SIGNIN_CLIENT_ID'), 'client_secret' => env('AMAZON_SIGNIN_SECRET'), 'redirect' => env('AMAZON_SIGNIN_REDIRECT_URI'), ], 'keycloak' => [ 'enabled' => env('KEYCLOAK_ENABLED', false), 'client_id' => env('KEYCLOAK_CLIENT_ID'), 'client_secret' => env('KEYCLOAK_CLIENT_SECRET'), 'redirect' => env('KEYCLOAK_REDIRECT_URI'), 'base_url' => env('KEYCLOAK_BASE_URL'), // Specify your keycloak server URL here 'realms' => env('KEYCLOAK_REALM'), // Specify your keycloak realm ], ]; ================================================ FILE: config/session.php ================================================ env('SESSION_DRIVER', 'database'), /* |-------------------------------------------------------------------------- | Session Lifetime |-------------------------------------------------------------------------- | | Here you may specify the number of minutes that you wish the session | to be allowed to remain idle before it expires. If you want them | to expire immediately when the browser is closed then you may | indicate that via the expire_on_close configuration option. | */ 'lifetime' => env('SESSION_LIFETIME', 120), 'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false), /* |-------------------------------------------------------------------------- | Session Encryption |-------------------------------------------------------------------------- | | This option allows you to easily specify that all of your session data | should be encrypted before it's stored. All encryption is performed | automatically by Laravel and you may use the session like normal. | */ 'encrypt' => env('SESSION_ENCRYPT', false), /* |-------------------------------------------------------------------------- | Session File Location |-------------------------------------------------------------------------- | | When utilizing the "file" session driver, the session files are placed | on disk. The default storage location is defined here; however, you | are free to provide another location where they should be stored. | */ 'files' => storage_path('framework/sessions'), /* |-------------------------------------------------------------------------- | Session Database Connection |-------------------------------------------------------------------------- | | When using the "database" or "redis" session drivers, you may specify a | connection that should be used to manage these sessions. This should | correspond to a connection in your database configuration options. | */ 'connection' => env('SESSION_CONNECTION'), /* |-------------------------------------------------------------------------- | Session Database Table |-------------------------------------------------------------------------- | | When using the "database" session driver, you may specify the table to | be used to store sessions. Of course, a sensible default is defined | for you; however, you're welcome to change this to another table. | */ 'table' => env('SESSION_TABLE', 'sessions'), /* |-------------------------------------------------------------------------- | Session Cache Store |-------------------------------------------------------------------------- | | When using one of the framework's cache driven session backends, you may | define the cache store which should be used to store the session data | between requests. This must match one of your defined cache stores. | | Affects: "apc", "dynamodb", "memcached", "redis" | */ 'store' => env('SESSION_STORE'), /* |-------------------------------------------------------------------------- | Session Sweeping Lottery |-------------------------------------------------------------------------- | | Some session drivers must manually sweep their storage location to get | rid of old sessions from storage. Here are the chances that it will | happen on a given request. By default, the odds are 2 out of 100. | */ 'lottery' => [2, 100], /* |-------------------------------------------------------------------------- | Session Cookie Name |-------------------------------------------------------------------------- | | Here you may change the name of the session cookie that is created by | the framework. Typically, you should not need to change this value | since doing so does not grant a meaningful security improvement. | */ 'cookie' => env( 'SESSION_COOKIE', Str::slug(env('APP_NAME', 'laravel'), '_').'_session' ), /* |-------------------------------------------------------------------------- | Session Cookie Path |-------------------------------------------------------------------------- | | The session cookie path determines the path for which the cookie will | be regarded as available. Typically, this will be the root path of | your application, but you're free to change this when necessary. | */ 'path' => env('SESSION_PATH', '/'), /* |-------------------------------------------------------------------------- | Session Cookie Domain |-------------------------------------------------------------------------- | | This value determines the domain and subdomains the session cookie is | available to. By default, the cookie will be available to the root | domain and all subdomains. Typically, this shouldn't be changed. | */ 'domain' => env('SESSION_DOMAIN'), /* |-------------------------------------------------------------------------- | HTTPS Only Cookies |-------------------------------------------------------------------------- | | By setting this option to true, session cookies will only be sent back | to the server if the browser has a HTTPS connection. This will keep | the cookie from being sent to you when it can't be done securely. | */ 'secure' => env('SESSION_SECURE_COOKIE'), /* |-------------------------------------------------------------------------- | HTTP Access Only |-------------------------------------------------------------------------- | | Setting this value to true will prevent JavaScript from accessing the | value of the cookie and the cookie will only be accessible through | the HTTP protocol. It's unlikely you should disable this option. | */ 'http_only' => env('SESSION_HTTP_ONLY', true), /* |-------------------------------------------------------------------------- | Same-Site Cookies |-------------------------------------------------------------------------- | | This option determines how your cookies behave when cross-site requests | take place, and can be used to mitigate CSRF attacks. By default, we | will set this value to "lax" to permit secure cross-site requests. | | See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value | | Supported: "lax", "strict", "none", null | */ 'same_site' => env('SESSION_SAME_SITE', 'lax'), /* |-------------------------------------------------------------------------- | Partitioned Cookies |-------------------------------------------------------------------------- | | Setting this value to true will tie the cookie to the top-level site for | a cross-site context. Partitioned cookies are accepted by the browser | when flagged "secure" and the Same-Site attribute is set to "none". | */ 'partitioned' => env('SESSION_PARTITIONED_COOKIE', false), ]; ================================================ FILE: database/.gitignore ================================================ *.sqlite* ================================================ FILE: database/factories/CourseFactory.php ================================================ */ class CourseFactory extends Factory { /** * Define the model's default state. * * @return array */ public function definition() { $name = $this->faker->sentence(6); return [ 'title' => $name, 'slug' => Str::slug($name), 'description' => $this->faker->text(), 'price' => $this->faker->randomFloat(2, 0, 199), 'thumbnail' => $this->faker->imageUrl($width = 640, $height = 480), 'status' => 1, 'created_by' => rand(1, 5), 'updated_by' => rand(1, 5), ]; } } ================================================ FILE: database/factories/ExamFactory.php ================================================ */ class ExamFactory extends Factory { /** * Define the model's default state. * * @return array */ public function definition() { return [ 'title' => $this->faker->sentence(6), 'description' => $this->faker->paragraph(), 'examiner' => rand(1, 10), 'status' => rand(1, 4), 'price' => 100, 'duration' => rand(30, 150), 'pass_mark' => 20, 'number_of_questions' => 10, 'certification' => false, ]; } } ================================================ FILE: database/factories/LessonFactory.php ================================================ */ class LessonFactory extends Factory { /** * Define the model's default state. * * @return array */ public function definition() { $name = $this->faker->text(50); return [ 'title' => $name, 'thumbnail' => $this->faker->imageUrl($width = 640, $height = 480), 'slug' => Str::slug($name), 'short_text' => $this->faker->paragraph(), 'full_text' => $this->faker->text(1000), 'position' => rand(1, 10), 'status' => rand(1, 3), 'created_by' => rand(1, 5), 'updated_by' => rand(1, 5), ]; } } ================================================ FILE: database/factories/QuestionFactory.php ================================================ */ class QuestionFactory extends Factory { /** * Define the model's default state. * * @return array */ public function definition() { $answer = Carbon::now()->isoFormat("x") . '-' . rand(100, 999); return [ 'created_by' => rand(1, 10), 'question' => $this->faker->sentence(10), 'options' => [ [ 'option' => 'true', 'id' => $answer ], [ 'option' => 'false', 'id' => Carbon::now()->isoFormat("x") . '-' . rand(100, 999) ], [ 'option' => 'both', 'id' => Carbon::now()->isoFormat("x") . '-' . rand(100, 999) ], [ 'option' => 'non', 'id' => Carbon::now()->isoFormat("x") . '-' . rand(100, 999) ] ], 'answer' => [$answer], 'hint' => 'Answer always true', 'explanation' => 'you are giving answer form faker generate question', 'exam_id' => rand(1, 10), ]; } } ================================================ FILE: database/factories/SectionFactory.php ================================================ */ class SectionFactory extends Factory { /** * Define the model's default state. * * @return array */ public function definition() { $name = ['Intruduction', 'Basic', 'Get into Deep', 'Advance', 'Example', 'Practices']; return [ 'title' => $name[rand(0, 5)], 'description' => $this->faker->paragraph(), ]; } } ================================================ FILE: database/factories/SubjectFactory.php ================================================ */ class SubjectFactory extends Factory { /** * Define the model's default state. * * @return array */ public function definition() { return [ 'title' => $this->faker->title, 'description' => $this->faker->paragraph() ]; } } ================================================ FILE: database/factories/TopicFactory.php ================================================ */ class TopicFactory extends Factory { /** * Define the model's default state. * * @return array */ public function definition() { return [ 'title' => $this->faker->unique()->words(rand(2, 4), true), 'description' => $this->faker->paragraph(), ]; } } ================================================ FILE: database/factories/UserFactory.php ================================================ */ class UserFactory extends Factory { /** * The current password being used by the factory. */ protected static ?string $password; /** * Define the model's default state. * * @return array */ public function definition(): array { return [ 'firstname' => $this->faker->firstName, 'lastname' => $this->faker->lastName, 'avatar' => 'http://lorempixel.com/80/60/', 'name' => $this->faker->unique()->userName, 'email' => $this->faker->unique()->safeEmail, 'email_verified_at' => now(), 'password' => static::$password ??= Hash::make('password'), 'ip' => $this->faker->ipv6, 'remember_token' => Str::random(10), ]; } /** * Indicate that the model's email address should be unverified. */ public function unverified(): static { return $this->state(fn (array $attributes) => [ 'email_verified_at' => null, ]); } public function banned(): static { return $this->state(fn(array $attributes) => [ 'deleted_at' => now() ]); } } ================================================ FILE: database/migrations/0001_01_01_000000_create_users_table.php ================================================ id(); $table->string('name'); $table->string('firstname')->nullable(); $table->string('lastname')->nullable(); $table->string('email')->unique(); $table->string('avatar')->nullable(); $table->string('password'); $table->timestamp('email_verified_at')->nullable(); $table->timestamp('last_loged_in')->useCurrent(); $table->ipAddress('ip')->nullable(); $table->rememberToken(); $table->timestamps(); $table->softDeletes(); }); Schema::create('password_reset_tokens', function (Blueprint $table) { $table->string('email')->primary(); $table->string('token'); $table->timestamp('created_at')->nullable(); }); Schema::create('sessions', function (Blueprint $table) { $table->string('id')->primary(); $table->foreignId('user_id')->nullable()->index(); $table->string('ip_address', 45)->nullable(); $table->text('user_agent')->nullable(); $table->longText('payload'); $table->integer('last_activity')->index(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('users'); Schema::dropIfExists('password_reset_tokens'); Schema::dropIfExists('sessions'); } }; ================================================ FILE: database/migrations/0001_01_01_000001_create_cache_table.php ================================================ string('key')->primary(); $table->mediumText('value'); $table->integer('expiration'); }); Schema::create('cache_locks', function (Blueprint $table) { $table->string('key')->primary(); $table->string('owner'); $table->integer('expiration'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('cache'); Schema::dropIfExists('cache_locks'); } }; ================================================ FILE: database/migrations/0001_01_01_000002_create_jobs_table.php ================================================ id(); $table->string('queue')->index(); $table->longText('payload'); $table->unsignedTinyInteger('attempts'); $table->unsignedInteger('reserved_at')->nullable(); $table->unsignedInteger('available_at'); $table->unsignedInteger('created_at'); }); Schema::create('job_batches', function (Blueprint $table) { $table->string('id')->primary(); $table->string('name'); $table->integer('total_jobs'); $table->integer('pending_jobs'); $table->integer('failed_jobs'); $table->longText('failed_job_ids'); $table->mediumText('options')->nullable(); $table->integer('cancelled_at')->nullable(); $table->integer('created_at'); $table->integer('finished_at')->nullable(); }); Schema::create('failed_jobs', function (Blueprint $table) { $table->id(); $table->string('uuid')->unique(); $table->text('connection'); $table->text('queue'); $table->longText('payload'); $table->longText('exception'); $table->timestamp('failed_at')->useCurrent(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('jobs'); Schema::dropIfExists('job_batches'); Schema::dropIfExists('failed_jobs'); } }; ================================================ FILE: database/migrations/2024_05_07_000001_create_personal_access_tokens_table.php ================================================ id(); $table->morphs('tokenable'); $table->string('name'); $table->string('token', 64)->unique(); $table->text('abilities')->nullable(); $table->timestamp('last_used_at')->nullable(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('personal_access_tokens'); } }; ================================================ FILE: database/migrations/2024_05_07_054845_create_files_table.php ================================================ id(); $table->string('name')->index(); $table->string('description', 150)->nullable(); $table->string('path')->nullable()->index(); $table->string('type', 20)->nullable()->index(); $table->string('public_path', 255)->nullable(); $table->string('extension', 10)->nullable(); $table->string('mime', 50)->nullable(); $table->bigInteger('file_size')->nullable()->unsigned(); $table->string('file_name', 255); $table->integer('parent_id')->nullable(); $table->string('driver')->nullable(); $table->string('driver_data')->nullable(); $table->boolean('isdraft')->default(true); $table->integer('uploaded_by')->nullable(); $table->integer('deleted_by')->nullable(); $table->json('meta')->nullable(); $table->json('permissions')->nullable(); $table->timestamps(); $table->softDeletes(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('files'); } }; ================================================ FILE: database/migrations/2024_05_07_054954_create_fileables_table.php ================================================ bigInteger('file_id')->unsigned(); $table->morphs('fileable'); $table->foreign('file_id') ->references('id') ->on('files') ->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('fileables'); } }; ================================================ FILE: database/migrations/2024_05_07_092254_create_settings_table.php ================================================ id(); $table->morphs('resource'); $table->string("key"); $table->string("value"); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('settings'); } }; ================================================ FILE: database/migrations/2024_05_07_112940_create_permission_tables.php ================================================ bigIncrements('id'); // permission id $table->string('name'); // For MySQL 8.0 use string('name', 125); $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); $table->timestamps(); $table->unique(['name', 'guard_name']); }); Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames) { $table->bigIncrements('id'); // role id if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); } $table->string('name'); // For MySQL 8.0 use string('name', 125); $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); $table->timestamps(); if ($teams || config('permission.testing')) { $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); } else { $table->unique(['name', 'guard_name']); } }); Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) { $table->unsignedBigInteger($pivotPermission); $table->string('model_type'); $table->unsignedBigInteger($columnNames['model_morph_key']); $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); $table->foreign($pivotPermission) ->references('id') // permission id ->on($tableNames['permissions']) ->onDelete('cascade'); if ($teams) { $table->unsignedBigInteger($columnNames['team_foreign_key']); $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); $table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_permission_model_type_primary'); } else { $table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_permission_model_type_primary'); } }); Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) { $table->unsignedBigInteger($pivotRole); $table->string('model_type'); $table->unsignedBigInteger($columnNames['model_morph_key']); $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); $table->foreign($pivotRole) ->references('id') // role id ->on($tableNames['roles']) ->onDelete('cascade'); if ($teams) { $table->unsignedBigInteger($columnNames['team_foreign_key']); $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); $table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'], 'model_has_roles_role_model_type_primary'); } else { $table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'], 'model_has_roles_role_model_type_primary'); } }); Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) { $table->unsignedBigInteger($pivotPermission); $table->unsignedBigInteger($pivotRole); $table->foreign($pivotPermission) ->references('id') // permission id ->on($tableNames['permissions']) ->onDelete('cascade'); $table->foreign($pivotRole) ->references('id') // role id ->on($tableNames['roles']) ->onDelete('cascade'); $table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary'); }); app('cache') ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null) ->forget(config('permission.cache.key')); } /** * Reverse the migrations. */ public function down(): void { $tableNames = config('permission.table_names'); if (empty($tableNames)) { throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'); } Schema::drop($tableNames['role_has_permissions']); Schema::drop($tableNames['model_has_roles']); Schema::drop($tableNames['model_has_permissions']); Schema::drop($tableNames['roles']); Schema::drop($tableNames['permissions']); } }; ================================================ FILE: database/migrations/2024_05_07_142204_create_sections_table.php ================================================ id(); $table->string('title'); $table->text('description'); $table->unsignedBigInteger('course_id')->index(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('sections'); } }; ================================================ FILE: database/migrations/2024_05_07_145026_create_subjects_table.php ================================================ id(); $table->string('title'); $table->string('slug'); $table->text('description')->nullable(); $table->bigInteger('parent')->default(0); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('subjects'); } }; ================================================ FILE: database/migrations/2024_05_07_152004_create_sectionables_table.php ================================================ id(); $table->bigInteger('section_id'); $table->morphs('sectionable'); $table->unsignedTinyInteger('order')->default(0); $table->bigInteger('course_id'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('sectionables'); } }; ================================================ FILE: database/migrations/2024_05_07_153027_create_exams_table.php ================================================ id(); $table->bigInteger('examiner')->unsigned(); $table->string('title'); $table->text("description")->nullable(); $table->unsignedTinyInteger('status')->default(1)->index()->comment("1=>free, 2=>course, 3=>course & paid, 4=>paid"); $table->integer("price")->nullable(); $table->unsignedTinyInteger('duration')->default(0); $table->unsignedTinyInteger('pass_mark')->default(0); $table->unsignedTinyInteger('number_of_questions')->default(0); $table->boolean('random_questions')->default(true); $table->boolean('certification')->default(false); $table->unsignedTinyInteger("difficulty")->default(1); $table->json('meta')->nullable(); $table->timestamps(); $table->softDeletes()->index(); $table->foreign('examiner')->references('id')->on('users')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('exams'); } }; ================================================ FILE: database/migrations/2024_05_07_153507_create_questions_table.php ================================================ id(); $table->bigInteger('created_by')->unsigned(); $table->tinyInteger( 'qtype' )->default( 0 )->comment( '0: Objective; 1: True/False;'); $table->string("question"); $table->json("options"); $table->json("answers"); $table->string("hint")->nullable(); $table->integer("mark")->default(1); $table->integer("nmark")->default(0); $table->string("explanation")->nullable(); $table->bigInteger('exam_id')->unsigned()->index(); $table->timestamps(); $table->softDeletes()->index(); $table->foreign('created_by')->references('id')->on('users')->onDelete('cascade'); $table->foreign('exam_id')->references('id')->on('exams')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('questions'); } }; ================================================ FILE: database/migrations/2024_05_07_170835_create_topics_table.php ================================================ id(); $table->string("title"); $table->text("description")->nullable(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('topics'); } }; ================================================ FILE: database/migrations/2024_05_07_180360_create_topicables_table.php ================================================ bigInteger('topic_id')->unsigned(); $table->morphs('topicable'); $table->foreign('topic_id') ->references('id')->on('topics') ->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('topicables'); } }; ================================================ FILE: database/migrations/2024_05_07_190045_create_results_table.php ================================================ id(); $table->bigInteger('examinee'); $table->bigInteger('exam_id'); $table->json('answers'); $table->float('obtain_mark'); $table->boolean('is_pass')->default(false); $table->float('time_taken'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('results'); } }; ================================================ FILE: database/migrations/2024_05_07_191956_create_courses_table.php ================================================ id(); $table->string('title'); $table->string('subtitle')->nullable(); $table->string('slug')->nullable(); $table->text('description')->nullable(); $table->text('features')->nullable(); $table->text('requirements')->nullable(); $table->decimal('price', 15, 2)->nullable(); $table->integer('discount')->nullable(); $table->string('thumbnail')->nullable(); $table->date('start_date')->nullable(); $table->tinyInteger('certified')->default(1); $table->tinyInteger('status')->default(0); $table->bigInteger('created_by')->unsigned(); $table->bigInteger('updated_by')->unsigned(); $table->timestamps(); $table->softDeletes(); $table->index(['deleted_at']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('courses'); } }; ================================================ FILE: database/migrations/2024_05_07_193251_create_lessons_table.php ================================================ id(); $table->string('title')->nullable(); $table->string('slug')->nullable(); $table->string('thumbnail')->nullable(); $table->unsignedTinyInteger('type')->default(1)->comment("1=>text, 2=> video, 3=>audio, 4=> pdf "); $table->json('object')->nullable(); $table->text('short_text')->nullable(); $table->text('full_text')->nullable(); $table->unsignedTinyInteger('position')->default(1); $table->unsignedTinyInteger('status')->default(1)->comment("1=>free, 2=>subscriber, 3=>paid"); $table->timestamps(); $table->softDeletes(); $table->bigInteger('created_by')->unsigned(); $table->bigInteger('updated_by')->unsigned(); $table->index(['deleted_at']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('lessons'); } }; ================================================ FILE: database/migrations/2024_05_07_195152_create_subjectables_table.php ================================================ bigInteger('subject_id')->unsigned(); $table->morphs('subjectables'); $table->foreign('subject_id') ->references('id')->on('subjects') ->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('subjectables'); } }; ================================================ FILE: database/migrations/2024_05_07_200921_create_course_students_table.php ================================================ bigInteger('course_id')->unsigned(); $table->foreign('course_id')->references('id')->on('courses')->onDelete('cascade'); $table->bigInteger('user_id')->unsigned(); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->integer('rating')->unsigned()->default(0); $table->integer('progress')->unsigned()->default(0); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('course_students'); } }; ================================================ FILE: database/migrations/2024_05_07_201001_create_course_teachers_table.php ================================================ bigInteger('course_id')->unsigned(); $table->foreign('course_id')->references('id')->on('courses')->onDelete('cascade'); $table->bigInteger('user_id')->unsigned(); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('course_teachers'); } }; ================================================ FILE: database/migrations/2024_05_07_203101_create_lesson_student_table.php ================================================ bigInteger('lesson_id')->unsigned(); $table->foreign('lesson_id')->references('id')->on('lessons')->onDelete('cascade'); $table->bigInteger('user_id')->unsigned(); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->integer('status')->unsigned()->default(0); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('lesson_student'); } }; ================================================ FILE: database/migrations/2024_07_23_104822_store_last_learning.php ================================================ json('last_lesson')->nullable()->default(null); }); } /** * Reverse the migrations. */ public function down(): void { // } }; ================================================ FILE: database/migrations/2024_08_13_073632_create_socialite_providers_table.php ================================================ id(); $table->unsignedBigInteger('user_id')->index(); $table->string('provider'); $table->string('provider_user_id')->index(); $table->longText('access_token')->nullable(); $table->longText('refresh_token')->nullable(); $table->longText('avatar')->nullable(); $table->timestamps(); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('socialite_providers'); } }; ================================================ FILE: database/migrations/2026_04_25_000001_add_icon_and_image_to_subjects_table.php ================================================ string('icon')->nullable()->after('description'); $table->string('image')->nullable()->after('icon'); }); } public function down(): void { Schema::table('subjects', function (Blueprint $table) { $table->dropColumn(['icon', 'image']); }); } }; ================================================ FILE: database/seeders/CourseSeed.php ================================================ command->ask('How Course seed do you need ?', 10); Course::factory()->count($count)->create()->each(function ($course) { $course->teachers()->sync(rand(1, 3)); $course->teachers()->sync(rand(1, 10)); $course->students()->sync(rand(1, 3)); $course->students()->sync(rand(1, 10)); $course->subjects()->attach(rand(1, 100)); $course->topics()->attach([rand(1, 5), rand(1, 10), rand(1, 10), rand(1, 10)]); $sessions = Section::factory()->count(5)->create(['course_id' => $course->id]); Lesson::factory()->count(15)->create()->each(function ($lesson) use ($sessions, $course) { $course->lessons()->save($lesson, [ 'section_id' => $sessions->random()->id, 'sectionable_type' => Lesson::class, 'order' => rand(1, 15) ]); }); Exam::factory()->count(rand(3, 8))->create()->each(function ($exam) use ($sessions, $course) { $exam->topics()->attach([rand(1, 5), rand(1, 10), rand(1, 10), rand(1, 10)]); $exam->subjects()->attach(rand(1, 100)); Question::factory()->count(20)->create(['exam_id' => $exam->id]); $course->exams()->save($exam, [ 'section_id' => $sessions->random()->id, 'sectionable_type' => Exam::class, 'order' => rand(15, 30) ]); }); }); } } ================================================ FILE: database/seeders/DatabaseSeeder.php ================================================ getDriverName(); if ($driver === 'mysql') { DB::statement('SET FOREIGN_KEY_CHECKS = 0'); } $this->call([ UserSeed::class, RoleSeed::class, SubjectSeed::class, TopicSeed::class, CourseSeed::class ]); // Re-enable foreign key checks for MySQL if ($driver === 'mysql') { DB::statement('SET FOREIGN_KEY_CHECKS = 1'); } } } ================================================ FILE: database/seeders/RoleSeed.php ================================================ 'superadmin']); Role::create(['name' => 'admin']); Role::create(['name' => 'instructor']); Permission::create(['name' => 'course.create ']); Permission::create(['name' => 'course.edit']); Permission::create(['name' => 'course.delete']); Permission::create(['name' => 'exam.create ']); Permission::create(['name' => 'exam.edit']); Permission::create(['name' => 'exam.delete']); Permission::create(['name' => 'question.create ']); Permission::create(['name' => 'question.edit']); Permission::create(['name' => 'question.delete']); Permission::create(['name' => 'lesson.create ']); Permission::create(['name' => 'lesson.edit']); Permission::create(['name' => 'lesson.delete']); Permission::create(['name' => 'topic.create ']); Permission::create(['name' => 'topic.edit']); Permission::create(['name' => 'topic.delete']); Permission::create(['name' => 'session.create ']); Permission::create(['name' => 'session.edit']); Permission::create(['name' => 'session.delete']); Permission::create(['name' => 'subject.create ']); Permission::create(['name' => 'subject.edit']); Permission::create(['name' => 'subject.delete']); Permission::create(['name' => 'file.view.all']); Permission::create(['name' => 'file.make.private']); Permission::create(['name' => 'file.view.private']); DB::table('model_has_roles')->insert([ 'role_id' => '1', 'model_id' => '1', 'model_type' => User::class ]); } } ================================================ FILE: database/seeders/SubjectSeed.php ================================================ 'CodeBracketIcon', 'Business' => 'BriefcaseIcon', 'Finance & Accounting' => 'BanknotesIcon', 'IT & Software' => 'ComputerDesktopIcon', 'Office Productivity' => 'DocumentTextIcon', 'Personal Development' => 'LightBulbIcon', 'Design' => 'PaintBrushIcon', 'Marketing' => 'MegaphoneIcon', 'Lifestyle' => 'SparklesIcon', 'Photography' => 'CameraIcon', 'Health & Fitness' => 'HeartIcon', 'Music' => 'MusicalNoteIcon', 'Teaching & Academics' => 'AcademicCapIcon', ]; /** Icon pool used when picking randomly for child subjects. */ private array $iconPool = [ 'BookOpenIcon', 'BeakerIcon', 'ChartBarIcon', 'CpuChipIcon', 'GlobeAltIcon', 'RocketLaunchIcon', 'StarIcon', 'TrophyIcon', 'PuzzlePieceIcon', 'FireIcon', 'WrenchScrewdriverIcon', 'KeyIcon', 'SignalIcon', 'CircleStackIcon', 'CommandLineIcon', 'ShieldCheckIcon', 'CloudIcon', 'TableCellsIcon', 'CalculatorIcon', 'FilmIcon', 'LanguageIcon', 'ChatBubbleLeftRightIcon', 'CursorArrowRaysIcon', 'SwatchIcon', 'MicrophoneIcon', 'TicketIcon', 'FlagIcon', 'TagIcon', ]; private function generateImage(): string { $dir = storage_path('app/public/subjects'); if (! is_dir($dir)) { mkdir($dir, 0755, true); } // Generate a simple colored SVG — no HTTP request, instant $colors = ['4f46e5', '0891b2', '059669', 'd97706', 'dc2626', '7c3aed', 'db2777', '0284c7']; $bg = $colors[array_rand($colors)]; $filename = Str::uuid() . '.svg'; $svg = << SVG; file_put_contents("{$dir}/{$filename}", $svg); return "subjects/{$filename}"; } /** * Run the database seeds. */ public function run(): void { // Truncate and reset primary key sequence (PostgreSQL) DB::statement('TRUNCATE TABLE subjects RESTART IDENTITY CASCADE'); // Also clear old generated images Storage::disk('public')->deleteDirectory('subjects'); $subjects = [ 'Development' => [ "Web Development", "Data Science", "Mobile Apps", "Programming Languages", "Game Development", "Databases", "Software Testing", "Software Engineering", "Development Tools", "E-Commerce", ], "Business" => [ "Finance", "Entrepreneurship", "Communications", "Management", "Sales", "Strategy", "Operations", "Project Management", "Business Law", "Data & Analytics", "Home Business", "Human Resources", "Industry", "Media", "Real Estate", ], "Finance & Accounting" => [ "Accounting & Bookkeeping", "Compliance", "Cryptocurrency & Blockchain", "Economics", "Finance", "Finance Cert & Exam Prep", "Financial Modeling & Analysis", "Investing & Trading", "Money Management Tools", "Taxes", "Other Finance & Economics", ], "IT & Software" => [ "IT Certification", "Network & Security", "Hardware", "Operating Systems", "Other", ], "Office Productivity" => [ "Microsoft", "Apple", "Google", "SAP", "Oracle", "Other", ], "Personal Development" => [ "Personal Transformation", "Productivity", "Leadership", "Personal Finance", "Career Development", "Parenting & Relationships", "Happiness", "Religion & Spirituality", "Personal Brand Building", "Creativity", "Influence", "Self Esteem", "Stress Management", "Memory & Study Skills", "Motivation", "Other", ], "Design" => [ "Web Design", "Graphic Design", "Design Tools", "User Experience", "Game Design", "Design Thinking", "3D & Animation", "Fashion", "Architectural Design", "Interior Design", "Other", ], "Marketing" => [ "Digital Marketing", "Search Engine Optimization", "Social Media Marketing", "Branding", "Marketing Fundamentals", "Analytics & Automation", "Public Relations", "Advertising", "Video & Mobile Marketing", "Content Marketing", "Growth Hacking", "Affiliate Marketing", "Product Marketing", "Other", ], "Lifestyle" => [ "Arts & Crafts", "Food & Beverage", "Beauty & Makeup", "Travel", "Gaming", "Home Improvement", "Pet Care & Training", "Other", ], "Photography" => [ "Digital Photography", "Photography Fundamentals", "Portraits", "Photography Tools", "Commercial Photography", "Video Design", "Other", ], "Health & Fitness" => [ "Fitness", "General Health", "Sports", "Nutrition", "Yoga", "Mental Health", "Dieting", "Self Defense", "Safety & First Aid", "Dance", "Meditation", "Other", ], "Music" => [ "Instruments", "Production", "Music Fundamentals", "Vocal", "Music Techniques", "Music Software", "Other", ], "Teaching & Academics" => [ "Engineering", "Humanities", "Math", "Science", "Online Education", "Social Science", "Language", "Teacher Training", "Test Prep", "Other Teaching & Academics", ], ]; foreach ($subjects as $key => $value) { $p = Subject::create([ 'title' => $key, 'slug' => Str::slug($key), 'icon' => $this->categoryIcons[$key] ?? $this->iconPool[array_rand($this->iconPool)], 'image' => $this->generateImage(), ]); foreach ($value as $subject) { Subject::create([ 'title' => $subject, 'parent' => $p->id, 'slug' => Str::slug($subject), 'icon' => $this->iconPool[array_rand($this->iconPool)], 'image' => $this->generateImage(), ]); } } } } ================================================ FILE: database/seeders/TopicSeed.php ================================================ count(50)->create(); } } ================================================ FILE: database/seeders/UserSeed.php ================================================ 'admin', 'lastname' => null, 'avatar' => 'http://lorempixel.com/80/60/', 'name' => 'admin', 'email' => 'admin@admin.com', 'password' => '$2y$10$l4MghrLnKXTRUDlR07XQeesKHRIaAe7WzDf90g751BEf70AwnJ5m.', // password 'remember_token' => Str::random(10) ], [ 'firstname' => 'Teacher', 'lastname' => null, 'avatar' => 'http://lorempixel.com/80/60/', 'name' => 'teacher', 'email' => 'teacher@admin.com', 'password' => '$2y$10$l4MghrLnKXTRUDlR07XQeesKHRIaAe7WzDf90g751BEf70AwnJ5m.', // password 'remember_token' => Str::random(10) ], [ 'firstname' => 'Student', 'lastname' => null, 'avatar' => 'http://lorempixel.com/80/60/', 'name' => 'student', 'email' => 'student@admin.com', 'password' => '$2y$10$l4MghrLnKXTRUDlR07XQeesKHRIaAe7WzDf90g751BEf70AwnJ5m.', // password 'remember_token' => Str::random(10) ] ]; foreach ($items as $key => $item) { $user = User::create($item); } User::factory()->count(50)->create(); User::factory()->unverified()->count(30)->create(); User::factory()->banned()->count(20)->create(); } } ================================================ FILE: docker/nginx/default.conf ================================================ server { listen 80; server_name localhost; root /var/www/html/public; index index.php index.html; # Client body size client_max_body_size 50M; # Logs access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; # Serve static files directly location ~* \.(jpg|jpeg|gif|css|png|js|ico|html|svg|woff|woff2|ttf|eot)$ { access_log off; expires max; add_header Cache-Control "public, immutable"; } # Front controller pattern location / { try_files $uri $uri/ /index.php?$query_string; } # PHP-FPM configuration location ~ \.php$ { try_files $uri =404; fastcgi_split_path_info ^(.+\.php)(/.+)$; fastcgi_pass app:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_read_timeout 300; fastcgi_buffers 16 16k; fastcgi_buffer_size 32k; } # Deny access to hidden files location ~ /\. { deny all; access_log off; log_not_found off; } # Deny access to certain files location ~ /\.(?!well-known).* { deny all; } } ================================================ FILE: docker/php/local.ini ================================================ upload_max_filesize=50M post_max_size=50M memory_limit=512M max_execution_time=300 max_input_time=300 date.timezone=UTC ================================================ FILE: docker-compose.yml ================================================ version: '3.8' services: # PHP-FPM Service app: build: context: . dockerfile: Dockerfile container_name: examinee-app restart: unless-stopped working_dir: /var/www/html volumes: - ./:/var/www/html - ./docker/php/local.ini:/usr/local/etc/php/conf.d/local.ini networks: - examinee-network depends_on: - db - redis environment: - DB_CONNECTION=pgsql - DB_HOST=db - DB_PORT=5432 - DB_DATABASE=examinee_db - DB_USERNAME=examinee_user - DB_PASSWORD=examinee_password - REDIS_HOST=redis - REDIS_PORT=6379 - CACHE_DRIVER=redis - SESSION_DRIVER=redis - QUEUE_CONNECTION=redis # Nginx Service nginx: image: nginx:alpine container_name: examinee-nginx restart: unless-stopped ports: - "8000:80" volumes: - ./:/var/www/html - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf networks: - examinee-network depends_on: - app # PostgreSQL Service db: image: postgres:16-alpine container_name: examinee-db restart: unless-stopped environment: POSTGRES_DB: examinee_db POSTGRES_USER: examinee_user POSTGRES_PASSWORD: examinee_password ports: - "5433:5432" volumes: - dbdata:/var/lib/postgresql/data networks: - examinee-network # Redis Service redis: image: redis:alpine container_name: examinee-redis restart: unless-stopped ports: - "6380:6379" networks: - examinee-network # Queue Worker Service queue: build: context: . dockerfile: Dockerfile container_name: examinee-queue restart: unless-stopped working_dir: /var/www/html volumes: - ./:/var/www/html networks: - examinee-network depends_on: - app - redis environment: - DB_CONNECTION=pgsql - DB_HOST=db - DB_PORT=5432 - DB_DATABASE=examinee_db - DB_USERNAME=examinee_user - DB_PASSWORD=examinee_password - REDIS_HOST=redis - REDIS_PORT=6379 - QUEUE_CONNECTION=redis command: php artisan queue:work --tries=3 --timeout=90 # Node Service for building assets node: image: node:20-alpine container_name: examinee-node working_dir: /var/www/html volumes: - ./:/var/www/html networks: - examinee-network command: sh -c "npm install && npm run build" profiles: - build networks: examinee-network: driver: bridge volumes: dbdata: driver: local ================================================ FILE: package.json ================================================ { "private": true, "type": "module", "scripts": { "dev": "vite", "build": "vue-tsc && vite build", "watch": "vue-tsc && vite build --watch" }, "devDependencies": { "@inertiajs/vue3": "^1.3.0", "@tailwindcss/forms": "^0.5.11", "@types/lodash": "^4.17.24", "@vitejs/plugin-vue": "^5.2.4", "autoprefixer": "^10.5.0", "axios": "^1.15.2", "laravel-vite-plugin": "^1.3.0", "postcss": "^8.5.10", "tailwindcss": "^3.4.19", "typescript": "^5.9.3", "vite": "^5.4.21", "vue": "^3.5.33", "vue-tsc": "^3.2.7" }, "dependencies": { "@headlessui/tailwindcss": "^0.2.2", "@headlessui/vue": "^1.7.23", "@heroicons/vue": "^2.2.0", "@vueuse/components": "^10.11.1", "@vueuse/core": "^10.11.1", "defu": "^6.1.7", "lodash": "^4.18.1", "moment": "^2.30.1", "tailwind-merge": "^2.6.1", "toasty": "^6.0.3", "vue-github-button": "^3.1.3", "vuex-persistedstate": "^4.1.0", "vuex-shared-mutations": "^1.0.2" } } ================================================ FILE: phpstan.neon ================================================ includes: - vendor/larastan/larastan/extension.neon parameters: paths: - app/ # Level 9 is the highest level level: 5 # ignoreErrors: # - '#PHPDoc tag @var#' # # excludePaths: # - ./*/*/FileToBeExcluded.php # # checkMissingIterableValueType: false ================================================ FILE: phpunit.xml ================================================ tests/Unit tests/Feature app ================================================ FILE: postcss.config.js ================================================ export default { plugins: { tailwindcss: {}, autoprefixer: {}, }, }; ================================================ FILE: public/.htaccess ================================================ Options -MultiViews -Indexes RewriteEngine On # Handle Authorization Header RewriteCond %{HTTP:Authorization} . RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] # Redirect Trailing Slashes If Not A Folder... RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_URI} (.+)/$ RewriteRule ^ %1 [L,R=301] # Send Requests To Front Controller... RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^ index.php [L] ================================================ FILE: public/index.php ================================================ handleRequest(Request::capture()); ================================================ FILE: public/robots.txt ================================================ User-agent: * Disallow: ================================================ FILE: resources/css/app.css ================================================ @tailwind base; @tailwind components; @tailwind utilities; ================================================ FILE: resources/js/Components/ApplicationLogo.vue ================================================ ================================================ FILE: resources/js/Components/Breadcrumb.vue ================================================ ================================================ FILE: resources/js/Components/Button.vue ================================================ ================================================ FILE: resources/js/Components/Card.vue ================================================ ================================================ FILE: resources/js/Components/Checkbox.vue ================================================ ================================================ FILE: resources/js/Components/CircleSvg.vue ================================================ ================================================ FILE: resources/js/Components/Datatable/Table.vue ================================================ ================================================ FILE: resources/js/Components/Dropdown.vue ================================================ ================================================ FILE: resources/js/Components/DropdownLink.vue ================================================ ================================================ FILE: resources/js/Components/Form/Checkbox.vue ================================================ ================================================ FILE: resources/js/Components/Form/HeroiconPicker.vue ================================================ ================================================ FILE: resources/js/Components/Form/Input.vue ================================================ ================================================ FILE: resources/js/Components/Form/Label.vue ================================================ ================================================ FILE: resources/js/Components/Form/Listbox.examplevue ================================================ ================================================ FILE: resources/js/Components/Form/Select.vue ================================================ ================================================ FILE: resources/js/Components/Icon.vue ================================================ ================================================ FILE: resources/js/Components/Icons/LoadingIcon.vue ================================================ ================================================ FILE: resources/js/Components/Icons/index.ts ================================================ export { default as LoadingIcon } from './LoadingIcon.vue' ================================================ FILE: resources/js/Components/Modal.vue ================================================ ================================================ FILE: resources/js/Components/NavLink.vue ================================================ ================================================ FILE: resources/js/Components/Pagination.vue ================================================ ================================================ FILE: resources/js/Components/ResponsiveNavLink.vue ================================================ ================================================ FILE: resources/js/Components/SocialiteLogins.vue ================================================ ================================================ FILE: resources/js/Components/Tabs.vue ================================================ ================================================ FILE: resources/js/Composables/analytics.js ================================================ /* eslint-disable */ // import { track } from '@services/analytics'; export function track( action, category = 'click event', label = 'clicked', value = 1, ) { let appGaEnabled = GA_ENABLED; // eslint-disable-line if (appGaEnabled) { this.$gtag.event(action, { event_category: category, event_label: label, value: value, }); } } ================================================ FILE: resources/js/Composables/common.js ================================================ /* eslint-disable */ import moment from 'moment'; // import { parseDisplayDate } from '@services/common'; export function parseDisplayDate(date) { return moment(date).format('MMM Do YYYY, h:mma'); } // import { capitalizeFirstLetter } from '@services/common'; export function capitalizeFirstLetter(str) { if (!str) return ''; var firstCodeUnit = str[0]; if (firstCodeUnit < '\uD800' || firstCodeUnit > '\uDFFF') { return str[0].toUpperCase() + str.slice(1); } return str.slice(0, 2).toUpperCase() + str.slice(2); } export function greeting() { const date = new Date(); const currentTime = date.getHours(); let greeting; if (currentTime >= 0 && currentTime <= 12) { greeting = 'Good Morning'; } else if (currentTime > 12 && currentTime <= 18) { greeting = 'Good Afternoon'; } else { greeting = 'Good Evening'; } return greeting; } export function providerIcon(provider = null) { if (provider.toLowerCase() == 'apple') { return 'fa-brands fa-apple text-gray-800 dark:text-gray-200'; } if (provider.toLowerCase() == 'twitter') { return 'fa-brands fa-twitter text-blue-300 dark:text-blue-200'; } if (provider.toLowerCase() == 'google') { return 'fa-brands fa-google text-red-500 dark:text-gray-200'; } if (provider.toLowerCase() == 'microsoft') { return 'fa-brands fa-microsoft text-blue-300 dark:text-gray-200'; } if (provider.toLowerCase() == 'tiktok') { return 'fa-brands fa-tiktok text-pink-600 dark:text-gray-200'; } if (provider.toLowerCase() == 'youtube') { return 'fa-brands fa-youtube text-red-600 dark:text-gray-200'; } if (provider.toLowerCase() == 'instagram') { return 'fa-brands fa-instagram text-gray-800 dark:text-gray-200'; } if (provider.toLowerCase() == 'facebook') { return 'fa-brands fa-facebook text-blue-600 dark:text-gray-200'; } if (provider.toLowerCase() == 'github') { return 'fa-brands fa-github text-gray-700 dark:text-gray-200'; } if (provider.toLowerCase() == 'twitch') { return 'fa-brands fa-twitch text-blue-300 dark:text-gray-200'; } if (provider.toLowerCase() == 'linkedin') { return 'fa-brands fa-linkedin text-blue-900 dark:text-gray-200'; } if (provider.toLowerCase() == 'zoho') { return 'fas fa-z text-yellow-500 dark:text-gray-200'; } if (provider.toLowerCase() == 'stackexchange') { return 'fa-brands fa-stack-exchange text-blue-400 dark:text-gray-200'; } if (provider.toLowerCase() == 'gitlab') { return 'fa-brands fa-square-gitlab text-orange-400 dark:text-gray-200'; } if (provider.toLowerCase() == 'reddit') { return 'fa-brands fa-square-reddit text-orange-700 dark:text-gray-200'; } if (provider.toLowerCase() == 'snapchat') { return 'fa-brands fa-square-snapchat text-yellow-400 dark:text-gray-200'; } if (provider.toLowerCase() == 'meetup') { return 'fa-brands fa-meetup text-red-400 dark:text-gray-200'; } if (provider.toLowerCase() == 'bitbucket') { return 'fa-brands fa-bitbucket text-blue-800 dark:text-gray-200'; } if (provider.toLowerCase() == 'atlassian') { return 'fa-brands fa-atlassian text-blue-800 dark:text-gray-200'; } // NEW_PROVIDER_PLUG :: Put New Provider HERE return 'fa-solid fa-plug-circle-check text-gray-600 dark:text-gray-200'; } ================================================ FILE: resources/js/Composables/useAuth.ts ================================================ import { usePage } from "@inertiajs/vue3"; import { User } from "@/types"; export default function useAuth() { const user: User = usePage().props.auth.user; return { user, authenticated: !! user, roles: { superAdmin: true, admin: true, moderator: false, editor: false, user: false, }, } } ================================================ FILE: resources/js/Composables/useButtonGroup.ts ================================================ import { computed, ref, provide, inject, onMounted, onUnmounted, getCurrentInstance } from 'vue' import type { Ref, ComponentInternalInstance } from 'vue' import { buttonGroup } from '@/ui.config' type ButtonGroupProps = { orientation?: Ref<'horizontal' | 'vertical'> size?: Ref ui?: Ref> rounded?: Ref<{ start: string, end: string }> } // make a ButtonGroupContext type for injection. Should include ButtonGroupProps type ButtonGroupContext = { children: ComponentInternalInstance[] register (child: ComponentInternalInstance): void unregister (child: ComponentInternalInstance): void orientation: 'horizontal' | 'vertical' size: string ui: Partial rounded: { start: string, end: string } } export function useProvideButtonGroup (buttonGroupProps: ButtonGroupProps) { const instance = getCurrentInstance() const groupKey = `group-${instance?.uid}` const state = ref({ children : [] as ComponentInternalInstance[], register (child: ComponentInternalInstance) { this.children.push(child) }, unregister (child: ComponentInternalInstance) { const index = this.children.indexOf(child) if (index > -1) { this.children.splice(index, 1) } }, ...buttonGroupProps }) provide(groupKey, state as Ref) } export function useInjectButtonGroup ({ ui, props }: { ui: any, props: any }) { const instance = getCurrentInstance() provide('ButtonGroupContextConsumer', true) const isParentPartOfGroup = inject('ButtonGroupContextConsumer', false) // early return if a parent is already part of the group if (isParentPartOfGroup) { return { size: computed(() => props.size), rounded: computed(() => ui.value.rounded) } } let parent = instance?.parent let groupContext: Ref | undefined // Traverse up the parent chain to find the nearest ButtonGroup while (parent && !groupContext) { if (parent.type.name === 'ButtonGroup') { groupContext = inject(`group-${parent.uid}`) break } parent = parent.parent } const positionInGroup = computed(() => instance ? groupContext?.value.children.indexOf(instance) : false) if (instance) { onMounted(() => { groupContext?.value.register(instance) }) onUnmounted(() => { groupContext?.value.unregister(instance) }) } return { size: computed(() => { if (!groupContext?.value) return props.size return groupContext?.value.size ?? ui.value.default.size }), rounded: computed(() => { if (!groupContext || positionInGroup.value === -1) return ui.value.rounded if (groupContext.value.children.length === 1) return groupContext.value.ui.rounded if (positionInGroup.value === 0) return groupContext.value.rounded.start if (positionInGroup.value === groupContext.value.children.length - 1) return groupContext.value.rounded.end return 'rounded-none' }) } } ================================================ FILE: resources/js/Composables/useFormGroup.ts ================================================ import { inject, ref, computed } from 'vue' import { type UseEventBusReturn, useDebounceFn } from '@vueuse/core' import type { FormEvent, FormEventType, InjectedFormGroupValue } from '@/types' type InputProps = { id?: string size?: string | number | symbol color?: string name?: string eagerValidation?: boolean legend?: string | null } export const useFormGroup = (inputProps?: InputProps, config?: any) => { const formBus = inject | undefined>('form-events', undefined) const formGroup = inject('form-group', undefined) const formInputs = inject('form-inputs', undefined) if (formGroup) { if (inputProps?.id) { // Updates for="..." attribute on label if inputProps.id is provided formGroup.inputId.value = inputProps?.id } if (formInputs) { formInputs.value[formGroup.name.value] = formGroup.inputId.value } } const blurred = ref(false) function emitFormEvent (type: FormEventType, path: string) { if (formBus) { formBus.emit({ type, path }) } } function emitFormBlur () { emitFormEvent('blur', formGroup?.name.value as string) blurred.value = true } function emitFormChange () { emitFormEvent('change', formGroup?.name.value as string) } const emitFormInput = useDebounceFn(() => { if (blurred.value || formGroup?.eagerValidation.value) { emitFormEvent('input', formGroup?.name.value as string) } }, 300) return { inputId: computed(() => inputProps?.id ?? formGroup?.inputId.value), name: computed(() => inputProps?.name ?? formGroup?.name.value), size: computed(() => { const formGroupSize = config.size[formGroup?.size.value as string] ? formGroup?.size.value : null return inputProps?.size ?? formGroupSize ?? 'sm' }), color: computed(() => formGroup?.error?.value ? 'red' : inputProps?.color), emitFormBlur, emitFormInput, emitFormChange } } ================================================ FILE: resources/js/Composables/useSidebarState.ts ================================================ import { ref } from 'vue' import { createGlobalState } from '@vueuse/core' export const useSidebarState = createGlobalState( () => { let sideBarOpen = ref(true); let fullScreenSideBarOpen = ref(true); function toggleSidebar() { sideBarOpen.value = !sideBarOpen.value; if (!sideBarOpen.value) { fullScreenSideBarOpen.value = false; } } function toggleFullScreenSidebar() { fullScreenSideBarOpen.value = !fullScreenSideBarOpen.value; if (fullScreenSideBarOpen.value) { sideBarOpen.value = true; } } return { sideBarOpen, fullScreenSideBarOpen, toggleSidebar, toggleFullScreenSidebar } } ) ================================================ FILE: resources/js/Composables/useUI.ts ================================================ import { computed, toValue, useAttrs } from 'vue' import type { Ref } from 'vue' import { mergeConfig, omit, get } from '@/Composables/utils'; import type { Strategy } from '@/types' export const useUI = (key: string, $ui?: Ref & { strategy?: Strategy } | undefined>, $config?: Ref | T, $wrapperClass?: Ref) => { const $attrs = useAttrs() const ui = computed(() => { const _ui = toValue($ui) const _config = toValue($config) const _wrapperClass = toValue($wrapperClass) return mergeConfig( _ui?.strategy || 'merge', _wrapperClass ? { wrapper: _wrapperClass } : {}, _ui || {}, _config || {} ) }) const attrs = computed(() => omit($attrs, ['class'])) return { ui, attrs } } ================================================ FILE: resources/js/Composables/utils.ts ================================================ import { defu, createDefu } from 'defu' import { extendTailwindMerge } from 'tailwind-merge' import type { Strategy } from '../types' const customTwMerge = extendTailwindMerge({ extend: { classGroups: { icons: [(classPart: string) => /^i-/.test(classPart)] } } }) const defuTwMerge = createDefu((obj, key, value, namespace) => { if (namespace === 'default' || namespace.startsWith('default.')) { return false } if (namespace === 'popper' || namespace.startsWith('popper.')) { return false } if (namespace.endsWith('avatar') && key === 'size') { return false } if (namespace.endsWith('chip') && key === 'size') { return false } if (namespace.endsWith('badge') && key === 'size' || key === 'color' || key === 'variant') { return false } if (typeof obj[key] === 'string' && typeof value === 'string' && obj[key] && value) { // @ts-ignore obj[key] = customTwMerge(obj[key], value) return true } }) export function mergeConfig (strategy: Strategy, ...configs: any): T { if (strategy === 'override') { return defu({}, ...configs) as T } return defuTwMerge({}, ...configs) as T } export function hexToRgb (hex: string) { // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i hex = hex.replace(shorthandRegex, function (_, r, g, b) { return r + r + g + g + b + b }) const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) return result ? `${parseInt(result[1], 16)} ${parseInt(result[2], 16)} ${parseInt(result[3], 16)}` : null } export function getSlotsChildren (slots: any) { let children = slots.default?.() if (children?.length) { children = children.flatMap((c: any )=> { if (typeof c.type === 'symbol') { if (typeof c.children === 'string') { // `v-if="false"` or commented node return } return c.children } else if (c.type.name === 'ContentSlot') { return c.ctx.slots.default?.() } return c }).filter(Boolean) } return children || [] } /** * "123-foo" will be parsed to 123 * This is used for the .number modifier in v-model */ export function looseToNumber (val: any): any { const n = parseFloat(val) return isNaN(n) ? val : n } export function omit, K extends keyof T> ( object: T, keysToOmit: K[] | any[] ): Pick> { const result = { ...object } for (const key of keysToOmit) { delete result[key] } return result } export function get (object: Record, path: (string | number)[] | string, defaultValue?: any): any { if (typeof path === 'string') { path = path.split('.').map(key => { const numKey = Number(key) return isNaN(numKey) ? key : numKey }) } let result: any = object for (const key of path) { if (result === undefined || result === null) { return defaultValue } result = result[key] } return result !== undefined ? result : defaultValue } export function useId(prefix?: string, length: number = 10): string { let result = ''; const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; const charactersLength = characters.length; let counter = 0; while (counter < length) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); counter += 1; } return prefix ? prefix.concat(result) : result; } export function slugify (string: string): string { const a = 'àáäâãåăæąçćčđďèéěėëêęğǵḧìíïîįłḿǹńňñòóöôœøṕŕřßşśšșťțùúüûǘůűūųẃẍÿýźžż·/_,:;' const b = 'aaaaaaaaacccddeeeeeeegghiiiiilmnnnnooooooprrsssssttuuuuuuuuuwxyyzzz------' const p = new RegExp(a.split('').join('|'), 'g') return string.toString().toLowerCase() .replace(/\s+/g, '-') // Replace spaces with - .replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters .replace(/&/g, '-and-') // Replace & with 'and' .replace(/[^\w-]+/g, '') // Remove all non-word characters .replace(/--+/g, '-') // Replace multiple - with single - .replace(/^-+/, '') // Trim - from start of text .replace(/-+$/, '') // Trim - from end of text } ================================================ FILE: resources/js/Elements/Course.vue ================================================ ================================================ FILE: resources/js/Elements/Description.vue ================================================ ================================================ FILE: resources/js/Elements/Lesson/Question.vue ================================================ ================================================ FILE: resources/js/Elements/Lesson/Session.vue ================================================ ================================================ FILE: resources/js/Elements/Lesson/SingleExam.vue ================================================ ================================================ FILE: resources/js/Elements/Lesson/SingleLesson.vue ================================================ ================================================ FILE: resources/js/Elements/Lesson/SingleResource.vue ================================================ ================================================ FILE: resources/js/Elements/admin/AdminFooter.vue ================================================ ================================================ FILE: resources/js/Elements/admin/AdminNavBar.vue ================================================ ================================================ FILE: resources/js/Elements/admin/AdminSidebar.vue ================================================ ================================================ FILE: resources/js/Elements/admin/GHButtons.vue ================================================ ================================================ FILE: resources/js/Elements/course/CourseForm.vue ================================================ ================================================ FILE: resources/js/Elements/course/CourseLayout.vue ================================================ ================================================ FILE: resources/js/Elements/course/Exam.vue ================================================ ================================================ FILE: resources/js/Elements/course/Lesson.vue ================================================ ================================================ FILE: resources/js/Elements/course/LessonForm.vue ================================================ ================================================ FILE: resources/js/Elements/course/SectionForm.vue ================================================ ================================================ FILE: resources/js/Elements/course/SingleSection.vue ================================================ ================================================ FILE: resources/js/Elements/exam/CreateExam.vue ================================================ ================================================ FILE: resources/js/Elements/exam/CreateQuestion.vue ================================================ ================================================ FILE: resources/js/Elements/exam/ExamLayout.vue ================================================ ================================================ FILE: resources/js/Elements/exam/QuestionTable.vue ================================================ ================================================ FILE: resources/js/Elements/roles/RolesBadges.vue ================================================ ================================================ FILE: resources/js/Layouts/AdminLayout.vue ================================================ ================================================ FILE: resources/js/Layouts/AuthenticatedLayout.vue ================================================ ================================================ FILE: resources/js/Layouts/GuestLayout.vue ================================================ ================================================ FILE: resources/js/Layouts/MainLayout.vue ================================================ ================================================ FILE: resources/js/Pages/Auth/ConfirmPassword.vue ================================================ ================================================ FILE: resources/js/Pages/Auth/ForgotPassword.vue ================================================ ================================================ FILE: resources/js/Pages/Auth/Login.vue ================================================ ================================================ FILE: resources/js/Pages/Auth/Register.vue ================================================ ================================================ FILE: resources/js/Pages/Auth/ResetPassword.vue ================================================ ================================================ FILE: resources/js/Pages/Auth/VerifyEmail.vue ================================================ ================================================ FILE: resources/js/Pages/Course/Course.vue ================================================ ================================================ FILE: resources/js/Pages/Course/Partials/Instractor.vue ================================================ ================================================ FILE: resources/js/Pages/Course/Partials/Pricing.vue ================================================ ================================================ FILE: resources/js/Pages/Home/Home.vue ================================================ ================================================ FILE: resources/js/Pages/Home/Slider.vue ================================================ ================================================ FILE: resources/js/Pages/Instructor/Courses.vue ================================================ ================================================ FILE: resources/js/Pages/Learning/Courses.vue ================================================ ================================================ FILE: resources/js/Pages/Learning/SingleResource.vue ================================================ ================================================ FILE: resources/js/Pages/Profile/Edit.vue ================================================ ================================================ FILE: resources/js/Pages/Profile/Partials/DeleteUserForm.vue ================================================ ================================================ FILE: resources/js/Pages/Profile/Partials/UpdatePasswordForm.vue ================================================ ================================================ FILE: resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue ================================================ ================================================ FILE: resources/js/Pages/admin/Dashboard.vue ================================================ ================================================ FILE: resources/js/Pages/admin/ServerInfo.vue ================================================ ================================================ FILE: resources/js/Pages/admin/courses/CourseStudent.vue ================================================ ================================================ FILE: resources/js/Pages/admin/courses/CreateCourse.vue ================================================ ================================================ FILE: resources/js/Pages/admin/courses/Index.vue ================================================ ================================================ FILE: resources/js/Pages/admin/courses/Sections.vue ================================================ ================================================ FILE: resources/js/Pages/admin/exams/Create.vue ================================================ ================================================ FILE: resources/js/Pages/admin/exams/CreateQuestion.vue ================================================ ================================================ FILE: resources/js/Pages/admin/exams/Edit.vue ================================================ ================================================ FILE: resources/js/Pages/admin/exams/Index.vue ================================================ ================================================ FILE: resources/js/Pages/admin/exams/Questions.vue ================================================ ================================================ FILE: resources/js/Pages/admin/roles/Create.vue ================================================ ================================================ FILE: resources/js/Pages/admin/roles/Edit.vue ================================================ ================================================ FILE: resources/js/Pages/admin/roles/Index.vue ================================================ ================================================ FILE: resources/js/Pages/admin/subjects/Create.vue ================================================ ================================================ FILE: resources/js/Pages/admin/topics/Edit.vue ================================================ ================================================ FILE: resources/js/Pages/admin/topics/Index.vue ================================================ ================================================ FILE: resources/js/Pages/admin/users/Create.vue ================================================ ================================================ FILE: resources/js/Pages/admin/users/Edit.vue ================================================ ================================================ FILE: resources/js/Pages/admin/users/Index.vue ================================================ ================================================ FILE: resources/js/app.ts ================================================ import './bootstrap'; import '../css/app.css'; import { createApp, h, DefineComponent } from 'vue'; import { createInertiaApp } from '@inertiajs/vue3'; import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; import { ZiggyVue } from '../../vendor/tightenco/ziggy'; const appName = import.meta.env.VITE_APP_NAME || 'Laravel'; createInertiaApp({ title: (title) => `${title} - ${appName}`, resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')), setup({ el, App, props, plugin }) { createApp({ render: () => h(App, props) }) .use(plugin) .use(ZiggyVue) .mount(el); }, progress: { color: '#4B5563', }, }); ================================================ FILE: resources/js/bootstrap.ts ================================================ import axios from 'axios'; window.axios = axios; window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; ================================================ FILE: resources/js/types/forms.d.ts ================================================ import type { Ref } from 'vue' export interface FormError { path: T message: string } export interface FormErrorWithId extends FormError { id: string } export interface Form { validate(path?: string | string[], opts?: { silent?: true }): Promise; validate(path?: string | string[], opts?: { silent?: false }): Promise; clear(path?: string): void errors: Ref setErrors(errs: FormError[], path?: string): void getErrors(path?: string): FormError[] submit(): Promise } export type FormSubmitEvent = SubmitEvent & { data: T } export type FormErrorEvent = SubmitEvent & { errors: FormErrorWithId[] } export type FormEventType = 'blur' | 'input' | 'change' | 'submit' export interface FormEvent { type: FormEventType path?: string } export interface InjectedFormGroupValue { inputId: Ref name: Ref size: Ref error: Ref eagerValidation: Ref } export type Sizes = '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'; export type InputColor = 'white' | 'gray' | 'red'; export type InputVariant = 'outline' | 'none'; ================================================ FILE: resources/js/types/global.d.ts ================================================ import { PageProps as InertiaPageProps } from '@inertiajs/core'; import { AxiosInstance } from 'axios'; import { route as ziggyRoute } from 'ziggy-js'; import { PageProps as AppPageProps } from './'; declare global { interface Window { axios: AxiosInstance; } var route: typeof ziggyRoute; } declare module 'vue' { interface ComponentCustomProperties { route: typeof ziggyRoute; } } declare module '@inertiajs/core' { interface PageProps extends InertiaPageProps, AppPageProps {} } ================================================ FILE: resources/js/types/icons.d.ts ================================================ import * as solid from "@heroicons/vue/24/solid"; export type CustomIconName = "LoadingIcon"; export type HeroIconName = keyof typeof solid export type IconName = HeroIconName | CustomIconName; ================================================ FILE: resources/js/types/index.d.ts ================================================ export * from './resources'; export * from './forms'; export * from './icons'; export type Strategy = 'merge' | 'override' export type PageProps = Record> = T & { auth: { user: User; }; }; ================================================ FILE: resources/js/types/resources.d.ts ================================================ export interface User { id: number; name: string; firstname?: string; lastname?: string; full_name?: string; avatar?: string; email: string; email_verified_at: string; created_at?: string; updated_at?: string; deleted_at?: string; roles?: Role[]; results?: Result[]; instructCourses?: Course[]; enrolledCourses?: Course[]; } export interface Student { id: number; name: string; full_name?: string; avatar?: string; rating: number; progress: number; } export interface Role { id: number; name: string; guard_name: string; permissions: Array } export interface Topic { id: number; title: string; description: string; created_at?: string; updated_at?: string; courses?: Course[]; exams?: Exam[]; courses_count?: number; exams_count?: number; } export interface Section { id: number; title: string; description: string; created_at?: string; updated_at?: string; exams?: Exam[]; lessons?: Lesson[]; resources: Exam[] | Lesson[] } export interface Result { id: number; answers: string; obtain_mark: number; is_pass: boolean; time_taken: string; created_at?: string; updated_at?: string; examinee?: User; exam?: any; } export interface QuestionOption { option: string; id?: string; answer?: boolean; } export interface Question { id: number; qtype: string; question?: string; options?: QuestionOption[]; answers?: Array; hint?: string; explanation?: string; mark?: number; nmark?: number; created_at?: string; updated_at?: string; topics?: Topic[]; exam?: any; } export interface Lesson { id: number; title: string; slug: string; thumbnail?: string; type?: string; object?: string; short_text?: string; full_text?: string; position?: string; status?: string; created_at?: string; updated_at?: string; courses?: Course[]; students?: User[]; examSessions?: any; } export interface File { id: number; name: string; path?: string; description?: string; file_name?: string; extension?: string; mime?: string; type?: string; public_path?: string; parent_id?: string; uploaded_by?: any; created_at?: string; updated_at?: string; } export interface Subject { id: number; title: string; slug: string; description?: string; icon?: string; image?: string; image_url?: string; parent?: number; created_at?: string; updated_at?: string; courses?: Course[]; exams?: Exam[]; courses_count?: number; exams_count?: number; children?: Subject[]; children_count?: number; } export interface Exam { id: number; title: string; description?: string; status?: number; price?: number; duration?: number; pass_mark?: number; number_of_questions?: number; random_questions?: boolean; certification?: boolean; difficulty?: string; permalink?: string; meta?: any; subjects?: Subject[]; courses?: Course[]; questions?: Question[]; examiner?: User; topics: Topic[]; results?: Result[]; } export interface Course { id: number; title: string; subtitle: string; slug: string; description?: string; requirements?: string; status?: number; thumbnail?: string; start_date?: string; features?: string; rating?: number; certified?: boolean; created_at?: string; updated_at?: string; price: number; discount: number; permalink: string; teachers?: User[]; students?: User[]; sections?: Section[]; lessons?: Lesson[]; exams?: Exam[]; topics?: Topic[]; subjects?: Subject[]; lessons_count?: number; students_count?: number; sections_count?: number; exams_count?: number; } export interface LinkType { name: string; href: string; current: boolean; icon?: string; } export interface JsonResponse { data: T; links: { fast: string; last: string; prev?: string; next?: string; }, meta: LaravelPagination } export interface PaginationLink { url: string; label: string; active: boolean } export interface LaravelPagination { current_page: number; from: number; last_page: number; links?: PaginationLink[]; path: string; per_page: number; to: number, total?: number } ================================================ FILE: resources/js/types/vite-env.d.ts ================================================ /// ================================================ FILE: resources/js/types/vuex.d.ts ================================================ declare module "vuex" { export * from "vuex/types/index.d.ts"; export * from "vuex/types/helpers.d.ts"; export * from "vuex/types/logger.d.ts"; export * from "vuex/types/vue.d.ts"; } ================================================ FILE: resources/js/ui.config/buttonGroup.ts ================================================ export default { wrapper: { horizontal: 'inline-flex -space-x-px', vertical: 'inline-flex flex-col -space-y-px' }, rounded: 'rounded-md', shadow: 'shadow-sm', orientation: { 'rounded-none': { horizontal: { start: 'rounded-s-none', end: 'rounded-e-none' }, vertical: { start: 'rounded-t-none', end: 'rounded-b-none' } }, 'rounded-sm': { horizontal: { start: 'rounded-s-sm', end: 'rounded-e-sm' }, vertical: { start: 'rounded-t-sm', end: 'rounded-b-sm' } }, rounded: { horizontal: { start: 'rounded-s', end: 'rounded-e' }, vertical: { start: 'rounded-t', end: 'rounded-b' } }, 'rounded-md': { horizontal: { start: 'rounded-s-md', end: 'rounded-e-md' }, vertical: { start: 'rounded-t-md', end: 'rounded-b-md' } }, 'rounded-lg': { horizontal: { start: 'rounded-s-lg', end: 'rounded-e-lg' }, vertical: { start: 'rounded-t-lg', end: 'rounded-b-lg' } }, 'rounded-xl': { horizontal: { start: 'rounded-s-xl', end: 'rounded-e-xl' }, vertical: { start: 'rounded-t-xl', end: 'rounded-b-xl' } }, 'rounded-2xl': { horizontal: { start: 'rounded-s-2xl', end: 'rounded-e-2xl' }, vertical: { start: 'rounded-t-2xl', end: 'rounded-b-2xl' } }, 'rounded-3xl': { horizontal: { start: 'rounded-s-3xl', end: 'rounded-e-3xl' }, vertical: { start: 'rounded-t-3xl', end: 'rounded-b-3xl' } }, 'rounded-full': { horizontal: { start: 'rounded-s-full', end: 'rounded-e-full' }, vertical: { start: 'rounded-t-full', end: 'rounded-b-full' } } } } ================================================ FILE: resources/js/ui.config/index.ts ================================================ export { default as buttonGroup } from './buttonGroup' ================================================ FILE: resources/views/app.blade.php ================================================ {{ config('app.name', 'Laravel') }} @routes @vite(['resources/js/app.ts', "resources/js/Pages/{$page['component']}.vue"]) @inertiaHead @inertia ================================================ FILE: resources/views/errors/401.blade.php ================================================ @extends('errors::minimal') @section('title', __('Unauthorized')) @section('code', '401') @section('message', __('Unauthorized')) ================================================ FILE: resources/views/errors/403.blade.php ================================================ @extends('errors::minimal') @section('title', __('Forbidden')) @section('code', '403') @section('message', __($exception->getMessage() ?: 'Forbidden')) ================================================ FILE: resources/views/errors/500.blade.php ================================================ @extends('errors::minimal') @section('title', __('Server Error')) @section('code', '500') @section('message', __('Server Error')) ================================================ FILE: resources/views/errors/503.blade.php ================================================ {{-- @extends('errors::minimal') @section('title', __('Service Unavailable')) @section('code', '503') @section('message', __('Service Unavailable')) --}} @if($ga_enabled) @endif {{ config('app.name') }} @vite(['resources/css/app.css'])

Under Maintenance

Our Enterprise administrators are performing scheduled maintenance.

================================================ FILE: resources/views/errors/layout.blade.php ================================================ @yield('title')
@yield('message')
================================================ FILE: resources/views/socialite/callback.blade.php ================================================ {{ config('app.name') }} ================================================ FILE: resources/views/socialite/denied.blade.php ================================================ @extends('errors.layout') @section('title', 'Login Error') @section('message', 'You did not share your profile data with '.config('app.name').'.') ================================================ FILE: routes/api.php ================================================ ['auth:sanctum']], function () { Route::get('courses/my-courses', [MyCourseController::class, 'index']); Route::get('courses/my-courses/{course:slug}', [MyCourseController::class, 'show']); Route::apiResource('course', CourseController::class); Route::put('sessions/{session}/attach-exam', [SessionController::class,'attachExam']); Route::put('sessions/{session}/attach-lession', [SessionController::class, 'attacLession']); Route::get('take-exam/{exam}', [TakeExamController::class, 'show']); Route::post('start-exam/{exam}', [TakeExamController::class, 'start']); Route::post('complete-exam/{exam}', [TakeExamController::class, 'complete']); Route::post('answer/{exam}', [TakeExamController::class, 'answer']); Route::apiResource('/files', FileController::class); }); Route::get('courses/{course}/sessions', [SessionableController::class, 'lessons']); ================================================ FILE: routes/auth.php ================================================ group(function () { Route::get('register', [RegisteredUserController::class, 'create']) ->name('register'); Route::post('register', [RegisteredUserController::class, 'store']); Route::get('login', [AuthenticatedSessionController::class, 'create']) ->name('login'); Route::post('login', [AuthenticatedSessionController::class, 'store']); Route::get('forgot-password', [PasswordResetLinkController::class, 'create']) ->name('password.request'); Route::post('forgot-password', [PasswordResetLinkController::class, 'store']) ->name('password.email'); Route::get('reset-password/{token}', [NewPasswordController::class, 'create']) ->name('password.reset'); Route::post('reset-password', [NewPasswordController::class, 'store']) ->name('password.store'); }); Route::middleware('auth')->group(function () { Route::get('verify-email', EmailVerificationPromptController::class) ->name('verification.notice'); Route::get('verify-email/{id}/{hash}', VerifyEmailController::class) ->middleware(['signed', 'throttle:6,1']) ->name('verification.verify'); Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store']) ->middleware('throttle:6,1') ->name('verification.send'); Route::get('confirm-password', [ConfirmablePasswordController::class, 'show']) ->name('password.confirm'); Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']); Route::put('password', [PasswordController::class, 'update'])->name('password.update'); Route::post('logout', [AuthenticatedSessionController::class, 'destroy']) ->name('logout'); }); ================================================ FILE: routes/console.php ================================================ comment(Inspiring::quote()); })->purpose('Display an inspiring quote')->hourly(); ================================================ FILE: routes/web.php ================================================ name('home'); Route::get('/courses/{course:slug}', [CourseController::class, 'show'])->name('course.show'); Route::get("/instructor/{user:name}/course", [InstructorController::class, 'courses'])->name("instructor.courses"); Route::post('/oauth/{driver}', [SocialiteController::class, 'getSocialRedirect'])->name('oauth'); Route::get('/oauth/{driver}/callback', [SocialiteController::class, 'handleSocialCallback'])->name('oauth.callback'); Route::group(['middleware' => ['auth']], function(){ Route::get("/learning/my-courses", [LearningController::class, 'myCourses' ])->name("learning.courses"); Route::get("/learning/{course:slug}", [LearningController::class, 'startCourse' ])->name("start.course"); Route::get("/learning/{course:slug}/{type}/{slog}", [LearningController::class, 'singleResource' ])->name("start.resource"); Route::post('/download', [DownloadController::class, 'download']); Route::get('/uploads/{id}/{any?}', UploadController::class)->where('any', '.*'); Route::post("/subscribe/{course}", [CourseController::class, 'subscribe'])->name('course.subscribe'); }); Route::middleware(['auth', 'verified'])->prefix('/dashboard')->name('admin.')->group(function () { Route::get('/', DashboardController::class)->name('dashboard'); Route::resource('users', UsersController::class); Route::put('users/{user}/sync-roles', [UsersController::class, 'syncRoles'])->name('users.sync-roles'); Route::post('users/bulk-delete', [UsersController::class, 'bulkDelete'])->name('users.bulk-delete'); Route::post('users/bulk-sync-roles', [UsersController::class, 'bulkSyncRoles'])->name('users.bulk-sync-roles'); Route::resource('roles', RolesController::class); Route::get('courses/{course}/students', CourseStudentController::class)->name('course-student'); Route::apiResource('courses/{course:id}/sections', SectionController::class)->except(['show', 'edit']); Route::resource('courses', CoursesController::class); Route::apiResource('courses/{course:id}/lessons', LessonController::class); Route::resource('topics', TopicsController::class)->except(['show']); Route::post('topics/bulk-delete', [TopicsController::class, 'bulkDelete'])->name('topics.bulk-delete'); Route::resource('subjects', SubjectController::class)->except(['show']); Route::resource('exams', ExamController::class)->except(['show']); Route::resource('exams/{exam:id}/questions', QuestionController::class)->except(['show']); Route::get('/server-info', [ServerInfoController::class, 'index'])->name('server-info'); Route::post('/oauth-revoke/{provider}', [SocialiteController::class, 'revokeSocialProvider']); }); Route::middleware('auth')->group(function () { Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update'); Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy'); }); require __DIR__.'/auth.php'; ================================================ FILE: storage/app/.gitignore ================================================ * !public/ !.gitignore ================================================ FILE: storage/framework/.gitignore ================================================ compiled.php config.php down events.scanned.php maintenance.php routes.php routes.scanned.php schedule-* services.json ================================================ FILE: storage/framework/cache/.gitignore ================================================ * !data/ !.gitignore ================================================ FILE: storage/framework/sessions/.gitignore ================================================ * !.gitignore ================================================ FILE: storage/framework/testing/.gitignore ================================================ * !.gitignore ================================================ FILE: storage/framework/views/.gitignore ================================================ * !.gitignore ================================================ FILE: storage/logs/.gitignore ================================================ * !.gitignore ================================================ FILE: tailwind.config.js ================================================ import defaultTheme from 'tailwindcss/defaultTheme'; import forms from '@tailwindcss/forms'; import headlessui from '@headlessui/tailwindcss'; /** @type {import('tailwindcss').Config} */ export default { darkMode: 'selector', content: [ './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php', './storage/framework/views/*.php', './resources/views/**/*.blade.php', './resources/js/**/*.vue', ], theme: { extend: { fontFamily: { sans: ['Figtree', ...defaultTheme.fontFamily.sans], }, }, }, variants: { extend: { display: ["group-hover"], }, }, plugins: [ forms, headlessui ], }; ================================================ FILE: tests/Feature/Auth/AuthenticationTest.php ================================================ get('/login'); $response->assertStatus(200); } public function test_users_can_authenticate_using_the_login_screen(): void { $user = User::factory()->create(); $response = $this->post('/login', [ 'email' => $user->email, 'password' => 'password', ]); $this->assertAuthenticated(); $response->assertRedirect(route('admin.dashboard', absolute: false)); } public function test_users_can_not_authenticate_with_invalid_password(): void { $user = User::factory()->create(); $this->post('/login', [ 'email' => $user->email, 'password' => 'wrong-password', ]); $this->assertGuest(); } public function test_users_can_logout(): void { $user = User::factory()->create(); $response = $this->actingAs($user)->post('/logout'); $this->assertGuest(); $response->assertRedirect('/'); } } ================================================ FILE: tests/Feature/Auth/EmailVerificationTest.php ================================================ unverified()->create(); $response = $this->actingAs($user)->get('/verify-email'); $response->assertStatus(200); } public function test_email_can_be_verified(): void { $user = User::factory()->unverified()->create(); Event::fake(); $verificationUrl = URL::temporarySignedRoute( 'verification.verify', now()->addMinutes(60), ['id' => $user->id, 'hash' => sha1($user->email)] ); $response = $this->actingAs($user)->get($verificationUrl); Event::assertDispatched(Verified::class); $this->assertTrue($user->fresh()->hasVerifiedEmail()); $response->assertRedirect(route('admin.dashboard', absolute: false).'?verified=1'); } public function test_email_is_not_verified_with_invalid_hash(): void { $user = User::factory()->unverified()->create(); $verificationUrl = URL::temporarySignedRoute( 'verification.verify', now()->addMinutes(60), ['id' => $user->id, 'hash' => sha1('wrong-email')] ); $this->actingAs($user)->get($verificationUrl); $this->assertFalse($user->fresh()->hasVerifiedEmail()); } } ================================================ FILE: tests/Feature/Auth/PasswordConfirmationTest.php ================================================ create(); $response = $this->actingAs($user)->get('/confirm-password'); $response->assertStatus(200); } public function test_password_can_be_confirmed(): void { $user = User::factory()->create(); $response = $this->actingAs($user)->post('/confirm-password', [ 'password' => 'password', ]); $response->assertRedirect(); $response->assertSessionHasNoErrors(); } public function test_password_is_not_confirmed_with_invalid_password(): void { $user = User::factory()->create(); $response = $this->actingAs($user)->post('/confirm-password', [ 'password' => 'wrong-password', ]); $response->assertSessionHasErrors(); } } ================================================ FILE: tests/Feature/Auth/PasswordResetTest.php ================================================ get('/forgot-password'); $response->assertStatus(200); } public function test_reset_password_link_can_be_requested(): void { Notification::fake(); $user = User::factory()->create(); $this->post('/forgot-password', ['email' => $user->email]); Notification::assertSentTo($user, ResetPassword::class); } public function test_reset_password_screen_can_be_rendered(): void { Notification::fake(); $user = User::factory()->create(); $this->post('/forgot-password', ['email' => $user->email]); Notification::assertSentTo($user, ResetPassword::class, function ($notification) { $response = $this->get('/reset-password/'.$notification->token); $response->assertStatus(200); return true; }); } public function test_password_can_be_reset_with_valid_token(): void { Notification::fake(); $user = User::factory()->create(); $this->post('/forgot-password', ['email' => $user->email]); Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) { $response = $this->post('/reset-password', [ 'token' => $notification->token, 'email' => $user->email, 'password' => 'password', 'password_confirmation' => 'password', ]); $response ->assertSessionHasNoErrors() ->assertRedirect(route('login')); return true; }); } } ================================================ FILE: tests/Feature/Auth/PasswordUpdateTest.php ================================================ create(); $response = $this ->actingAs($user) ->from('/profile') ->put('/password', [ 'current_password' => 'password', 'password' => 'new-password', 'password_confirmation' => 'new-password', ]); $response ->assertSessionHasNoErrors() ->assertRedirect('/profile'); $this->assertTrue(Hash::check('new-password', $user->refresh()->password)); } public function test_correct_password_must_be_provided_to_update_password(): void { $user = User::factory()->create(); $response = $this ->actingAs($user) ->from('/profile') ->put('/password', [ 'current_password' => 'wrong-password', 'password' => 'new-password', 'password_confirmation' => 'new-password', ]); $response ->assertSessionHasErrors('current_password') ->assertRedirect('/profile'); } } ================================================ FILE: tests/Feature/Auth/RegistrationTest.php ================================================ get('/register'); $response->assertStatus(200); } public function test_new_users_can_register(): void { $response = $this->post('/register', [ 'name' => 'Test User', 'email' => 'test@example.com', 'password' => 'password', 'password_confirmation' => 'password', ]); $this->assertAuthenticated(); $response->assertRedirect(route('admin.dashboard', absolute: false)); } } ================================================ FILE: tests/Feature/ExampleTest.php ================================================ get('/'); $response->assertStatus(200); } } ================================================ FILE: tests/Feature/Http/Controllers/Admin/UsersControllerTest.php ================================================ get('/'); $response->assertStatus(200); } } ================================================ FILE: tests/Feature/ProfileTest.php ================================================ create(); $response = $this ->actingAs($user) ->get('/profile'); $response->assertOk(); } public function test_profile_information_can_be_updated(): void { $user = User::factory()->create(); $response = $this ->actingAs($user) ->patch('/profile', [ 'name' => 'Test User', 'email' => 'test@example.com', ]); $response ->assertSessionHasNoErrors() ->assertRedirect('/profile'); $user->refresh(); $this->assertSame('Test User', $user->name); $this->assertSame('test@example.com', $user->email); $this->assertNull($user->email_verified_at); } public function test_email_verification_status_is_unchanged_when_the_email_address_is_unchanged(): void { $user = User::factory()->create(); $response = $this ->actingAs($user) ->patch('/profile', [ 'name' => 'Test User', 'email' => $user->email, ]); $response ->assertSessionHasNoErrors() ->assertRedirect('/profile'); $this->assertNotNull($user->refresh()->email_verified_at); } public function test_user_can_delete_their_account(): void { $user = User::factory()->create(); $response = $this ->actingAs($user) ->delete('/profile', [ 'password' => 'password', ]); $response ->assertSessionHasNoErrors() ->assertRedirect('/'); $this->assertGuest(); $this->assertNull(User::withoutTrashed()->where('id', $user->id)->first()); } public function test_correct_password_must_be_provided_to_delete_account(): void { $user = User::factory()->create(); $response = $this ->actingAs($user) ->from('/profile') ->delete('/profile', [ 'password' => 'wrong-password', ]); $response ->assertSessionHasErrors('password') ->assertRedirect('/profile'); $this->assertNotNull($user->fresh()); } } ================================================ FILE: tests/TestCase.php ================================================ assertTrue(true); } } ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "noImplicitAny": false, "strictNullChecks": false, "allowJs": true, "module": "ESNext", "moduleResolution": "bundler", "jsx": "preserve", "strict": true, "isolatedModules": true, "target": "ESNext", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "noEmit": true, "skipLibCheck": true, "paths": { "@/*": ["./resources/js/*"], "ziggy-js": ["./vendor/tightenco/ziggy"] } }, "include": ["resources/js/**/*.ts", "resources/js/**/*.d.ts", "resources/js/**/*.vue"] } ================================================ FILE: vite.config.js ================================================ import { defineConfig } from 'vite'; import laravel from 'laravel-vite-plugin'; import vue from '@vitejs/plugin-vue'; import path from 'path'; export default defineConfig({ plugins: [ laravel({ input: 'resources/js/app.ts', refresh: true, }), vue({ template: { transformAssetUrls: { base: null, includeAbsolute: false, }, }, }), ], resolve: { alias: { '~': path.resolve(__dirname, 'node_modules'), '@': path.resolve(__dirname, 'resources/js'), '@css': path.resolve(__dirname, 'resources/css'), '@img': path.resolve(__dirname, 'resources/img'), '@views': path.resolve(__dirname, 'resources/js/views'), '@pages': path.resolve(__dirname, 'resources/js/views/pages'), '@store': path.resolve(__dirname, 'resources/js/store'), '@Services': path.resolve(__dirname, 'resources/js/Services'), '@router': path.resolve(__dirname, 'resources/js/router'), '@Components': path.resolve(__dirname, 'resources/js/Components'), }, } });