From 0024080c28f2727e410241e2873dfb3c359efd90 Mon Sep 17 00:00:00 2001 From: JaguarJack Date: Mon, 5 Dec 2022 23:01:12 +0800 Subject: [PATCH] first commit --- .editorconfig | 18 + .env.example | 59 + .gitattributes | 11 + .gitee/ISSUE_TEMPLATE.zh-CN.md | 24 + .github/issue_template.md | 25 + .gitignore | 24 + .php-cs-fixer.dist.php | 101 + .prettierrc | 10 + README.md | 35 + app/Console/Kernel.php | 32 + app/Events/Create.php | 33 + app/Events/Test.php | 33 + app/Exceptions/Handler.php | 83 + app/Http/Controllers/Controller.php | 13 + app/Http/Kernel.php | 67 + app/Http/Middleware/Authenticate.php | 21 + app/Http/Middleware/EncryptCookies.php | 17 + .../PreventRequestsDuringMaintenance.php | 17 + .../Middleware/RedirectIfAuthenticated.php | 32 + app/Http/Middleware/TrimStrings.php | 19 + app/Http/Middleware/TrustHosts.php | 20 + app/Http/Middleware/TrustProxies.php | 28 + app/Http/Middleware/ValidateSignature.php | 22 + app/Http/Middleware/VerifyCsrfToken.php | 17 + app/Listeners/Command.php | 30 + app/Listeners/RouteMatched.php | 28 + app/Listeners/test.php | 27 + .../Modules/Users/Models/CatchController.php | 11 + app/Models/User.php | 45 + app/Providers/AppServiceProvider.php | 28 + app/Providers/AuthServiceProvider.php | 30 + app/Providers/BroadcastServiceProvider.php | 21 + app/Providers/EventServiceProvider.php | 53 + app/Providers/RouteServiceProvider.php | 52 + artisan | 53 + auto-imports.d.ts | 278 + bootstrap/app.php | 55 + bootstrap/cache/packages.php | 49 + bootstrap/cache/services.php | 237 + catch/config/catch.php | 145 + .../migrations/2022_11_14_034127_module.php | 47 + catch/src/Base/CatchController.php | 34 + catch/src/Base/CatchModel.php | 89 + catch/src/CatchAdmin.php | 363 + catch/src/Commands/CatchCommand.php | 141 + catch/src/Commands/Create/Controller.php | 100 + catch/src/Commands/Create/Event.php | 100 + catch/src/Commands/Create/Listener.php | 103 + catch/src/Commands/Create/Model.php | 174 + catch/src/Commands/InstallCommand.php | 316 + catch/src/Commands/Migrate/MigrateFresh.php | 55 + catch/src/Commands/Migrate/MigrateMake.php | 91 + catch/src/Commands/Migrate/MigrateRun.php | 74 + .../Commands/Migrate/MigrationRollback.php | 65 + catch/src/Commands/Migrate/SeedRun.php | 88 + catch/src/Commands/Migrate/SeederMake.php | 88 + catch/src/Commands/stubs/controller.stub | 35 + catch/src/Commands/stubs/event.stub | 36 + catch/src/Commands/stubs/listener.stub | 30 + catch/src/Commands/stubs/migration.stub | 31 + catch/src/Commands/stubs/model.stub | 16 + catch/src/Commands/stubs/seeder.stub | 18 + .../Contracts/ModuleRepositoryInterface.php | 35 + catch/src/Enums/Code.php | 79 + catch/src/Enums/Enum.php | 10 + catch/src/Enums/Status.php | 49 + catch/src/Events/Module/Created.php | 21 + catch/src/Events/Module/Creating.php | 21 + catch/src/Events/Module/Deleted.php | 22 + catch/src/Events/Module/Updated.php | 21 + catch/src/Events/Module/Updating.php | 21 + catch/src/Events/User.php | 27 + catch/src/Exceptions/CatchException.php | 65 + catch/src/Exceptions/FailedException.php | 22 + catch/src/Exceptions/Handler.php | 34 + .../Exceptions/UnMatchedTokenException.php | 22 + catch/src/Facade/Module.php | 25 + catch/src/Facade/Zipper.php | 24 + .../src/Listeners/RequestHandledListener.php | 57 + catch/src/Middleware/AuthMiddleware.php | 42 + .../src/Middleware/JsonResponseMiddleware.php | 21 + .../Providers/CatchAdminServiceProvider.php | 229 + .../Providers/CatchModuleServiceProvider.php | 63 + catch/src/Support/Composer.php | 108 + catch/src/Support/DB/Query.php | 55 + catch/src/Support/DB/SoftDelete.php | 19 + catch/src/Support/Macros/Blueprint.php | 140 + catch/src/Support/Macros/Builder.php | 107 + catch/src/Support/Macros/Collection.php | 65 + catch/src/Support/Macros/Register.php | 23 + .../Support/Module/Driver/DatabaseDriver.php | 171 + .../src/Support/Module/Driver/FileDriver.php | 193 + catch/src/Support/Module/Installer.php | 97 + catch/src/Support/Module/ModuleManager.php | 63 + catch/src/Support/Module/ModuleRepository.php | 144 + catch/src/Support/Tree.php | 61 + catch/src/Support/Zip/ZipRepository.php | 200 + catch/src/Support/Zip/Zipper.php | 626 ++ catch/src/Support/helpers.php | 122 + catch/src/Traits/DB/BaseOperate.php | 239 + catch/src/Traits/DB/ScopeTrait.php | 20 + catch/src/Traits/DB/Trans.php | 57 + composer.json | 76 + composer.lock | 8428 +++++++++++++++++ config/app.php | 215 + config/auth.php | 111 + config/broadcasting.php | 70 + config/cache.php | 110 + config/catch.php | 145 + config/cors.php | 34 + config/database.php | 151 + config/filesystems.php | 76 + config/hashing.php | 52 + config/jwt.php | 301 + config/logging.php | 122 + config/mail.php | 118 + config/queue.php | 93 + config/services.php | 34 + config/session.php | 201 + config/view.php | 36 + database/.gitignore | 1 + database/factories/UserFactory.php | 40 + ..._08_19_000000_create_failed_jobs_table.php | 37 + ...01_create_personal_access_tokens_table.php | 37 + database/seeders/DatabaseSeeder.php | 24 + database/seeders/good.php | 19 + lang/en/auth.php | 20 + lang/en/pagination.php | 19 + lang/en/passwords.php | 22 + lang/en/validation.php | 174 + .../Http/Controllers/GenerateController.php | 21 + .../Http/Controllers/ModuleController.php | 91 + .../Http/Controllers/SchemaController.php | 61 + modules/Develop/Listeners/CreatedListener.php | 39 + modules/Develop/Listeners/DeletedListener.php | 30 + modules/Develop/Models/Schemas.php | 129 + .../Providers/DevelopServiceProvider.php | 30 + modules/Develop/Providers/Install.php | 40 + .../Support/Generate/Create/Controller.php | 111 + .../Support/Generate/Create/Creator.php | 112 + .../Support/Generate/Create/FrontForm.php | 188 + .../Support/Generate/Create/FrontTable.php | 252 + .../Develop/Support/Generate/Create/Model.php | 276 + .../Support/Generate/Create/Request.php | 138 + .../Develop/Support/Generate/Create/Route.php | 123 + .../Support/Generate/Create/Schema.php | 317 + .../Develop/Support/Generate/Generator.php | 214 + modules/Develop/Support/Generate/Module.php | 86 + .../Support/Generate/stubs/controller.stub | 43 + .../Support/Generate/stubs/migration.stub | 30 + .../Develop/Support/Generate/stubs/model.stub | 36 + .../Support/Generate/stubs/provider.stub | 20 + .../Support/Generate/stubs/request.stub | 28 + .../Develop/Support/Generate/stubs/route.stub | 7 + .../Support/Generate/stubs/vue/form.stub | 38 + .../stubs/vue/formItems/cascader.stub | 3 + .../Generate/stubs/vue/formItems/date.stub | 9 + .../stubs/vue/formItems/datetime.stub | 9 + .../stubs/vue/formItems/input-number.stub | 3 + .../Generate/stubs/vue/formItems/input.stub | 3 + .../Generate/stubs/vue/formItems/radio.stub | 5 + .../Generate/stubs/vue/formItems/rate.stub | 3 + .../Generate/stubs/vue/formItems/select.stub | 10 + .../Generate/stubs/vue/formItems/switch.stub | 3 + .../stubs/vue/formItems/tree-select.stub | 9 + .../Generate/stubs/vue/formItems/tree.stub | 8 + .../Support/Generate/stubs/vue/paginate.stub | 12 + .../Support/Generate/stubs/vue/table.stub | 78 + .../Develop/database/migrations/Schemas.php | 28 + modules/Develop/route.php | 14 + .../views/generate/components/codeGen.vue | 109 + .../views/generate/components/store.ts | 112 + .../views/generate/components/structure.vue | 99 + modules/Develop/views/generate/index.vue | 10 + modules/Develop/views/module/create.vue | 89 + modules/Develop/views/module/index.vue | 103 + modules/Develop/views/router.ts | 32 + modules/Develop/views/schema/create.vue | 35 + modules/Develop/views/schema/index.vue | 120 + modules/Develop/views/schema/show.vue | 38 + modules/Develop/views/schema/steps/schema.vue | 107 + .../Develop/views/schema/steps/structure.vue | 231 + modules/Develop/views/schema/store/index.ts | 146 + modules/Options/Http/OptionController.php | 20 + modules/Options/README.md | 2 + modules/Options/Repository/DataRange.php | 38 + modules/Options/Repository/Factory.php | 28 + modules/Options/Repository/Modules.php | 25 + .../Options/Repository/OptionInterface.php | 11 + modules/Options/Repository/Status.php | 23 + modules/Permissions/Enums/DataRange.php | 38 + .../Http/Controllers/RolesController.php | 45 + .../Permissions/Http/Requests/RoleRequest.php | 44 + modules/Permissions/Models/RolesModel.php | 53 + .../Providers/PermissionsServiceProvider.php | 20 + .../2022_12_05_084442_create_roles.php | 41 + modules/Permissions/route.php | 11 + modules/Permissions/views/roles/create.vue | 95 + modules/Permissions/views/roles/index.vue | 80 + modules/Permissions/views/router.ts | 20 + modules/User/Events/Login.php | 24 + .../User/Http/Controllers/AuthController.php | 43 + .../User/Http/Controllers/UserController.php | 113 + modules/User/Listeners/Login.php | 96 + modules/User/Models/LogLogin.php | 37 + modules/User/Models/Users.php | 98 + .../User/Providers/UserServiceProvider.php | 32 + .../2022_12_04_060250_create_users.php | 54 + .../2022_12_04_062539_create_log_login.php | 40 + modules/User/route.php | 15 + modules/User/views/router.ts | 26 + modules/User/views/user/center.vue | 44 + .../User/views/user/components/loginLog.vue | 45 + .../User/views/user/components/operateLog.vue | 43 + .../User/views/user/components/profile.vue | 88 + modules/User/views/user/create.vue | 82 + modules/User/views/user/index.vue | 104 + package.json | 52 + phpunit.xml | 31 + postcss.config.js | 6 + public/.htaccess | 21 + public/admin.html | 22 + public/favicon.ico | Bin 0 -> 16958 bytes public/index.php | 55 + public/robots.txt | 2 + resources/admin/App.vue | 3 + resources/admin/app.ts | 7 + resources/admin/assets/404.png | Bin 0 -> 68972 bytes resources/admin/assets/enum/app.ts | 35 + resources/admin/assets/login-left.png | Bin 0 -> 40792 bytes resources/admin/assets/logo.png | Bin 0 -> 9353 bytes resources/admin/components/404/index.vue | 27 + resources/admin/components/HelloWorld.vue | 97 + .../admin/components/admin/Select/index.vue | 49 + .../admin/components/admin/buttons/add.vue | 16 + .../components/admin/buttons/destroy.vue | 16 + .../admin/components/admin/buttons/show.vue | 16 + .../admin/components/admin/buttons/update.vue | 18 + .../admin/components/admin/dialog/index.vue | 100 + .../admin/components/admin/status/index.vue | 24 + .../admin/components/breadcrumbs/index.vue | 59 + resources/admin/components/icon/index.vue | 23 + resources/admin/composables/curd/useCreate.ts | 69 + .../admin/composables/curd/useDestroy.ts | 36 + .../admin/composables/curd/useEnabled.ts | 27 + .../admin/composables/curd/useGetList.ts | 75 + resources/admin/composables/curd/useShow.ts | 14 + resources/admin/enum/app.ts | 43 + resources/admin/env.d.ts | 8 + resources/admin/i18n/index.ts | 22 + resources/admin/i18n/languages/en.ts | 150 + resources/admin/i18n/languages/zh.ts | 155 + .../admin/layout/components/Menu/index.vue | 124 + .../admin/layout/components/Menu/item.vue | 58 + .../admin/layout/components/Menu/mask.vue | 9 + .../admin/layout/components/Menu/menus.vue | 66 + resources/admin/layout/components/content.vue | 30 + .../admin/layout/components/header/index.vue | 33 + .../admin/layout/components/header/lang.vue | 38 + .../admin/layout/components/header/logo.vue | 22 + .../layout/components/header/notification.vue | 49 + .../layout/components/header/profile.vue | 34 + .../admin/layout/components/header/search.vue | 58 + .../admin/layout/components/header/theme.vue | 22 + resources/admin/layout/components/sider.vue | 60 + resources/admin/layout/index.vue | 8 + resources/admin/router/constantRoutes.ts | 9 + resources/admin/router/guard/index.ts | 68 + resources/admin/router/index.ts | 65 + resources/admin/stores/index.ts | 10 + resources/admin/stores/modules/app/index.ts | 76 + resources/admin/stores/modules/user/index.ts | 152 + .../admin/stores/modules/user/permissions.ts | 193 + resources/admin/styles/element.scss | 61 + resources/admin/styles/index.scss | 12 + resources/admin/styles/tailwind.css | 22 + resources/admin/styles/theme/dark.scss | 36 + resources/admin/styles/theme/index.scss | 3 + resources/admin/styles/theme/light.scss | 25 + resources/admin/styles/var.scss | 12 + resources/admin/support/cache.ts | 32 + resources/admin/support/catchAdmin.ts | 88 + resources/admin/support/helper.ts | 89 + resources/admin/support/http.ts | 213 + resources/admin/support/message.ts | 59 + resources/admin/support/progress.ts | 14 + resources/admin/types/Permission.ts | 27 + resources/admin/types/Role.ts | 1 + resources/admin/types/User.ts | 22 + resources/admin/types/menu.ts | 33 + resources/admin/types/responseData.ts | 7 + resources/admin/types/router.ts | 15 + .../admin/views/dashboard/dependencies.vue | 36 + resources/admin/views/dashboard/index.vue | 84 + resources/admin/views/dashboard/introduce.vue | 121 + resources/admin/views/dashboard/project.vue | 39 + resources/admin/views/login/index.vue | 74 + resources/admin/views/login/login.ts | 60 + resources/views/welcome.blade.php | 0 routes/api.php | 28 + routes/channels.php | 18 + routes/console.php | 19 + routes/web.php | 14 + storage/app/.gitignore | 3 + storage/app/public/.gitignore | 2 + storage/framework/.gitignore | 9 + storage/framework/cache/.gitignore | 3 + storage/framework/cache/data/.gitignore | 2 + storage/framework/sessions/.gitignore | 2 + storage/framework/testing/.gitignore | 2 + storage/framework/views/.gitignore | 2 + storage/logs/.gitignore | 2 + tailwind.config.js | 21 + tests/CreatesApplication.php | 22 + tests/ExampleTest.php | 5 + tests/Feature/ExampleTest.php | 21 + tests/Pest.php | 45 + tests/TestCase.php | 10 + tests/Unit/ExampleTest.php | 18 + tsconfig.json | 43 + tsconfig.node.json | 8 + vite.config.js | 103 + 322 files changed, 27698 insertions(+) create mode 100644 .editorconfig create mode 100644 .env.example create mode 100644 .gitattributes create mode 100644 .gitee/ISSUE_TEMPLATE.zh-CN.md create mode 100644 .github/issue_template.md create mode 100644 .gitignore create mode 100644 .php-cs-fixer.dist.php create mode 100644 .prettierrc create mode 100644 README.md create mode 100644 app/Console/Kernel.php create mode 100644 app/Events/Create.php create mode 100644 app/Events/Test.php create mode 100644 app/Exceptions/Handler.php create mode 100644 app/Http/Controllers/Controller.php create mode 100644 app/Http/Kernel.php create mode 100644 app/Http/Middleware/Authenticate.php create mode 100644 app/Http/Middleware/EncryptCookies.php create mode 100644 app/Http/Middleware/PreventRequestsDuringMaintenance.php create mode 100644 app/Http/Middleware/RedirectIfAuthenticated.php create mode 100644 app/Http/Middleware/TrimStrings.php create mode 100644 app/Http/Middleware/TrustHosts.php create mode 100644 app/Http/Middleware/TrustProxies.php create mode 100644 app/Http/Middleware/ValidateSignature.php create mode 100644 app/Http/Middleware/VerifyCsrfToken.php create mode 100644 app/Listeners/Command.php create mode 100644 app/Listeners/RouteMatched.php create mode 100644 app/Listeners/test.php create mode 100644 app/Models/Modules/Users/Models/CatchController.php create mode 100644 app/Models/User.php create mode 100644 app/Providers/AppServiceProvider.php create mode 100644 app/Providers/AuthServiceProvider.php create mode 100644 app/Providers/BroadcastServiceProvider.php create mode 100644 app/Providers/EventServiceProvider.php create mode 100644 app/Providers/RouteServiceProvider.php create mode 100644 artisan create mode 100644 auto-imports.d.ts create mode 100644 bootstrap/app.php create mode 100755 bootstrap/cache/packages.php create mode 100755 bootstrap/cache/services.php create mode 100644 catch/config/catch.php create mode 100644 catch/database/migrations/2022_11_14_034127_module.php create mode 100644 catch/src/Base/CatchController.php create mode 100644 catch/src/Base/CatchModel.php create mode 100644 catch/src/CatchAdmin.php create mode 100644 catch/src/Commands/CatchCommand.php create mode 100644 catch/src/Commands/Create/Controller.php create mode 100644 catch/src/Commands/Create/Event.php create mode 100644 catch/src/Commands/Create/Listener.php create mode 100644 catch/src/Commands/Create/Model.php create mode 100644 catch/src/Commands/InstallCommand.php create mode 100644 catch/src/Commands/Migrate/MigrateFresh.php create mode 100644 catch/src/Commands/Migrate/MigrateMake.php create mode 100644 catch/src/Commands/Migrate/MigrateRun.php create mode 100644 catch/src/Commands/Migrate/MigrationRollback.php create mode 100644 catch/src/Commands/Migrate/SeedRun.php create mode 100644 catch/src/Commands/Migrate/SeederMake.php create mode 100644 catch/src/Commands/stubs/controller.stub create mode 100644 catch/src/Commands/stubs/event.stub create mode 100644 catch/src/Commands/stubs/listener.stub create mode 100644 catch/src/Commands/stubs/migration.stub create mode 100644 catch/src/Commands/stubs/model.stub create mode 100644 catch/src/Commands/stubs/seeder.stub create mode 100644 catch/src/Contracts/ModuleRepositoryInterface.php create mode 100644 catch/src/Enums/Code.php create mode 100644 catch/src/Enums/Enum.php create mode 100644 catch/src/Enums/Status.php create mode 100644 catch/src/Events/Module/Created.php create mode 100644 catch/src/Events/Module/Creating.php create mode 100644 catch/src/Events/Module/Deleted.php create mode 100644 catch/src/Events/Module/Updated.php create mode 100644 catch/src/Events/Module/Updating.php create mode 100644 catch/src/Events/User.php create mode 100644 catch/src/Exceptions/CatchException.php create mode 100644 catch/src/Exceptions/FailedException.php create mode 100644 catch/src/Exceptions/Handler.php create mode 100644 catch/src/Exceptions/UnMatchedTokenException.php create mode 100644 catch/src/Facade/Module.php create mode 100644 catch/src/Facade/Zipper.php create mode 100644 catch/src/Listeners/RequestHandledListener.php create mode 100644 catch/src/Middleware/AuthMiddleware.php create mode 100644 catch/src/Middleware/JsonResponseMiddleware.php create mode 100644 catch/src/Providers/CatchAdminServiceProvider.php create mode 100644 catch/src/Providers/CatchModuleServiceProvider.php create mode 100644 catch/src/Support/Composer.php create mode 100644 catch/src/Support/DB/Query.php create mode 100644 catch/src/Support/DB/SoftDelete.php create mode 100644 catch/src/Support/Macros/Blueprint.php create mode 100644 catch/src/Support/Macros/Builder.php create mode 100644 catch/src/Support/Macros/Collection.php create mode 100644 catch/src/Support/Macros/Register.php create mode 100644 catch/src/Support/Module/Driver/DatabaseDriver.php create mode 100644 catch/src/Support/Module/Driver/FileDriver.php create mode 100644 catch/src/Support/Module/Installer.php create mode 100644 catch/src/Support/Module/ModuleManager.php create mode 100644 catch/src/Support/Module/ModuleRepository.php create mode 100644 catch/src/Support/Tree.php create mode 100644 catch/src/Support/Zip/ZipRepository.php create mode 100644 catch/src/Support/Zip/Zipper.php create mode 100644 catch/src/Support/helpers.php create mode 100644 catch/src/Traits/DB/BaseOperate.php create mode 100644 catch/src/Traits/DB/ScopeTrait.php create mode 100644 catch/src/Traits/DB/Trans.php create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 config/app.php create mode 100644 config/auth.php create mode 100644 config/broadcasting.php create mode 100644 config/cache.php create mode 100644 config/catch.php create mode 100644 config/cors.php create mode 100644 config/database.php create mode 100644 config/filesystems.php create mode 100644 config/hashing.php create mode 100644 config/jwt.php create mode 100644 config/logging.php create mode 100644 config/mail.php create mode 100644 config/queue.php create mode 100644 config/services.php create mode 100644 config/session.php create mode 100644 config/view.php create mode 100644 database/.gitignore create mode 100644 database/factories/UserFactory.php create mode 100644 database/migrations/2019_08_19_000000_create_failed_jobs_table.php create mode 100644 database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php create mode 100644 database/seeders/DatabaseSeeder.php create mode 100644 database/seeders/good.php create mode 100644 lang/en/auth.php create mode 100644 lang/en/pagination.php create mode 100644 lang/en/passwords.php create mode 100644 lang/en/validation.php create mode 100644 modules/Develop/Http/Controllers/GenerateController.php create mode 100644 modules/Develop/Http/Controllers/ModuleController.php create mode 100644 modules/Develop/Http/Controllers/SchemaController.php create mode 100644 modules/Develop/Listeners/CreatedListener.php create mode 100644 modules/Develop/Listeners/DeletedListener.php create mode 100644 modules/Develop/Models/Schemas.php create mode 100644 modules/Develop/Providers/DevelopServiceProvider.php create mode 100644 modules/Develop/Providers/Install.php create mode 100644 modules/Develop/Support/Generate/Create/Controller.php create mode 100644 modules/Develop/Support/Generate/Create/Creator.php create mode 100644 modules/Develop/Support/Generate/Create/FrontForm.php create mode 100644 modules/Develop/Support/Generate/Create/FrontTable.php create mode 100644 modules/Develop/Support/Generate/Create/Model.php create mode 100644 modules/Develop/Support/Generate/Create/Request.php create mode 100644 modules/Develop/Support/Generate/Create/Route.php create mode 100644 modules/Develop/Support/Generate/Create/Schema.php create mode 100644 modules/Develop/Support/Generate/Generator.php create mode 100644 modules/Develop/Support/Generate/Module.php create mode 100644 modules/Develop/Support/Generate/stubs/controller.stub create mode 100644 modules/Develop/Support/Generate/stubs/migration.stub create mode 100644 modules/Develop/Support/Generate/stubs/model.stub create mode 100644 modules/Develop/Support/Generate/stubs/provider.stub create mode 100644 modules/Develop/Support/Generate/stubs/request.stub create mode 100644 modules/Develop/Support/Generate/stubs/route.stub create mode 100644 modules/Develop/Support/Generate/stubs/vue/form.stub create mode 100644 modules/Develop/Support/Generate/stubs/vue/formItems/cascader.stub create mode 100644 modules/Develop/Support/Generate/stubs/vue/formItems/date.stub create mode 100644 modules/Develop/Support/Generate/stubs/vue/formItems/datetime.stub create mode 100644 modules/Develop/Support/Generate/stubs/vue/formItems/input-number.stub create mode 100644 modules/Develop/Support/Generate/stubs/vue/formItems/input.stub create mode 100644 modules/Develop/Support/Generate/stubs/vue/formItems/radio.stub create mode 100644 modules/Develop/Support/Generate/stubs/vue/formItems/rate.stub create mode 100644 modules/Develop/Support/Generate/stubs/vue/formItems/select.stub create mode 100644 modules/Develop/Support/Generate/stubs/vue/formItems/switch.stub create mode 100644 modules/Develop/Support/Generate/stubs/vue/formItems/tree-select.stub create mode 100644 modules/Develop/Support/Generate/stubs/vue/formItems/tree.stub create mode 100644 modules/Develop/Support/Generate/stubs/vue/paginate.stub create mode 100644 modules/Develop/Support/Generate/stubs/vue/table.stub create mode 100644 modules/Develop/database/migrations/Schemas.php create mode 100644 modules/Develop/route.php create mode 100644 modules/Develop/views/generate/components/codeGen.vue create mode 100644 modules/Develop/views/generate/components/store.ts create mode 100644 modules/Develop/views/generate/components/structure.vue create mode 100644 modules/Develop/views/generate/index.vue create mode 100644 modules/Develop/views/module/create.vue create mode 100644 modules/Develop/views/module/index.vue create mode 100644 modules/Develop/views/router.ts create mode 100644 modules/Develop/views/schema/create.vue create mode 100644 modules/Develop/views/schema/index.vue create mode 100644 modules/Develop/views/schema/show.vue create mode 100644 modules/Develop/views/schema/steps/schema.vue create mode 100644 modules/Develop/views/schema/steps/structure.vue create mode 100644 modules/Develop/views/schema/store/index.ts create mode 100644 modules/Options/Http/OptionController.php create mode 100644 modules/Options/README.md create mode 100644 modules/Options/Repository/DataRange.php create mode 100644 modules/Options/Repository/Factory.php create mode 100644 modules/Options/Repository/Modules.php create mode 100644 modules/Options/Repository/OptionInterface.php create mode 100644 modules/Options/Repository/Status.php create mode 100644 modules/Permissions/Enums/DataRange.php create mode 100644 modules/Permissions/Http/Controllers/RolesController.php create mode 100644 modules/Permissions/Http/Requests/RoleRequest.php create mode 100644 modules/Permissions/Models/RolesModel.php create mode 100644 modules/Permissions/Providers/PermissionsServiceProvider.php create mode 100644 modules/Permissions/database/migrations/2022_12_05_084442_create_roles.php create mode 100644 modules/Permissions/route.php create mode 100644 modules/Permissions/views/roles/create.vue create mode 100644 modules/Permissions/views/roles/index.vue create mode 100644 modules/Permissions/views/router.ts create mode 100644 modules/User/Events/Login.php create mode 100644 modules/User/Http/Controllers/AuthController.php create mode 100644 modules/User/Http/Controllers/UserController.php create mode 100644 modules/User/Listeners/Login.php create mode 100644 modules/User/Models/LogLogin.php create mode 100644 modules/User/Models/Users.php create mode 100644 modules/User/Providers/UserServiceProvider.php create mode 100644 modules/User/database/migrations/2022_12_04_060250_create_users.php create mode 100644 modules/User/database/migrations/2022_12_04_062539_create_log_login.php create mode 100644 modules/User/route.php create mode 100644 modules/User/views/router.ts create mode 100644 modules/User/views/user/center.vue create mode 100644 modules/User/views/user/components/loginLog.vue create mode 100644 modules/User/views/user/components/operateLog.vue create mode 100644 modules/User/views/user/components/profile.vue create mode 100644 modules/User/views/user/create.vue create mode 100644 modules/User/views/user/index.vue create mode 100644 package.json create mode 100644 phpunit.xml create mode 100644 postcss.config.js create mode 100644 public/.htaccess create mode 100644 public/admin.html create mode 100644 public/favicon.ico create mode 100644 public/index.php create mode 100644 public/robots.txt create mode 100644 resources/admin/App.vue create mode 100644 resources/admin/app.ts create mode 100644 resources/admin/assets/404.png create mode 100644 resources/admin/assets/enum/app.ts create mode 100644 resources/admin/assets/login-left.png create mode 100644 resources/admin/assets/logo.png create mode 100644 resources/admin/components/404/index.vue create mode 100644 resources/admin/components/HelloWorld.vue create mode 100644 resources/admin/components/admin/Select/index.vue create mode 100644 resources/admin/components/admin/buttons/add.vue create mode 100644 resources/admin/components/admin/buttons/destroy.vue create mode 100644 resources/admin/components/admin/buttons/show.vue create mode 100644 resources/admin/components/admin/buttons/update.vue create mode 100644 resources/admin/components/admin/dialog/index.vue create mode 100644 resources/admin/components/admin/status/index.vue create mode 100644 resources/admin/components/breadcrumbs/index.vue create mode 100644 resources/admin/components/icon/index.vue create mode 100644 resources/admin/composables/curd/useCreate.ts create mode 100644 resources/admin/composables/curd/useDestroy.ts create mode 100644 resources/admin/composables/curd/useEnabled.ts create mode 100644 resources/admin/composables/curd/useGetList.ts create mode 100644 resources/admin/composables/curd/useShow.ts create mode 100644 resources/admin/enum/app.ts create mode 100644 resources/admin/env.d.ts create mode 100644 resources/admin/i18n/index.ts create mode 100644 resources/admin/i18n/languages/en.ts create mode 100644 resources/admin/i18n/languages/zh.ts create mode 100644 resources/admin/layout/components/Menu/index.vue create mode 100644 resources/admin/layout/components/Menu/item.vue create mode 100644 resources/admin/layout/components/Menu/mask.vue create mode 100644 resources/admin/layout/components/Menu/menus.vue create mode 100644 resources/admin/layout/components/content.vue create mode 100644 resources/admin/layout/components/header/index.vue create mode 100644 resources/admin/layout/components/header/lang.vue create mode 100644 resources/admin/layout/components/header/logo.vue create mode 100644 resources/admin/layout/components/header/notification.vue create mode 100644 resources/admin/layout/components/header/profile.vue create mode 100644 resources/admin/layout/components/header/search.vue create mode 100644 resources/admin/layout/components/header/theme.vue create mode 100644 resources/admin/layout/components/sider.vue create mode 100644 resources/admin/layout/index.vue create mode 100644 resources/admin/router/constantRoutes.ts create mode 100644 resources/admin/router/guard/index.ts create mode 100644 resources/admin/router/index.ts create mode 100644 resources/admin/stores/index.ts create mode 100644 resources/admin/stores/modules/app/index.ts create mode 100644 resources/admin/stores/modules/user/index.ts create mode 100644 resources/admin/stores/modules/user/permissions.ts create mode 100644 resources/admin/styles/element.scss create mode 100644 resources/admin/styles/index.scss create mode 100644 resources/admin/styles/tailwind.css create mode 100644 resources/admin/styles/theme/dark.scss create mode 100644 resources/admin/styles/theme/index.scss create mode 100644 resources/admin/styles/theme/light.scss create mode 100644 resources/admin/styles/var.scss create mode 100644 resources/admin/support/cache.ts create mode 100644 resources/admin/support/catchAdmin.ts create mode 100644 resources/admin/support/helper.ts create mode 100644 resources/admin/support/http.ts create mode 100644 resources/admin/support/message.ts create mode 100644 resources/admin/support/progress.ts create mode 100644 resources/admin/types/Permission.ts create mode 100644 resources/admin/types/Role.ts create mode 100644 resources/admin/types/User.ts create mode 100644 resources/admin/types/menu.ts create mode 100644 resources/admin/types/responseData.ts create mode 100644 resources/admin/types/router.ts create mode 100644 resources/admin/views/dashboard/dependencies.vue create mode 100644 resources/admin/views/dashboard/index.vue create mode 100644 resources/admin/views/dashboard/introduce.vue create mode 100644 resources/admin/views/dashboard/project.vue create mode 100644 resources/admin/views/login/index.vue create mode 100644 resources/admin/views/login/login.ts create mode 100644 resources/views/welcome.blade.php create mode 100644 routes/api.php create mode 100644 routes/channels.php create mode 100644 routes/console.php create mode 100644 routes/web.php create mode 100644 storage/app/.gitignore create mode 100644 storage/app/public/.gitignore create mode 100644 storage/framework/.gitignore create mode 100644 storage/framework/cache/.gitignore create mode 100644 storage/framework/cache/data/.gitignore create mode 100644 storage/framework/sessions/.gitignore create mode 100644 storage/framework/testing/.gitignore create mode 100644 storage/framework/views/.gitignore create mode 100644 storage/logs/.gitignore create mode 100644 tailwind.config.js create mode 100644 tests/CreatesApplication.php create mode 100644 tests/ExampleTest.php create mode 100644 tests/Feature/ExampleTest.php create mode 100644 tests/Pest.php create mode 100644 tests/TestCase.php create mode 100644 tests/Unit/ExampleTest.php create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.js diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8f0de65 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +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 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..fcf7598 --- /dev/null +++ b/.env.example @@ -0,0 +1,59 @@ +APP_NAME=Laravel +APP_ENV=local +APP_KEY= +APP_DEBUG=true +APP_URL=http://localhost + +LOG_CHANNEL=stack +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=mysql +DB_HOST=127.0.0.1 +DB_PORT=3306 +DB_DATABASE=laravel +DB_USERNAME=root +DB_PASSWORD= +DB_PREFIX= + +BROADCAST_DRIVER=log +CACHE_DRIVER=file +FILESYSTEM_DISK=local +QUEUE_CONNECTION=sync +SESSION_DRIVER=file +SESSION_LIFETIME=120 + +MEMCACHED_HOST=127.0.0.1 + +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_MAILER=smtp +MAIL_HOST=mailhog +MAIL_PORT=1025 +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=null +MAIL_FROM_ADDRESS="hello@example.com" +MAIL_FROM_NAME="${APP_NAME}" + +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_DEFAULT_REGION=us-east-1 +AWS_BUCKET= +AWS_USE_PATH_STYLE_ENDPOINT=false + +PUSHER_APP_ID= +PUSHER_APP_KEY= +PUSHER_APP_SECRET= +PUSHER_HOST= +PUSHER_PORT=443 +PUSHER_SCHEME=https +PUSHER_APP_CLUSTER=mt1 + +VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}" +VITE_PUSHER_HOST="${PUSHER_HOST}" +VITE_PUSHER_PORT="${PUSHER_PORT}" +VITE_PUSHER_SCHEME="${PUSHER_SCHEME}" +VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7dbbf41 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +* text=auto + +*.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 diff --git a/.gitee/ISSUE_TEMPLATE.zh-CN.md b/.gitee/ISSUE_TEMPLATE.zh-CN.md new file mode 100644 index 0000000..dfe1c92 --- /dev/null +++ b/.gitee/ISSUE_TEMPLATE.zh-CN.md @@ -0,0 +1,24 @@ +# 环境 +- 操作系统: +- php 版本: +- Laravel 版本: +- Mysql 版本: +- web 服务器: + +# 问题 +- 问题描述: +- 问题截图: + +# 结果 +- 实际结果: +- 预期结果: + +# 分析 +- 所做的尝试: + - + - + +# 方案: +- 解决方案: + +> 请在问题解决后关闭 issue diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 0000000..cdaf1ba --- /dev/null +++ b/.github/issue_template.md @@ -0,0 +1,25 @@ +# 环境 +- 操作系统: +- php 版本: +- Laravel 版本: +- Mysql 版本: +- web 服务器: + +# 问题 +- 问题描述: + +- 问题截图: + +# 结果 +- 实际结果: +- 预期结果: + +# 分析 +- 所做的尝试: + - + - + +# 方案: +- 解决方案: + +> 请在问题解决后关闭 issue diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b1484a6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +/node_modules +/public/build +/public/hot +/public/storage +/storage/*.key +/vendor +/fixer +.env +.env.backup +.env.production +.phpunit.result.cache +.php-cs-fixer.cache +Homestead.json +Homestead.yaml +auth.json +npm-debug.log +yarn-error.log +yarn.lock +composer.lock +/.fleet +/.idea +/.vscode +components.d.ts +auto-imports.d.ts diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..7c24876 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,101 @@ +exclude('packages') + //// ->notPath('./packages/test.php') + // in 配置需要规则的目录 + ->in([ + __DIR__.DIRECTORY_SEPARATOR.'app', + + __DIR__.DIRECTORY_SEPARATOR.'catch', + + __DIR__.DIRECTORY_SEPARATOR.'modules', + ]) + // 排除 . 开头的文件 + ->ignoreDotFiles(true) + // vcs 文件 + ->ignoreVCS(true); + +$config = new Config(); + +return $config->setRules([ + '@PSR1' => true, // psr1 + + '@PSR2' => true, // psr2 规范 + + '@PSR12' => true, // psr12 规范 + + 'binary_operator_spaces' => true, // 二元操作符号空格 $a=1 => $a = 1; + + 'array_syntax' => [ + 'syntax' => 'short', // array('1') => ['1'] + ], + + 'no_trailing_comma_in_singleline_array' => true, // -$a = array('sample', ); => $a = array('sample'); + + 'trim_array_spaces' => true, // array( 'a', 'b' ); => array('a', 'b') + + 'single_trait_insert_per_statement' => false, + + 'standardize_not_equals' => true, // "!=" => "<>" + + 'magic_constant_casing' => true, // __dir__ => __DIR__ + + 'native_function_casing' => true, // STRLEN($str); => strlen($str); + + 'cast_spaces' => true, // (int)$b => (int) $b + + 'simplified_if_return' => true, // if ($foo) { return true; } return false; => return (bool) ($foo) ; + + 'no_unused_imports' => true, // use \DateTime; -use \Exception; => use \DateTime; + + 'not_operator_with_successor_space' => true, // if (!$bar) => if (! $bar) + + /** + * // function example($b) { + if ($b) { + return; + } + - return; + */ + 'no_useless_return' => true, + + /** + * function a() { + - $a = 1; + - return $a; + + return 1; + */ + 'return_assignment' => true, + + /** + - true, + + /** + * $foo = [ + - 'bar' => [ + - 'baz' => true, + - ], + + 'bar' => [ + + 'baz' => true, + + ], + */ + 'array_indentation' => true, + + /** + * -$sample = $b [ 'a' ] [ 'b' ]; + +$sample = $b['a']['b']; + */ + 'no_spaces_around_offset' => true, + + 'concat_space' => true, // $a.$b => $a . $b +])->setFinder($finder); diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..b93b9c0 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "semi": false, + "printWidth": 200, + "tabWidth": 2, + "useTabs": false, + "singleQuote": true, + "arrowParens": "avoid", + "trailingComma": "all", + "bracketSpacing": true +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..a04afdc --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +## About CatchAdmin + + +## 安装 +```shell +yarn install + +yarn dev +``` + +``` +composer install + +php artisan serve +``` +## 规范 +### PHP +使用 fixer 进行代码检查, 具体请查看根目录下 `.php-cs-fixer.dist.php` 文件的规范,还需要进行以下两步骤 +```shell +mkdir path && cd path // any path name you set +``` +```shell +composer require --working-dir=path friendsofphp/php-cs-fixer +``` +安装完成之后可以使用 +```shell +composer cs +``` +进行代码格式化,这个命令会直接修改文件完成修正,如果只需要查看格式是否正确,那么使用 +```shell +composer cs-diff +``` +会列出不符合的代码格式 + + diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php new file mode 100644 index 0000000..d8bc1d2 --- /dev/null +++ b/app/Console/Kernel.php @@ -0,0 +1,32 @@ +command('inspire')->hourly(); + } + + /** + * Register the commands for the application. + * + * @return void + */ + protected function commands() + { + $this->load(__DIR__.'/Commands'); + + require base_path('routes/console.php'); + } +} diff --git a/app/Events/Create.php b/app/Events/Create.php new file mode 100644 index 0000000..cb5ed4a --- /dev/null +++ b/app/Events/Create.php @@ -0,0 +1,33 @@ +, \Psr\Log\LogLevel::*> + */ + protected $levels = [ + // + ]; + + /** + * A list of the exception types that are not reported. + * + * @var array> + */ + protected $dontReport = [ + // + ]; + + /** + * A list of the inputs that are never flashed to the session on 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) { + // + }); + } + + + /** + * render + * + * @param $request + * @param Throwable $e + * @return JsonResponse|Response + * @throws Throwable + */ + public function render($request, Throwable $e): JsonResponse|Response + { + $message = $e->getMessage(); + + if (method_exists($e, 'getStatusCode')) { + if ($e->getStatusCode() == Response::HTTP_NOT_FOUND) { + $message = '路由未找到或未注册'; + } + } + + $e = new FailedException($message ?: 'Server Error'); + + $response = parent::render($request, $e); + + $response->header('Access-Control-Allow-Origin', '*'); + $response->header('Access-Control-Allow-Methods', '*'); + $response->header('Access-Control-Allow-Headers', '*'); + + return $response; + } +} diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php new file mode 100644 index 0000000..a0a2a8a --- /dev/null +++ b/app/Http/Controllers/Controller.php @@ -0,0 +1,13 @@ + + */ + protected $middleware = [ + // \App\Http\Middleware\TrustHosts::class, + \App\Http\Middleware\TrustProxies::class, + \Illuminate\Http\Middleware\HandleCors::class, + \App\Http\Middleware\PreventRequestsDuringMaintenance::class, + \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, + \App\Http\Middleware\TrimStrings::class, + \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, + ]; + + /** + * The application's route middleware groups. + * + * @var array> + */ + protected $middlewareGroups = [ + 'web' => [ + \App\Http\Middleware\EncryptCookies::class, + \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, + \Illuminate\Session\Middleware\StartSession::class, + \Illuminate\View\Middleware\ShareErrorsFromSession::class, + \App\Http\Middleware\VerifyCsrfToken::class, + \Illuminate\Routing\Middleware\SubstituteBindings::class, + ], + + 'api' => [ + // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, + 'throttle:api', + \Illuminate\Routing\Middleware\SubstituteBindings::class + ], + ]; + + /** + * The application's route middleware. + * + * These middleware may be assigned to groups or used individually. + * + * @var array + */ + protected $routeMiddleware = [ + 'auth' => \App\Http\Middleware\Authenticate::class, + 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, + 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class, + 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, + 'can' => \Illuminate\Auth\Middleware\Authorize::class, + 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, + 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, + 'signed' => \App\Http\Middleware\ValidateSignature::class, + 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, + 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + ]; +} diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php new file mode 100644 index 0000000..704089a --- /dev/null +++ b/app/Http/Middleware/Authenticate.php @@ -0,0 +1,21 @@ +expectsJson()) { + return route('login'); + } + } +} diff --git a/app/Http/Middleware/EncryptCookies.php b/app/Http/Middleware/EncryptCookies.php new file mode 100644 index 0000000..867695b --- /dev/null +++ b/app/Http/Middleware/EncryptCookies.php @@ -0,0 +1,17 @@ + + */ + protected $except = [ + // + ]; +} diff --git a/app/Http/Middleware/PreventRequestsDuringMaintenance.php b/app/Http/Middleware/PreventRequestsDuringMaintenance.php new file mode 100644 index 0000000..74cbd9a --- /dev/null +++ b/app/Http/Middleware/PreventRequestsDuringMaintenance.php @@ -0,0 +1,17 @@ + + */ + protected $except = [ + // + ]; +} diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php new file mode 100644 index 0000000..a2813a0 --- /dev/null +++ b/app/Http/Middleware/RedirectIfAuthenticated.php @@ -0,0 +1,32 @@ +check()) { + return redirect(RouteServiceProvider::HOME); + } + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/TrimStrings.php b/app/Http/Middleware/TrimStrings.php new file mode 100644 index 0000000..88cadca --- /dev/null +++ b/app/Http/Middleware/TrimStrings.php @@ -0,0 +1,19 @@ + + */ + protected $except = [ + 'current_password', + 'password', + 'password_confirmation', + ]; +} diff --git a/app/Http/Middleware/TrustHosts.php b/app/Http/Middleware/TrustHosts.php new file mode 100644 index 0000000..7186414 --- /dev/null +++ b/app/Http/Middleware/TrustHosts.php @@ -0,0 +1,20 @@ + + */ + public function hosts() + { + return [ + $this->allSubdomainsOfApplicationUrl(), + ]; + } +} diff --git a/app/Http/Middleware/TrustProxies.php b/app/Http/Middleware/TrustProxies.php new file mode 100644 index 0000000..3391630 --- /dev/null +++ b/app/Http/Middleware/TrustProxies.php @@ -0,0 +1,28 @@ +|string|null + */ + protected $proxies; + + /** + * The headers that should be used to detect proxies. + * + * @var int + */ + protected $headers = + Request::HEADER_X_FORWARDED_FOR | + Request::HEADER_X_FORWARDED_HOST | + Request::HEADER_X_FORWARDED_PORT | + Request::HEADER_X_FORWARDED_PROTO | + Request::HEADER_X_FORWARDED_AWS_ELB; +} diff --git a/app/Http/Middleware/ValidateSignature.php b/app/Http/Middleware/ValidateSignature.php new file mode 100644 index 0000000..093bf64 --- /dev/null +++ b/app/Http/Middleware/ValidateSignature.php @@ -0,0 +1,22 @@ + + */ + protected $except = [ + // 'fbclid', + // 'utm_campaign', + // 'utm_content', + // 'utm_medium', + // 'utm_source', + // 'utm_term', + ]; +} diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php new file mode 100644 index 0000000..9e86521 --- /dev/null +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -0,0 +1,17 @@ + + */ + protected $except = [ + // + ]; +} diff --git a/app/Listeners/Command.php b/app/Listeners/Command.php new file mode 100644 index 0000000..70c9547 --- /dev/null +++ b/app/Listeners/Command.php @@ -0,0 +1,30 @@ +command); + } +} diff --git a/app/Listeners/RouteMatched.php b/app/Listeners/RouteMatched.php new file mode 100644 index 0000000..a23ece1 --- /dev/null +++ b/app/Listeners/RouteMatched.php @@ -0,0 +1,28 @@ +route); + } +} diff --git a/app/Listeners/test.php b/app/Listeners/test.php new file mode 100644 index 0000000..74e3eeb --- /dev/null +++ b/app/Listeners/test.php @@ -0,0 +1,27 @@ + + */ + protected $fillable = [ + 'name', + 'email', + 'password', + ]; + + /** + * The attributes that should be hidden for serialization. + * + * @var array + */ + protected $hidden = [ + 'password', + 'remember_token', + ]; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'email_verified_at' => 'datetime', + ]; +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php new file mode 100644 index 0000000..ee8ca5b --- /dev/null +++ b/app/Providers/AppServiceProvider.php @@ -0,0 +1,28 @@ + + */ + protected $policies = [ + // 'App\Models\Model' => 'App\Policies\ModelPolicy', + ]; + + /** + * Register any authentication / authorization services. + * + * @return void + */ + public function boot() + { + $this->registerPolicies(); + + // + } +} diff --git a/app/Providers/BroadcastServiceProvider.php b/app/Providers/BroadcastServiceProvider.php new file mode 100644 index 0000000..395c518 --- /dev/null +++ b/app/Providers/BroadcastServiceProvider.php @@ -0,0 +1,21 @@ +> + */ + protected $listen = [ + Registered::class => [ + SendEmailVerificationNotification::class, + ], + + RouteMatched::class => [ + \App\Listeners\RouteMatched::class + ], + + CommandFinished::class => [ + Command::class + ] + ]; + + /** + * Register any events for your application. + * + * @return void + */ + public function boot() + { + // + } + + /** + * Determine if events and listeners should be automatically discovered. + * + * @return bool + */ + public function shouldDiscoverEvents() + { + return false; + } +} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php new file mode 100644 index 0000000..ea87f2e --- /dev/null +++ b/app/Providers/RouteServiceProvider.php @@ -0,0 +1,52 @@ +configureRateLimiting(); + + $this->routes(function () { + Route::middleware('api') + ->prefix('api') + ->group(base_path('routes/api.php')); + + Route::middleware('web') + ->group(base_path('routes/web.php')); + }); + } + + /** + * Configure the rate limiters for the application. + * + * @return void + */ + protected function configureRateLimiting() + { + RateLimiter::for('api', function (Request $request) { + return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip()); + }); + } +} diff --git a/artisan b/artisan new file mode 100644 index 0000000..67a3329 --- /dev/null +++ b/artisan @@ -0,0 +1,53 @@ +#!/usr/bin/env php +make(Illuminate\Contracts\Console\Kernel::class); + +$status = $kernel->handle( + $input = new Symfony\Component\Console\Input\ArgvInput, + new Symfony\Component\Console\Output\ConsoleOutput +); + +/* +|-------------------------------------------------------------------------- +| Shutdown The Application +|-------------------------------------------------------------------------- +| +| Once Artisan has finished running, we will fire off the shutdown events +| so that any final work may be done by the application before we shut +| down the process. This is the last thing to happen to the request. +| +*/ + +$kernel->terminate($input, $status); + +exit($status); diff --git a/auto-imports.d.ts b/auto-imports.d.ts new file mode 100644 index 0000000..6e7f7f5 --- /dev/null +++ b/auto-imports.d.ts @@ -0,0 +1,278 @@ +// Generated by 'unplugin-auto-import' +export {} +declare global { + const EffectScope: typeof import('vue')['EffectScope'] + const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate'] + const asyncComputed: typeof import('@vueuse/core')['asyncComputed'] + const autoResetRef: typeof import('@vueuse/core')['autoResetRef'] + const computed: typeof import('vue')['computed'] + const computedAsync: typeof import('@vueuse/core')['computedAsync'] + const computedEager: typeof import('@vueuse/core')['computedEager'] + const computedInject: typeof import('@vueuse/core')['computedInject'] + const computedWithControl: typeof import('@vueuse/core')['computedWithControl'] + const controlledComputed: typeof import('@vueuse/core')['controlledComputed'] + const controlledRef: typeof import('@vueuse/core')['controlledRef'] + const createApp: typeof import('vue')['createApp'] + const createEventHook: typeof import('@vueuse/core')['createEventHook'] + const createGlobalState: typeof import('@vueuse/core')['createGlobalState'] + const createInjectionState: typeof import('@vueuse/core')['createInjectionState'] + const createPinia: typeof import('pinia')['createPinia'] + const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn'] + const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable'] + const createUnrefFn: typeof import('@vueuse/core')['createUnrefFn'] + const customRef: typeof import('vue')['customRef'] + const debouncedRef: typeof import('@vueuse/core')['debouncedRef'] + const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch'] + const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] + const defineComponent: typeof import('vue')['defineComponent'] + const defineStore: typeof import('pinia')['defineStore'] + const eagerComputed: typeof import('@vueuse/core')['eagerComputed'] + const effectScope: typeof import('vue')['effectScope'] + const extendRef: typeof import('@vueuse/core')['extendRef'] + const getActivePinia: typeof import('pinia')['getActivePinia'] + const getCurrentInstance: typeof import('vue')['getCurrentInstance'] + const getCurrentScope: typeof import('vue')['getCurrentScope'] + const h: typeof import('vue')['h'] + const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch'] + const inject: typeof import('vue')['inject'] + const isDefined: typeof import('@vueuse/core')['isDefined'] + const isProxy: typeof import('vue')['isProxy'] + const isReactive: typeof import('vue')['isReactive'] + const isReadonly: typeof import('vue')['isReadonly'] + const isRef: typeof import('vue')['isRef'] + const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable'] + const mapActions: typeof import('pinia')['mapActions'] + const mapGetters: typeof import('pinia')['mapGetters'] + const mapState: typeof import('pinia')['mapState'] + const mapStores: typeof import('pinia')['mapStores'] + const mapWritableState: typeof import('pinia')['mapWritableState'] + const markRaw: typeof import('vue')['markRaw'] + const nextTick: typeof import('vue')['nextTick'] + const onActivated: typeof import('vue')['onActivated'] + const onBeforeMount: typeof import('vue')['onBeforeMount'] + const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave'] + const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate'] + const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] + const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] + const onClickOutside: typeof import('@vueuse/core')['onClickOutside'] + const onDeactivated: typeof import('vue')['onDeactivated'] + const onErrorCaptured: typeof import('vue')['onErrorCaptured'] + const onKeyStroke: typeof import('@vueuse/core')['onKeyStroke'] + const onLongPress: typeof import('@vueuse/core')['onLongPress'] + const onMounted: typeof import('vue')['onMounted'] + const onRenderTracked: typeof import('vue')['onRenderTracked'] + const onRenderTriggered: typeof import('vue')['onRenderTriggered'] + const onScopeDispose: typeof import('vue')['onScopeDispose'] + const onServerPrefetch: typeof import('vue')['onServerPrefetch'] + const onStartTyping: typeof import('@vueuse/core')['onStartTyping'] + const onUnmounted: typeof import('vue')['onUnmounted'] + const onUpdated: typeof import('vue')['onUpdated'] + const pausableWatch: typeof import('@vueuse/core')['pausableWatch'] + const provide: typeof import('vue')['provide'] + const reactify: typeof import('@vueuse/core')['reactify'] + const reactifyObject: typeof import('@vueuse/core')['reactifyObject'] + const reactive: typeof import('vue')['reactive'] + const reactiveComputed: typeof import('@vueuse/core')['reactiveComputed'] + const reactiveOmit: typeof import('@vueuse/core')['reactiveOmit'] + const reactivePick: typeof import('@vueuse/core')['reactivePick'] + const readonly: typeof import('vue')['readonly'] + const ref: typeof import('vue')['ref'] + const refAutoReset: typeof import('@vueuse/core')['refAutoReset'] + const refDebounced: typeof import('@vueuse/core')['refDebounced'] + const refDefault: typeof import('@vueuse/core')['refDefault'] + const refThrottled: typeof import('@vueuse/core')['refThrottled'] + const refWithControl: typeof import('@vueuse/core')['refWithControl'] + const resolveComponent: typeof import('vue')['resolveComponent'] + const resolveDirective: typeof import('vue')['resolveDirective'] + const resolveRef: typeof import('@vueuse/core')['resolveRef'] + const resolveUnref: typeof import('@vueuse/core')['resolveUnref'] + const setActivePinia: typeof import('pinia')['setActivePinia'] + const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix'] + const shallowReactive: typeof import('vue')['shallowReactive'] + const shallowReadonly: typeof import('vue')['shallowReadonly'] + const shallowRef: typeof import('vue')['shallowRef'] + const storeToRefs: typeof import('pinia')['storeToRefs'] + const syncRef: typeof import('@vueuse/core')['syncRef'] + const syncRefs: typeof import('@vueuse/core')['syncRefs'] + const templateRef: typeof import('@vueuse/core')['templateRef'] + const throttledRef: typeof import('@vueuse/core')['throttledRef'] + const throttledWatch: typeof import('@vueuse/core')['throttledWatch'] + const toRaw: typeof import('vue')['toRaw'] + const toReactive: typeof import('@vueuse/core')['toReactive'] + const toRef: typeof import('vue')['toRef'] + const toRefs: typeof import('vue')['toRefs'] + const triggerRef: typeof import('vue')['triggerRef'] + const tryOnBeforeMount: typeof import('@vueuse/core')['tryOnBeforeMount'] + const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount'] + const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted'] + const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose'] + const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted'] + const unref: typeof import('vue')['unref'] + const unrefElement: typeof import('@vueuse/core')['unrefElement'] + const until: typeof import('@vueuse/core')['until'] + const useActiveElement: typeof import('@vueuse/core')['useActiveElement'] + const useArrayEvery: typeof import('@vueuse/core')['useArrayEvery'] + const useArrayFilter: typeof import('@vueuse/core')['useArrayFilter'] + const useArrayFind: typeof import('@vueuse/core')['useArrayFind'] + const useArrayFindIndex: typeof import('@vueuse/core')['useArrayFindIndex'] + const useArrayJoin: typeof import('@vueuse/core')['useArrayJoin'] + const useArrayMap: typeof import('@vueuse/core')['useArrayMap'] + const useArrayReduce: typeof import('@vueuse/core')['useArrayReduce'] + const useArraySome: typeof import('@vueuse/core')['useArraySome'] + const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue'] + const useAsyncState: typeof import('@vueuse/core')['useAsyncState'] + const useAttrs: typeof import('vue')['useAttrs'] + const useBase64: typeof import('@vueuse/core')['useBase64'] + const useBattery: typeof import('@vueuse/core')['useBattery'] + const useBluetooth: typeof import('@vueuse/core')['useBluetooth'] + const useBreakpoints: typeof import('@vueuse/core')['useBreakpoints'] + const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel'] + const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation'] + const useCached: typeof import('@vueuse/core')['useCached'] + const useClipboard: typeof import('@vueuse/core')['useClipboard'] + const useCloned: typeof import('@vueuse/core')['useCloned'] + const useColorMode: typeof import('@vueuse/core')['useColorMode'] + const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog'] + const useCounter: typeof import('@vueuse/core')['useCounter'] + const useCssModule: typeof import('vue')['useCssModule'] + const useCssVar: typeof import('@vueuse/core')['useCssVar'] + const useCssVars: typeof import('vue')['useCssVars'] + const useCurrentElement: typeof import('@vueuse/core')['useCurrentElement'] + const useCycleList: typeof import('@vueuse/core')['useCycleList'] + const useDark: typeof import('@vueuse/core')['useDark'] + const useDateFormat: typeof import('@vueuse/core')['useDateFormat'] + const useDebounce: typeof import('@vueuse/core')['useDebounce'] + const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn'] + const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory'] + const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion'] + const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation'] + const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio'] + const useDevicesList: typeof import('@vueuse/core')['useDevicesList'] + const useDisplayMedia: typeof import('@vueuse/core')['useDisplayMedia'] + const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility'] + const useDraggable: typeof import('@vueuse/core')['useDraggable'] + const useDropZone: typeof import('@vueuse/core')['useDropZone'] + const useElementBounding: typeof import('@vueuse/core')['useElementBounding'] + const useElementByPoint: typeof import('@vueuse/core')['useElementByPoint'] + const useElementHover: typeof import('@vueuse/core')['useElementHover'] + const useElementSize: typeof import('@vueuse/core')['useElementSize'] + const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility'] + const useEventBus: typeof import('@vueuse/core')['useEventBus'] + const useEventListener: typeof import('@vueuse/core')['useEventListener'] + const useEventSource: typeof import('@vueuse/core')['useEventSource'] + const useEyeDropper: typeof import('@vueuse/core')['useEyeDropper'] + const useFavicon: typeof import('@vueuse/core')['useFavicon'] + const useFetch: typeof import('@vueuse/core')['useFetch'] + const useFileDialog: typeof import('@vueuse/core')['useFileDialog'] + const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess'] + const useFocus: typeof import('@vueuse/core')['useFocus'] + const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin'] + const useFps: typeof import('@vueuse/core')['useFps'] + const useFullscreen: typeof import('@vueuse/core')['useFullscreen'] + const useGamepad: typeof import('@vueuse/core')['useGamepad'] + const useGeolocation: typeof import('@vueuse/core')['useGeolocation'] + const useIdle: typeof import('@vueuse/core')['useIdle'] + const useImage: typeof import('@vueuse/core')['useImage'] + const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll'] + const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver'] + const useInterval: typeof import('@vueuse/core')['useInterval'] + const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn'] + const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier'] + const useLastChanged: typeof import('@vueuse/core')['useLastChanged'] + const useLink: typeof import('vue-router')['useLink'] + const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage'] + const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys'] + const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory'] + const useMediaControls: typeof import('@vueuse/core')['useMediaControls'] + const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery'] + const useMemoize: typeof import('@vueuse/core')['useMemoize'] + const useMemory: typeof import('@vueuse/core')['useMemory'] + const useMounted: typeof import('@vueuse/core')['useMounted'] + const useMouse: typeof import('@vueuse/core')['useMouse'] + const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement'] + const useMousePressed: typeof import('@vueuse/core')['useMousePressed'] + const useMutationObserver: typeof import('@vueuse/core')['useMutationObserver'] + const useNavigatorLanguage: typeof import('@vueuse/core')['useNavigatorLanguage'] + const useNetwork: typeof import('@vueuse/core')['useNetwork'] + const useNow: typeof import('@vueuse/core')['useNow'] + const useObjectUrl: typeof import('@vueuse/core')['useObjectUrl'] + const useOffsetPagination: typeof import('@vueuse/core')['useOffsetPagination'] + const useOnline: typeof import('@vueuse/core')['useOnline'] + const usePageLeave: typeof import('@vueuse/core')['usePageLeave'] + const useParallax: typeof import('@vueuse/core')['useParallax'] + const usePermission: typeof import('@vueuse/core')['usePermission'] + const usePointer: typeof import('@vueuse/core')['usePointer'] + const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe'] + const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme'] + const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast'] + const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark'] + const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages'] + const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion'] + const useRafFn: typeof import('@vueuse/core')['useRafFn'] + const useRefHistory: typeof import('@vueuse/core')['useRefHistory'] + const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver'] + const useRoute: typeof import('vue-router')['useRoute'] + const useRouter: typeof import('vue-router')['useRouter'] + const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation'] + const useScreenSafeArea: typeof import('@vueuse/core')['useScreenSafeArea'] + const useScriptTag: typeof import('@vueuse/core')['useScriptTag'] + const useScroll: typeof import('@vueuse/core')['useScroll'] + const useScrollLock: typeof import('@vueuse/core')['useScrollLock'] + const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage'] + const useShare: typeof import('@vueuse/core')['useShare'] + const useSlots: typeof import('vue')['useSlots'] + const useSorted: typeof import('@vueuse/core')['useSorted'] + const useSpeechRecognition: typeof import('@vueuse/core')['useSpeechRecognition'] + const useSpeechSynthesis: typeof import('@vueuse/core')['useSpeechSynthesis'] + const useStepper: typeof import('@vueuse/core')['useStepper'] + const useStorage: typeof import('@vueuse/core')['useStorage'] + const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync'] + const useStyleTag: typeof import('@vueuse/core')['useStyleTag'] + const useSupported: typeof import('@vueuse/core')['useSupported'] + const useSwipe: typeof import('@vueuse/core')['useSwipe'] + const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList'] + const useTextDirection: typeof import('@vueuse/core')['useTextDirection'] + const useTextSelection: typeof import('@vueuse/core')['useTextSelection'] + const useTextareaAutosize: typeof import('@vueuse/core')['useTextareaAutosize'] + const useThrottle: typeof import('@vueuse/core')['useThrottle'] + const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn'] + const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory'] + const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo'] + const useTimeout: typeof import('@vueuse/core')['useTimeout'] + const useTimeoutFn: typeof import('@vueuse/core')['useTimeoutFn'] + const useTimeoutPoll: typeof import('@vueuse/core')['useTimeoutPoll'] + const useTimestamp: typeof import('@vueuse/core')['useTimestamp'] + const useTitle: typeof import('@vueuse/core')['useTitle'] + const useToNumber: typeof import('@vueuse/core')['useToNumber'] + const useToString: typeof import('@vueuse/core')['useToString'] + const useToggle: typeof import('@vueuse/core')['useToggle'] + const useTransition: typeof import('@vueuse/core')['useTransition'] + const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams'] + const useUserMedia: typeof import('@vueuse/core')['useUserMedia'] + const useVModel: typeof import('@vueuse/core')['useVModel'] + const useVModels: typeof import('@vueuse/core')['useVModels'] + const useVibrate: typeof import('@vueuse/core')['useVibrate'] + const useVirtualList: typeof import('@vueuse/core')['useVirtualList'] + const useWakeLock: typeof import('@vueuse/core')['useWakeLock'] + const useWebNotification: typeof import('@vueuse/core')['useWebNotification'] + const useWebSocket: typeof import('@vueuse/core')['useWebSocket'] + const useWebWorker: typeof import('@vueuse/core')['useWebWorker'] + const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn'] + const useWindowFocus: typeof import('@vueuse/core')['useWindowFocus'] + const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll'] + const useWindowSize: typeof import('@vueuse/core')['useWindowSize'] + const watch: typeof import('vue')['watch'] + const watchArray: typeof import('@vueuse/core')['watchArray'] + const watchAtMost: typeof import('@vueuse/core')['watchAtMost'] + const watchDebounced: typeof import('@vueuse/core')['watchDebounced'] + const watchEffect: typeof import('vue')['watchEffect'] + const watchIgnorable: typeof import('@vueuse/core')['watchIgnorable'] + const watchOnce: typeof import('@vueuse/core')['watchOnce'] + const watchPausable: typeof import('@vueuse/core')['watchPausable'] + const watchPostEffect: typeof import('vue')['watchPostEffect'] + const watchSyncEffect: typeof import('vue')['watchSyncEffect'] + const watchThrottled: typeof import('@vueuse/core')['watchThrottled'] + const watchTriggerable: typeof import('@vueuse/core')['watchTriggerable'] + const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter'] + const whenever: typeof import('@vueuse/core')['whenever'] +} diff --git a/bootstrap/app.php b/bootstrap/app.php new file mode 100644 index 0000000..037e17d --- /dev/null +++ b/bootstrap/app.php @@ -0,0 +1,55 @@ +singleton( + Illuminate\Contracts\Http\Kernel::class, + App\Http\Kernel::class +); + +$app->singleton( + Illuminate\Contracts\Console\Kernel::class, + App\Console\Kernel::class +); + +$app->singleton( + Illuminate\Contracts\Debug\ExceptionHandler::class, + App\Exceptions\Handler::class +); + +/* +|-------------------------------------------------------------------------- +| Return The Application +|-------------------------------------------------------------------------- +| +| This script returns the application instance. The instance is given to +| the calling script so we can separate the building of the instances +| from the actual running of the application and sending responses. +| +*/ + +return $app; diff --git a/bootstrap/cache/packages.php b/bootstrap/cache/packages.php new file mode 100755 index 0000000..b6afc0a --- /dev/null +++ b/bootstrap/cache/packages.php @@ -0,0 +1,49 @@ + + array ( + 'providers' => + array ( + 0 => 'Laravel\\Tinker\\TinkerServiceProvider', + ), + ), + 'nesbot/carbon' => + array ( + 'providers' => + array ( + 0 => 'Carbon\\Laravel\\ServiceProvider', + ), + ), + 'nunomaduro/collision' => + array ( + 'providers' => + array ( + 0 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider', + ), + ), + 'nunomaduro/termwind' => + array ( + 'providers' => + array ( + 0 => 'Termwind\\Laravel\\TermwindServiceProvider', + ), + ), + 'pestphp/pest' => + array ( + 'providers' => + array ( + 0 => 'Pest\\Laravel\\PestServiceProvider', + ), + ), + 'tymon/jwt-auth' => + array ( + 'aliases' => + array ( + 'JWTAuth' => 'Tymon\\JWTAuth\\Facades\\JWTAuth', + 'JWTFactory' => 'Tymon\\JWTAuth\\Facades\\JWTFactory', + ), + 'providers' => + array ( + 0 => 'Tymon\\JWTAuth\\Providers\\LaravelServiceProvider', + ), + ), +); \ No newline at end of file diff --git a/bootstrap/cache/services.php b/bootstrap/cache/services.php new file mode 100755 index 0000000..ff94066 --- /dev/null +++ b/bootstrap/cache/services.php @@ -0,0 +1,237 @@ + + array ( + 0 => 'Illuminate\\Auth\\AuthServiceProvider', + 1 => 'Illuminate\\Broadcasting\\BroadcastServiceProvider', + 2 => 'Illuminate\\Bus\\BusServiceProvider', + 3 => 'Illuminate\\Cache\\CacheServiceProvider', + 4 => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 5 => 'Illuminate\\Cookie\\CookieServiceProvider', + 6 => 'Illuminate\\Database\\DatabaseServiceProvider', + 7 => 'Illuminate\\Encryption\\EncryptionServiceProvider', + 8 => 'Illuminate\\Filesystem\\FilesystemServiceProvider', + 9 => 'Illuminate\\Foundation\\Providers\\FoundationServiceProvider', + 10 => 'Illuminate\\Hashing\\HashServiceProvider', + 11 => 'Illuminate\\Mail\\MailServiceProvider', + 12 => 'Illuminate\\Notifications\\NotificationServiceProvider', + 13 => 'Illuminate\\Pagination\\PaginationServiceProvider', + 14 => 'Illuminate\\Pipeline\\PipelineServiceProvider', + 15 => 'Illuminate\\Queue\\QueueServiceProvider', + 16 => 'Illuminate\\Redis\\RedisServiceProvider', + 17 => 'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider', + 18 => 'Illuminate\\Session\\SessionServiceProvider', + 19 => 'Illuminate\\Translation\\TranslationServiceProvider', + 20 => 'Illuminate\\Validation\\ValidationServiceProvider', + 21 => 'Illuminate\\View\\ViewServiceProvider', + 22 => 'Laravel\\Tinker\\TinkerServiceProvider', + 23 => 'Carbon\\Laravel\\ServiceProvider', + 24 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider', + 25 => 'Termwind\\Laravel\\TermwindServiceProvider', + 26 => 'Pest\\Laravel\\PestServiceProvider', + 27 => 'Tymon\\JWTAuth\\Providers\\LaravelServiceProvider', + 28 => 'Catch\\Providers\\CatchAdminServiceProvider', + 29 => 'App\\Providers\\AppServiceProvider', + 30 => 'App\\Providers\\AuthServiceProvider', + 31 => 'App\\Providers\\EventServiceProvider', + 32 => 'App\\Providers\\RouteServiceProvider', + ), + 'eager' => + array ( + 0 => 'Illuminate\\Auth\\AuthServiceProvider', + 1 => 'Illuminate\\Cookie\\CookieServiceProvider', + 2 => 'Illuminate\\Database\\DatabaseServiceProvider', + 3 => 'Illuminate\\Encryption\\EncryptionServiceProvider', + 4 => 'Illuminate\\Filesystem\\FilesystemServiceProvider', + 5 => 'Illuminate\\Foundation\\Providers\\FoundationServiceProvider', + 6 => 'Illuminate\\Notifications\\NotificationServiceProvider', + 7 => 'Illuminate\\Pagination\\PaginationServiceProvider', + 8 => 'Illuminate\\Session\\SessionServiceProvider', + 9 => 'Illuminate\\View\\ViewServiceProvider', + 10 => 'Carbon\\Laravel\\ServiceProvider', + 11 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider', + 12 => 'Termwind\\Laravel\\TermwindServiceProvider', + 13 => 'Pest\\Laravel\\PestServiceProvider', + 14 => 'Tymon\\JWTAuth\\Providers\\LaravelServiceProvider', + 15 => 'Catch\\Providers\\CatchAdminServiceProvider', + 16 => 'App\\Providers\\AppServiceProvider', + 17 => 'App\\Providers\\AuthServiceProvider', + 18 => 'App\\Providers\\EventServiceProvider', + 19 => 'App\\Providers\\RouteServiceProvider', + ), + 'deferred' => + array ( + 'Illuminate\\Broadcasting\\BroadcastManager' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider', + 'Illuminate\\Contracts\\Broadcasting\\Factory' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider', + 'Illuminate\\Contracts\\Broadcasting\\Broadcaster' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider', + 'Illuminate\\Bus\\Dispatcher' => 'Illuminate\\Bus\\BusServiceProvider', + 'Illuminate\\Contracts\\Bus\\Dispatcher' => 'Illuminate\\Bus\\BusServiceProvider', + 'Illuminate\\Contracts\\Bus\\QueueingDispatcher' => 'Illuminate\\Bus\\BusServiceProvider', + 'Illuminate\\Bus\\BatchRepository' => 'Illuminate\\Bus\\BusServiceProvider', + 'Illuminate\\Bus\\DatabaseBatchRepository' => 'Illuminate\\Bus\\BusServiceProvider', + 'cache' => 'Illuminate\\Cache\\CacheServiceProvider', + 'cache.store' => 'Illuminate\\Cache\\CacheServiceProvider', + 'cache.psr6' => 'Illuminate\\Cache\\CacheServiceProvider', + 'memcached.connector' => 'Illuminate\\Cache\\CacheServiceProvider', + 'Illuminate\\Cache\\RateLimiter' => 'Illuminate\\Cache\\CacheServiceProvider', + 'Illuminate\\Foundation\\Console\\AboutCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Cache\\Console\\ClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Cache\\Console\\ForgetCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\ClearCompiledCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Auth\\Console\\ClearResetsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\ConfigCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\ConfigClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Database\\Console\\DbCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Database\\Console\\MonitorCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Database\\Console\\PruneCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Database\\Console\\ShowCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Database\\Console\\TableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Database\\Console\\WipeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\DownCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\EnvironmentCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\EnvironmentDecryptCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\EnvironmentEncryptCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\EventCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\EventClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\EventListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\KeyGenerateCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\OptimizeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\OptimizeClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\PackageDiscoverCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Queue\\Console\\ClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Queue\\Console\\ListFailedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Queue\\Console\\FlushFailedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Queue\\Console\\ForgetFailedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Queue\\Console\\ListenCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Queue\\Console\\MonitorCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Queue\\Console\\PruneBatchesCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Queue\\Console\\PruneFailedJobsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Queue\\Console\\RestartCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Queue\\Console\\RetryCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Queue\\Console\\RetryBatchCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Queue\\Console\\WorkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\RouteCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\RouteClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\RouteListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Database\\Console\\DumpCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Database\\Console\\Seeds\\SeedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Console\\Scheduling\\ScheduleFinishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Console\\Scheduling\\ScheduleListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Console\\Scheduling\\ScheduleRunCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Console\\Scheduling\\ScheduleClearCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Console\\Scheduling\\ScheduleTestCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Console\\Scheduling\\ScheduleWorkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\ShowModelCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\StorageLinkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\UpCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\ViewCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\ViewClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Cache\\Console\\CacheTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\CastMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\ChannelMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\ComponentMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\ConsoleMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Routing\\Console\\ControllerMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\DocsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\EventGenerateCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\EventMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\ExceptionMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Database\\Console\\Factories\\FactoryMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\JobMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\ListenerMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\MailMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Routing\\Console\\MiddlewareMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\ModelMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\NotificationMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Notifications\\Console\\NotificationTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\ObserverMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\PolicyMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\ProviderMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Queue\\Console\\FailedTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Queue\\Console\\TableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Queue\\Console\\BatchesTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\RequestMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\ResourceMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\RuleMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\ScopeMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Database\\Console\\Seeds\\SeederMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Session\\Console\\SessionTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\ServeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\StubPublishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\TestMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Foundation\\Console\\VendorPublishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'migrator' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'migration.repository' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'migration.creator' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Database\\Console\\Migrations\\MigrateCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Database\\Console\\Migrations\\FreshCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Database\\Console\\Migrations\\InstallCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Database\\Console\\Migrations\\RefreshCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Database\\Console\\Migrations\\ResetCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Database\\Console\\Migrations\\RollbackCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Database\\Console\\Migrations\\StatusCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'Illuminate\\Database\\Console\\Migrations\\MigrateMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'composer' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', + 'hash' => 'Illuminate\\Hashing\\HashServiceProvider', + 'hash.driver' => 'Illuminate\\Hashing\\HashServiceProvider', + 'mail.manager' => 'Illuminate\\Mail\\MailServiceProvider', + 'mailer' => 'Illuminate\\Mail\\MailServiceProvider', + 'Illuminate\\Mail\\Markdown' => 'Illuminate\\Mail\\MailServiceProvider', + 'Illuminate\\Contracts\\Pipeline\\Hub' => 'Illuminate\\Pipeline\\PipelineServiceProvider', + 'queue' => 'Illuminate\\Queue\\QueueServiceProvider', + 'queue.connection' => 'Illuminate\\Queue\\QueueServiceProvider', + 'queue.failer' => 'Illuminate\\Queue\\QueueServiceProvider', + 'queue.listener' => 'Illuminate\\Queue\\QueueServiceProvider', + 'queue.worker' => 'Illuminate\\Queue\\QueueServiceProvider', + 'redis' => 'Illuminate\\Redis\\RedisServiceProvider', + 'redis.connection' => 'Illuminate\\Redis\\RedisServiceProvider', + 'auth.password' => 'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider', + 'auth.password.broker' => 'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider', + 'translator' => 'Illuminate\\Translation\\TranslationServiceProvider', + 'translation.loader' => 'Illuminate\\Translation\\TranslationServiceProvider', + 'validator' => 'Illuminate\\Validation\\ValidationServiceProvider', + 'validation.presence' => 'Illuminate\\Validation\\ValidationServiceProvider', + 'command.tinker' => 'Laravel\\Tinker\\TinkerServiceProvider', + ), + 'when' => + array ( + 'Illuminate\\Broadcasting\\BroadcastServiceProvider' => + array ( + ), + 'Illuminate\\Bus\\BusServiceProvider' => + array ( + ), + 'Illuminate\\Cache\\CacheServiceProvider' => + array ( + ), + 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider' => + array ( + ), + 'Illuminate\\Hashing\\HashServiceProvider' => + array ( + ), + 'Illuminate\\Mail\\MailServiceProvider' => + array ( + ), + 'Illuminate\\Pipeline\\PipelineServiceProvider' => + array ( + ), + 'Illuminate\\Queue\\QueueServiceProvider' => + array ( + ), + 'Illuminate\\Redis\\RedisServiceProvider' => + array ( + ), + 'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider' => + array ( + ), + 'Illuminate\\Translation\\TranslationServiceProvider' => + array ( + ), + 'Illuminate\\Validation\\ValidationServiceProvider' => + array ( + ), + 'Laravel\\Tinker\\TinkerServiceProvider' => + array ( + ), + ), +); \ No newline at end of file diff --git a/catch/config/catch.php b/catch/config/catch.php new file mode 100644 index 0000000..8ea0465 --- /dev/null +++ b/catch/config/catch.php @@ -0,0 +1,145 @@ + [ + + ], + + /* + |-------------------------------------------------------------------------- + | catch-admin catch_auth_middleware_alias + |-------------------------------------------------------------------------- + | + | where you can set default middlewares + | + */ + 'catch_auth_middleware_alias' => [ + + ], + + /* + |-------------------------------------------------------------------------- + | catch-admin super admin id + |-------------------------------------------------------------------------- + | + | where you can set super admin id + | + */ + 'super_admin' => 1, + + /* + |-------------------------------------------------------------------------- + | catch-admin module setting + |-------------------------------------------------------------------------- + | + | the root where module generate + | the namespace is module root namespace + | the default dirs is module generate default dirs + */ + 'module' => [ + 'root' => 'modules', + + 'namespace' => 'Modules', + + 'default' => ['develop', 'user', 'permission'], + + 'default_dirs' => [ + 'Http'.DIRECTORY_SEPARATOR, + + 'Http'.DIRECTORY_SEPARATOR.'Requests'.DIRECTORY_SEPARATOR, + + 'Http'.DIRECTORY_SEPARATOR.'Controllers'.DIRECTORY_SEPARATOR, + + 'Models'.DIRECTORY_SEPARATOR, + + 'views'.DIRECTORY_SEPARATOR, + ], + + // storage module information + // which driver should be used? + 'driver' => [ + // currently, catchadmin support file and database + // the default is driver + 'default' => 'file', + + // use database driver + 'table_name' => 'admin_modules' + ] + ], + + /* + |-------------------------------------------------------------------------- + | catch-admin response + |-------------------------------------------------------------------------- + */ + 'response' => [ + // it's a controller middleware, it's set in CatchController + // if you not need json response, don't extend CatchController + 'always_json' => \Catch\Middleware\JsonResponseMiddleware::class, + + // response listener + // it listens [RequestHandled] event, if you don't need this + // you can change this config + 'request_handled_listener' => \Catch\Listeners\RequestHandledListener::class + ], + + /* + |-------------------------------------------------------------------------- + | catch-admin auth setting + |-------------------------------------------------------------------------- + */ + 'auth' => [ + 'guards' => [ + 'admin' => [ + 'driver' => 'jwt', + 'provider' => 'admin_users', + ], + ], + + 'providers' => [ + 'admin_users' => [ + 'driver' => 'eloquent', + 'model' => \Modules\User\Models\Users::class + ] + ] + ], + + /* + |-------------------------------------------------------------------------- + | database sql log + |-------------------------------------------------------------------------- + */ + 'listen_db_log' => true, + + /* + |-------------------------------------------------------------------------- + | route config + |-------------------------------------------------------------------------- + */ + 'route' => [ + 'prefix' => 'api', + + 'middlewares' => [ + \Catch\Middleware\AuthMiddleware::class, + \Catch\Middleware\JsonResponseMiddleware::class + ] + ], +]; diff --git a/catch/database/migrations/2022_11_14_034127_module.php b/catch/database/migrations/2022_11_14_034127_module.php new file mode 100644 index 0000000..6601bb1 --- /dev/null +++ b/catch/database/migrations/2022_11_14_034127_module.php @@ -0,0 +1,47 @@ +increments('id'); + + $table->string('name')->comment('模块名称'); + + $table->string('path', 20)->comment('模块目录'); + + $table->string('description')->comment('模块描述'); + + $table->string('keywords')->comment('模块关键字'); + + $table->string('version', 20)->comment('模块版本号')->default('1.0.0'); + + $table->boolean('status')->comment('模块状态')->default(1); + + $table->unsignedInteger('created_at')->comment('创建时间')->default(0); + + $table->unsignedInteger('updated_at')->comment('更新时间')->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + Schema::dropIfExists(config('catch.module.table_name')); + } +}; diff --git a/catch/src/Base/CatchController.php b/catch/src/Base/CatchController.php new file mode 100644 index 0000000..101c176 --- /dev/null +++ b/catch/src/Base/CatchController.php @@ -0,0 +1,34 @@ +user(); + } +} diff --git a/catch/src/Base/CatchModel.php b/catch/src/Base/CatchModel.php new file mode 100644 index 0000000..325e05f --- /dev/null +++ b/catch/src/Base/CatchModel.php @@ -0,0 +1,89 @@ + 'datetime:Y-m-d H:i:s', + + 'updated_at' => 'datetime:Y-m-d H:i:s', + + 'deleted_at' => 'datetime:Y-m-d H:i:s' + ]; + + /** + * @var array + */ + protected array $fieldsInList = ['*']; + + /** + * @var bool + */ + protected bool $isPaginate = true; + + /** + * @var array $searchable + */ + public array $searchable = []; + + + /** + * soft delete + * + * @time 2021年08月09日 + * @return void + */ + public static function bootSoftDeletes(): void + { + static::addGlobalScope(new SoftDelete()); + } +} diff --git a/catch/src/CatchAdmin.php b/catch/src/CatchAdmin.php new file mode 100644 index 0000000..8d95ff3 --- /dev/null +++ b/catch/src/CatchAdmin.php @@ -0,0 +1,363 @@ +name + ) { + $this->signature = $this->name.' {module}'; + } + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @return void + */ + protected function initialize(InputInterface $input, OutputInterface $output): void + { + if ($input->hasArgument('module') + && ! Module::all()->pluck('name')->merge(Collection::make(config('catch.module.default')))->contains(lcfirst($input->getArgument('module'))) + ) { + $this->error(sprintf('Module [%s] Not Found', $input->getArgument('module'))); + exit; + } + } + + + /** + * + * @param string $question + * @param null $default + * @param bool $isChoice + * @return string|int|null + */ + public function askFor(string $question, $default = null, bool $isChoice = false): string|null|int + { + $_default = $default ? "[$default]" : ''; + + $choice = $isChoice ? 'YesORNo' : ''; + + $answer = ask( + << +
CatchAdmin
+ + $question + $_default + $choice + : + + +HTML + ); + + + $this->newLine(); + + if ($default && ! $answer) { + return $default; + } + + return $answer; + } + + + /** + * info + * + * @param $string + * @param null $verbosity + * @return void + */ + public function info($string, $verbosity = null): void + { + render( + << +
CatchAdmin
+ + $string + + +HTML + ); + } + + /** + * error + * + * @param $string + * @param null $verbosity + * @return void + */ + public function error($string, $verbosity = null): void + { + render( + << +
CatchAdmin
+ + $string + + +HTML + ); + } +} diff --git a/catch/src/Commands/Create/Controller.php b/catch/src/Commands/Create/Controller.php new file mode 100644 index 0000000..a4c5004 --- /dev/null +++ b/catch/src/Commands/Create/Controller.php @@ -0,0 +1,100 @@ +argument('module')); + + $file = $controllerPath.$this->getControllerFile(); + + if (File::exists($file)) { + $answer = $this->ask($file.' already exists, Did you want replace it?', 'Y'); + + if (! Str::of($answer)->lower()->exactly('y')) { + exit; + } + } + + File::put($file, Str::of($this->getStubContent())->replace([ + '{namespace}', '{controller}' + ], [trim(CatchAdmin::getModuleControllerNamespace($this->argument('module')), '\\'), $this->getControllerName()])->toString()); + + if (File::exists($file)) { + $this->info($file.' has been created'); + } else { + $this->error($file.' create failed'); + } + } + + /** + * + * + * @return string + */ + protected function getControllerFile(): string + { + return $this->getControllerName().'.php'; + } + + /** + * + * + * @return string + */ + protected function getControllerName(): string + { + return Str::of($this->argument('name')) + ->whenContains('Controller', function ($str) { + return $str; + }, function ($str) { + return $str->append('Controller'); + })->ucfirst()->toString(); + } + + /** + * get stub content + * + * @return string + */ + protected function getStubContent(): string + { + return File::get(dirname(__DIR__).DIRECTORY_SEPARATOR.'stubs'.DIRECTORY_SEPARATOR.'controller.stub'); + } +} diff --git a/catch/src/Commands/Create/Event.php b/catch/src/Commands/Create/Event.php new file mode 100644 index 0000000..95552e7 --- /dev/null +++ b/catch/src/Commands/Create/Event.php @@ -0,0 +1,100 @@ +argument('module')); + + $file = $eventPath.$this->getEventFile(); + + if (File::exists($file)) { + $answer = $this->ask($file.' already exists, Did you want replace it?', 'Y'); + + if (! Str::of($answer)->lower()->exactly('y')) { + exit; + } + } + + File::put($file, Str::of($this->getStubContent())->replace([ + '{namespace}', '{event}' + ], [trim(CatchAdmin::getModuleEventsNamespace($this->argument('module')), '\\'), $this->getEventName()])->toString()); + + if (File::exists($file)) { + $this->info($file.' has been created'); + } else { + $this->error($file.' create failed'); + } + } + + /** + * + * + * @return string + */ + protected function getEventFile(): string + { + return $this->getEventName().'.php'; + } + + /** + * + * + * @return string + */ + protected function getEventName(): string + { + return Str::of($this->argument('name')) + ->whenContains('Event', function ($str) { + return $str; + }, function ($str) { + return $str->append('Event'); + })->ucfirst()->toString(); + } + + /** + * get stub content + * + * @return string + */ + protected function getStubContent(): string + { + return File::get(dirname(__DIR__).DIRECTORY_SEPARATOR.'stubs'.DIRECTORY_SEPARATOR.'event.stub'); + } +} diff --git a/catch/src/Commands/Create/Listener.php b/catch/src/Commands/Create/Listener.php new file mode 100644 index 0000000..165852a --- /dev/null +++ b/catch/src/Commands/Create/Listener.php @@ -0,0 +1,103 @@ +argument('module')); + + $file = $eventPath.$this->getListenerFile(); + + if (File::exists($file)) { + $answer = $this->ask($file.' already exists, Did you want replace it?', 'Y'); + + if (! Str::of($answer)->lower()->exactly('y')) { + exit; + } + } + + File::put($file, Str::of($this->getStubContent())->replace([ + '{namespace}', '{listener}' + ], [ + trim(CatchAdmin::getModuleListenersNamespace($this->argument('module')), '\\'), + + $this->getListenerName()])->toString()); + + if (File::exists($file)) { + $this->info($file.' has been created'); + } else { + $this->error($file.' create failed'); + } + } + + /** + * + * + * @return string + */ + protected function getListenerFile(): string + { + return $this->getListenerName().'.php'; + } + + /** + * + * + * @return string + */ + protected function getListenerName(): string + { + return Str::of($this->argument('name')) + ->whenContains('Listener', function ($str) { + return $str; + }, function ($str) { + return $str->append('Listener'); + })->ucfirst()->toString(); + } + + /** + * get stub content + * + * @return string + */ + protected function getStubContent(): string + { + return File::get(dirname(__DIR__).DIRECTORY_SEPARATOR.'stubs'.DIRECTORY_SEPARATOR.'listener.stub'); + } +} diff --git a/catch/src/Commands/Create/Model.php b/catch/src/Commands/Create/Model.php new file mode 100644 index 0000000..0dce160 --- /dev/null +++ b/catch/src/Commands/Create/Model.php @@ -0,0 +1,174 @@ +getTableName())) { + $this->error('Schema ['.$this->getTableName().'] not found'); + exit; + } + + $modelPath = CatchAdmin::getModuleModelPath($this->argument('module')); + + $file = $modelPath.$this->getModelFile(); + + if (File::exists($file)) { + $answer = $this->ask($file.' already exists, Did you want replace it?', 'Y'); + + if (! Str::of($answer)->lower()->exactly('y')) { + exit; + } + } + + File::put($file, $this->getModelContent()); + + if (File::exists($file)) { + $this->info($file.' has been created'); + } else { + $this->error($file.' create failed'); + } + } + + /** + * + * + * @return string + */ + protected function getModelFile(): string + { + return $this->getModelName().'.php'; + } + + /** + * + * + * @return string + */ + protected function getModelName(): string + { + return Str::of($this->argument('model'))->ucfirst()->toString(); + } + + /** + * get stub content + * + * @return string + */ + protected function getStubContent(): string + { + return File::get(dirname(__DIR__).DIRECTORY_SEPARATOR.'stubs'.DIRECTORY_SEPARATOR.'model.stub'); + } + + + /** + * get model content + * + * @return string + */ + protected function getModelContent(): string + { + return Str::of($this->getStubContent()) + + ->replace( + [ + '{namespace}', '{model}', '{table}', '{fillable}' + ], + [ + + $this->getModelNamespace(), $this->getModelName(), + + $this->getTableName(), $this->getFillable() + ] + )->toString(); + } + + /** + * get namespace + * + * @return string + */ + protected function getModelNamespace(): string + { + return trim(CatchAdmin::getModuleModelNamespace($this->argument('module')), '\\'); + } + + /** + * get table name + * + * @return string + */ + protected function getTableName(): string + { + return $this->option('t') ? $this->option('t') : + Str::of($this->argument('model')) + ->snake()->lcfirst()->toString(); + } + + /** + * + * + * @return string + */ + protected function getFillable(): string + { + $fillable = Str::of(''); + + + foreach (getTableColumns($this->getTableName()) as $column) { + $fillable = $fillable->append("'{$column}', "); + } + + + return $fillable->trim(',')->toString(); + } +} diff --git a/catch/src/Commands/InstallCommand.php b/catch/src/Commands/InstallCommand.php new file mode 100644 index 0000000..4f278b3 --- /dev/null +++ b/catch/src/Commands/InstallCommand.php @@ -0,0 +1,316 @@ +detectionEnvironment(); + + $this->copyEnvFile(); + + $this->askForCreatingDatabase(); + + $this->publishConfig(); + + $this->installed(); + } catch (\Throwable $e) { + File::delete(app()->environmentFilePath()); + + $this->error($e->getMessage()); + } + } + + /** + * 环境检测 + * + * @return void + */ + protected function detectionEnvironment(): void + { + $this->checkPHPVersion(); + + $this->checkExtensions(); + } + + + /** + * check needed php extensions + */ + private function checkExtensions() + { + /* @var Collection $loadedExtensions */ + $loadedExtensions = Collection::make(get_loaded_extensions())->map(function ($item) { + return strtolower($item); + }); + + Collection::make($this->defaultExtensions) + ->each(function ($extension) use ($loadedExtensions, &$continue) { + $extension = strtolower($extension); + + if (! $loadedExtensions->contains($extension)) { + $this->error("$extension extension 未安装"); + } + }); + } + + /** + * check php version + */ + private function checkPHPVersion() + { + if (version_compare(PHP_VERSION, '8.1.0', '<')) { + // $this->error('php version should >= 8.1'); + } + } + + + /** + * create database + * + * @param string $databaseName + * @return void + * @throws BindingResolutionException + */ + private function createDatabase(string $databaseName): void + { + $databaseConfig = config('database.connections.'.DB::getDefaultConnection()); + + $databaseConfig['database'] = null; + + app(ConnectionFactory::class)->make($databaseConfig)->select(sprintf("create database if not exists $databaseName default charset %s collate %s", 'utf8mb4', 'utf8mb4_general_ci')); + } + + /** + * copy .env + * + * @return void + */ + protected function copyEnvFile(): void + { + if (! File::exists(app()->environmentFilePath())) { + File::copy(app()->environmentFilePath().'.example', app()->environmentFilePath()); + } + + if (! File::exists(app()->environmentFilePath())) { + $this->error('【.env】创建失败, 请重新尝试或者手动创建!'); + } + + File::put(app()->environmentFile(), implode("\n", explode("\n", $this->getEnvFileContent()))); + } + + /** + * get env file content + * + * @return string + */ + protected function getEnvFileContent(): string + { + return File::get(app()->environmentFile()); + } + + /** + * publish config + * + * @return void + */ + protected function publishConfig(): void + { + // can't use Artisan::call, it will block the process, no reason found, just block!!! + exec(Application::formatCommandString('key:generate')); + + exec(Application::formatCommandString('vendor:publish --tag=catch-config')); + + exec(Application::formatCommandString('vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"')); + + exec(Application::formatCommandString('jwt:secret')); + } + + /** + * create database + */ + protected function askForCreatingDatabase() + { + $appUrl = $this->askFor('请配置应用的 URL'); + + if ($appUrl && ! Str::contains($appUrl, 'http://') && ! Str::contains($appUrl, 'https://')) { + $appUrl = 'http://'.$appUrl; + } + + $databaseName = $this->askFor('请输入数据库名称'); + + $prefix = $this->askFor('请输入数据库表前缀', ''); + + $dbHost = $this->askFor('请输入数据库主机地址', '127.0.0.1'); + + $dbPort = $this->askFor('请输入数据的端口号', 3306); + + $dbUsername = $this->askFor('请输入数据的用户名', 'root'); + + $dbPassword = $this->askFor('请输入数据库密码'); + + if (! $dbPassword) { + $dbPassword = $this->askFor('确认数据库密码为空吗?'); + } + + // set env + $env = explode("\n", $this->getEnvFileContent()); + + foreach ($env as &$value) { + foreach ([ + 'APP_URL' => $appUrl, + 'DB_HOST' => $dbHost, + 'DB_PORT' => $dbPort, + 'DB_DATABASE' => $databaseName, + 'DB_USERNAME' => $dbUsername, + 'DB_PASSWORD' => $dbPassword, + 'DB_PREFIX' => $prefix + ] as $key => $newValue) { + if (Str::contains($value, $key)) { + $value = $this->resetEnvValue($value, $newValue); + } + } + } + + // add vite config + $env[] = 'VITE_BASE_URL=${APP_URL}/api/'; + + File::put(app()->environmentFile(), implode("\n", $env)); + + app()->bootstrapWith([ + LoadEnvironmentVariables::class, + LoadConfiguration::class + ]); + + $this->info("正在创建数据库[$databaseName]..."); + + $this->createDatabase($databaseName); + + $this->info("创建数据库[$databaseName] 成功"); + } + + /** + * @param $originValue + * @param $newValue + * @return string + */ + protected function resetEnvValue($originValue, $newValue): string + { + if (Str::contains($originValue, '=')) { + $originValue = explode('=', $originValue); + + $originValue[1] = $newValue; + + return implode('=', $originValue); + } + + return $originValue; + } + + /** + * add prs4 autoload + */ + protected function addPsr4Autoload() + { + $composerFile = base_path().DIRECTORY_SEPARATOR.'composer.json'; + + $composerJson = json_decode(File::get(base_path().DIRECTORY_SEPARATOR.'composer.json'), true); + + $composerJson['autoload']['psr-4'][CatchAdmin::getModuleRootNamespace()] = str_replace('\\', '/', config('catch.module.root')); + + File::put($composerFile, json_encode($composerJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + + $this->info('composer dump autoload..., 请耐心等待'); + + app(Composer::class)->dumpAutoloads(); + } + + /** + * admin installed + */ + public function installed() + { + $this->addPsr4Autoload(); + + $this->info('🎉 CatchAdmin 已安装, 欢迎!'); + + $this->output->info(sprintf(' + /------------------------ welcome ----------------------------\ +| __ __ ___ __ _ | +| _________ _/ /______/ /_ / | ____/ /___ ___ (_)___ | +| / ___/ __ `/ __/ ___/ __ \ / /| |/ __ / __ `__ \/ / __ \ | +| / /__/ /_/ / /_/ /__/ / / / / ___ / /_/ / / / / / / / / / / | +| \___/\__,_/\__/\___/_/ /_/ /_/ |_\__,_/_/ /_/ /_/_/_/ /_/ | +| | + \ __ __ __ __ _ __ _ __ enjoy it ! _ __ __ __ __ __ __ ___ _ @ + 版本: %s + 初始账号: catch@admin.com + 初始密码: catchadmin', CatchAdmin::VERSION)); + + $this->support(); + } + + /** + * support + * + * @return void + */ + protected function support(): void + { + $answer = $this->askFor('支持我们! 感谢在 Github 上 star 该项目', 'yes', true); + + if (in_array(strtolower($answer), ['yes', 'y'])) { + if (PHP_OS_FAMILY == 'Darwin') { + exec('open https://github.com/JaguarJack/catch-admin'); + } + if (PHP_OS_FAMILY == 'Windows') { + exec('start https://github.com/JaguarJack/catch-admin'); + } + if (PHP_OS_FAMILY == 'Linux') { + exec('xdg-open https://github.com/JaguarJack/catch-admin'); + } + } + + $this->info('支 持: https://github.com/jaguarjack/catchadmin'); + $this->info('文 档: https://catchadmin.com/docs/3.0/intro'); + $this->info('官 网: https://catchadmin.com'); + } +} diff --git a/catch/src/Commands/Migrate/MigrateFresh.php b/catch/src/Commands/Migrate/MigrateFresh.php new file mode 100644 index 0000000..957e01d --- /dev/null +++ b/catch/src/Commands/Migrate/MigrateFresh.php @@ -0,0 +1,55 @@ +argument('module'); + + if (! File::isDirectory(CatchAdmin::getModuleMigrationPath($module))) { + Artisan::call('migration:fresh', [ + '--path' => CatchAdmin::getModuleRelativePath(CatchAdmin::getModuleMigrationPath($module)), + + '--force' => $this->option('force') + ]); + } else { + $this->error('No migration files in module'); + } + } +} diff --git a/catch/src/Commands/Migrate/MigrateMake.php b/catch/src/Commands/Migrate/MigrateMake.php new file mode 100644 index 0000000..f406a47 --- /dev/null +++ b/catch/src/Commands/Migrate/MigrateMake.php @@ -0,0 +1,91 @@ +argument('module')); + + $file = $migrationPath.$this->getMigrationFile(); + + File::put($file, Str::of($this->getStubContent())->replace( + '{table}', + $this->getTable() + )->toString()); + + + if (File::exists($file)) { + $this->info($file.' has been created'); + } else { + $this->error($file.' create failed'); + } + } + + /** + * + * + * @return string + */ + protected function getMigrationFile(): string + { + return date('Y_m_d_His').'_create_'.$this->getTable().'.php'; + } + + /** + * + * + * @return string + */ + protected function getTable(): string + { + return Str::of($this->argument('table'))->ucfirst()->snake()->lower()->toString(); + } + + /** + * get stub content + * + * @return string + */ + protected function getStubContent(): string + { + return File::get(dirname(__DIR__).DIRECTORY_SEPARATOR.'stubs'.DIRECTORY_SEPARATOR.'migration.stub'); + } +} diff --git a/catch/src/Commands/Migrate/MigrateRun.php b/catch/src/Commands/Migrate/MigrateRun.php new file mode 100644 index 0000000..4511ed0 --- /dev/null +++ b/catch/src/Commands/Migrate/MigrateRun.php @@ -0,0 +1,74 @@ +argument('module'); + + if (File::isDirectory(CatchAdmin::getModuleMigrationPath($module))) { + foreach (File::files(CatchAdmin::getModuleMigrationPath($module)) as $file) { + $path = Str::of(CatchAdmin::getModuleRelativePath(CatchAdmin::getModuleMigrationPath($module))) + + ->remove('.')->append($file->getFilename()); + + Artisan::call('migrate', [ + '--path' => $path, + + '--force' => $this->option('force') + ]); + } + + $this->info("Module [$module] migrate success"); + } else { + $this->error('No migration files in module'); + } + } +} diff --git a/catch/src/Commands/Migrate/MigrationRollback.php b/catch/src/Commands/Migrate/MigrationRollback.php new file mode 100644 index 0000000..55d4278 --- /dev/null +++ b/catch/src/Commands/Migrate/MigrationRollback.php @@ -0,0 +1,65 @@ +argument('module'); + + if (! File::isDirectory(CatchAdmin::getModuleMigrationPath($module))) { + Artisan::call('migration:rollback', [ + '--path' => CatchAdmin::getModuleRelativePath(CatchAdmin::getModuleMigrationPath($module)), + + '--force' => $this->option('force') + ]); + } else { + $this->error('No migration files in module'); + } + } +} diff --git a/catch/src/Commands/Migrate/SeedRun.php b/catch/src/Commands/Migrate/SeedRun.php new file mode 100644 index 0000000..34ef8c2 --- /dev/null +++ b/catch/src/Commands/Migrate/SeedRun.php @@ -0,0 +1,88 @@ +loadModuleSeeders(); + + if ($class = $this->option('class')) { + (new $class())->run(); + } else { + foreach ($classes as $class) { + $class = new $class(); + if (method_exists($class, 'run')) { + $class->run(); + } + } + } + + $this->info('Seed run successfully'); + } + + + /** + * + * @time 2021年07月31日 + * @return array + */ + protected function loadModuleSeeders(): array + { + $files = File::allFiles(CatchAdmin::getModuleSeederPath($this->argument('module'))); + + $fileNames = []; + + foreach ($files as $file) { + require_once $file->getRealPath(); + + $fileNames[] = pathinfo($file->getBasename(), PATHINFO_FILENAME); + } + + return $fileNames; + } +} diff --git a/catch/src/Commands/Migrate/SeederMake.php b/catch/src/Commands/Migrate/SeederMake.php new file mode 100644 index 0000000..8aa1557 --- /dev/null +++ b/catch/src/Commands/Migrate/SeederMake.php @@ -0,0 +1,88 @@ +argument('module')); + + $file = $seederPath.$this->getSeederName().'.php'; + + if (File::exists($file)) { + $answer = $this->ask($file.' already exists, Did you want replace it?', 'Y'); + + if (! Str::of($answer)->lower()->exactly('y')) { + exit; + } + } + + File::put($file, $this->getSeederContent()); + + if (File::exists($file)) { + $this->info($file.' has been created'); + } else { + $this->error($file.' create failed'); + } + } + + /** + * seeder content + * + * @return string + * @throws \Exception + */ + protected function getSeederContent(): string + { + return File::get(dirname(__DIR__).DIRECTORY_SEPARATOR.'stubs'.DIRECTORY_SEPARATOR.'seeder.stub'); + } + + /** + * seeder name + * + * @return string + */ + protected function getSeederName(): string + { + return Str::of($this->argument('name'))->ucfirst()->toString(); + } +} diff --git a/catch/src/Commands/stubs/controller.stub b/catch/src/Commands/stubs/controller.stub new file mode 100644 index 0000000..ddaac06 --- /dev/null +++ b/catch/src/Commands/stubs/controller.stub @@ -0,0 +1,35 @@ +name(); + } + + + /** + * get value + * + * @return int + */ + public function value(): int + { + return match ($this) { + Code::SUCCESS => 10000, + Code::LOST_LOGIN => 10001, + Code::VALIDATE_FAILED => 10002, + Code::PERMISSION_FORBIDDEN => 10003, + Code::LOGIN_FAILED => 10004, + Code::FAILED => 10005, + Code::LOGIN_EXPIRED => 10006, + Code::LOGIN_BLACKLIST => 10007, + Code::USER_FORBIDDEN => 10008, + Code::WECHAT_RESPONSE_ERROR => 40000, + }; + } + + /** + * name + * + * @return string + */ + public function name(): string + { + return match ($this) { + self::SUCCESS => '操作成功', + self::LOST_LOGIN => '登陆失效', + self::VALIDATE_FAILED => '验证失败', + self::PERMISSION_FORBIDDEN => '权限禁止', + self::LOGIN_FAILED => '登陆失败', + self::FAILED => '操作失败', + self::LOGIN_EXPIRED => '登陆过期', + self::LOGIN_BLACKLIST => '已被加入黑名单', + self::USER_FORBIDDEN => '账户被禁用', + self::WECHAT_RESPONSE_ERROR => '微信响应错误' + }; + } +} diff --git a/catch/src/Enums/Enum.php b/catch/src/Enums/Enum.php new file mode 100644 index 0000000..e344303 --- /dev/null +++ b/catch/src/Enums/Enum.php @@ -0,0 +1,10 @@ + '启用', + + Status::Disable => '禁用' + }; + } + + /** + * get value + * + * @return int + */ + public function value(): int + { + return match ($this) { + Status::Enable => 1, + + Status::Disable => 2, + }; + } +} diff --git a/catch/src/Events/Module/Created.php b/catch/src/Events/Module/Created.php new file mode 100644 index 0000000..6d5ac32 --- /dev/null +++ b/catch/src/Events/Module/Created.php @@ -0,0 +1,21 @@ +user = $user; + } +} diff --git a/catch/src/Exceptions/CatchException.php b/catch/src/Exceptions/CatchException.php new file mode 100644 index 0000000..9e3020c --- /dev/null +++ b/catch/src/Exceptions/CatchException.php @@ -0,0 +1,65 @@ +value(); + } + + if ($this->code instanceof Enum && ! $code) { + $code = $this->code->value(); + } + + parent::__construct($this->statusCode(), $message ?: $this->message, null, [], $code); + } + + /** + * status code + * + * @return int + */ + public function statusCode(): int + { + return 500; + } + + /** + * render + * + * @return array + */ + public function render(): array + { + return [ + 'code' => $this->code, + + 'message' => $this->message + ]; + } +} diff --git a/catch/src/Exceptions/FailedException.php b/catch/src/Exceptions/FailedException.php new file mode 100644 index 0000000..715d614 --- /dev/null +++ b/catch/src/Exceptions/FailedException.php @@ -0,0 +1,22 @@ +dontReport = config('catch.exception.dont_report'); + + $this->dontFlash = config('catch.exception.dont_flash'); + } + } +} diff --git a/catch/src/Exceptions/UnMatchedTokenException.php b/catch/src/Exceptions/UnMatchedTokenException.php new file mode 100644 index 0000000..a034044 --- /dev/null +++ b/catch/src/Exceptions/UnMatchedTokenException.php @@ -0,0 +1,22 @@ +response; + + if ($response instanceof JsonResponse) { + $exception = $response->exception; + + if ($response->getStatusCode() == SymfonyResponse::HTTP_OK && ! $exception) { + $response->setData($this->formatData($response->getData())); + } + } + } + + /** + * @param mixed $data + * @return array + */ + protected function formatData(mixed $data): array + { + $responseData = [ + 'code' => Code::SUCCESS->value(), + 'message' => Code::SUCCESS->message(), + ]; + + if (is_object($data) && property_exists($data, 'per_page') + && property_exists($data, 'total') + && property_exists($data, 'current_page')) { + $responseData['data'] = $data->data; + $responseData['total'] = $data->total; + $responseData['limit'] = $data->per_page; + $responseData['page'] = $data->current_page; + + return $responseData; + } + + $responseData['data'] = $data; + + return $responseData; + } +} diff --git a/catch/src/Middleware/AuthMiddleware.php b/catch/src/Middleware/AuthMiddleware.php new file mode 100644 index 0000000..b7b2dd8 --- /dev/null +++ b/catch/src/Middleware/AuthMiddleware.php @@ -0,0 +1,42 @@ +check()) { + $user = Auth::guard($guardName)->user(); + + Event::dispatch(new UserEvent($user)); + } + } catch (Exception|Throwable $e) { + if ($e instanceof TokenExpiredException) { + throw new FailedException(Code::LOGIN_EXPIRED->message(), Code::LOGIN_EXPIRED); + } + + if ($e instanceof TokenBlacklistedException) { + throw new FailedException(Code::LOGIN_BLACKLIST->message(), Code::LOGIN_BLACKLIST); + } + + throw new FailedException(Code::LOST_LOGIN->message().":{$e->getMessage()}", Code::LOST_LOGIN); + } finally { + return $next($request); + } + } +} diff --git a/catch/src/Middleware/JsonResponseMiddleware.php b/catch/src/Middleware/JsonResponseMiddleware.php new file mode 100644 index 0000000..95cfe23 --- /dev/null +++ b/catch/src/Middleware/JsonResponseMiddleware.php @@ -0,0 +1,21 @@ +getContent()); + } + + return $response; + } +} diff --git a/catch/src/Providers/CatchAdminServiceProvider.php b/catch/src/Providers/CatchAdminServiceProvider.php new file mode 100644 index 0000000..1e77c47 --- /dev/null +++ b/catch/src/Providers/CatchAdminServiceProvider.php @@ -0,0 +1,229 @@ +bootDefaultModuleProviders(); + $this->bootModuleProviders(); + + $this->registerEvents(); + + $this->listenDBLog(); + + $this->mergeAuthConfig(); + + // $this->registerExceptionHandler(); + + MacrosRegister::boot(); + } + + /** + * register + * + * @return void + * @throws ReflectionException + */ + public function register(): void + { + $this->registerCommands(); + + $this->registerModuleRepository(); + + $this->publishConfig(); + + $this->publishModuleMigration(); + } + + + /** + * register commands + * + * @return void + * @throws ReflectionException + */ + protected function registerCommands(): void + { + loadCommands(dirname(__DIR__).DIRECTORY_SEPARATOR.'Commands', 'Catch\\'); + } + + /** + * register exception handler + * + * @return void + */ + protected function registerExceptionHandler(): void + { + $this->app->singleton( + ExceptionHandler::class, + Handler::class + ); + } + + /** + * bind module repository + * + * @return void + */ + protected function registerModuleRepository(): void + { + // register module manager + $this->app->singleton(ModuleManager::class, function () { + return new ModuleManager(fn () => Container::getInstance()); + }); + + // register module repository + $this->app->singleton(ModuleRepositoryInterface::class, function () { + return $this->app->make(ModuleManager::class)->driver(); + }); + + $this->app->alias(ModuleRepositoryInterface::class, 'module'); + } + + /** + * register events + * + * @return void + */ + protected function registerEvents(): void + { + Event::listen(RequestHandled::class, config('catch.response.request_handled_listener')); + } + + /** + * publish config + * + * @return void + */ + protected function publishConfig(): void + { + if ($this->app->runningInConsole()) { + $from = dirname(__DIR__, 2).DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'catch.php'; + + $to = config_path('catch.php'); + + $this->publishes([$from => $to], 'catch-config'); + } + } + + + /** + * publish module migration + * + * @return void + */ + protected function publishModuleMigration(): void + { + if ($this->app->runningInConsole()) { + $form = dirname(__DIR__, 2).DIRECTORY_SEPARATOR.'database'.DIRECTORY_SEPARATOR.'migrations'.DIRECTORY_SEPARATOR.'2022_11_14_034127_module.php'; + + $to = database_path('migrations').DIRECTORY_SEPARATOR.'2022_11_14_034127_module.php'; + + $this->publishes([$form => $to], 'catch-module'); + } + } + + /** + * + * @return void + * @throws NotFoundExceptionInterface + * @throws ContainerExceptionInterface + */ + protected function bootDefaultModuleProviders(): void + { + foreach ($this->app['config']->get('catch.module.default') as $module) { + $provider = CatchAdmin::getModuleServiceProvider($module); + if (class_exists($provider)) { + $this->app->register($provider); + } + } + } + + /** + * boot module + * + * @throws BindingResolutionException + */ + protected function bootModuleProviders() + { + foreach ($this->app->make(ModuleRepositoryInterface::class)->getEnabled() as $module) { + if (class_exists($module['service'])) { + $this->app->register($module['service']); + } + } + } + + /** + * listen db log + * + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @return void + */ + protected function listenDBLog(): void + { + if ($this->app['config']->get('catch.listen_db_log')) { + Query::listen(); + + $this->app->terminating(function () { + Query::log(); + }); + } + } + + /** + * merge auth config + * + * @throws BindingResolutionException + * @return void + */ + protected function mergeAuthConfig(): void + { + if (! $this->app->configurationIsCached()) { + $config = $this->app->make('config'); + + $config->set('auth', array_merge_recursive( + $config->get('catch.auth', []), + $config->get('auth', []) + )); + } + } +} diff --git a/catch/src/Providers/CatchModuleServiceProvider.php b/catch/src/Providers/CatchModuleServiceProvider.php new file mode 100644 index 0000000..f292f23 --- /dev/null +++ b/catch/src/Providers/CatchModuleServiceProvider.php @@ -0,0 +1,63 @@ +registerModuleRoute(); + + foreach ($this->events as $event => $listener) { + Event::listen($event, $listener); + } + } + + /** + * load module router + * + * @return void + * @throws NotFoundExceptionInterface + * @throws ContainerExceptionInterface + */ + protected function registerModuleRoute(): void + { + $route = $this->app['config']->get('catch.route'); + + Route::prefix($route['prefix']) + ->middleware($route['middlewares']) + ->group($this->routePath()); + } + + /** + * route path + * + * @return string|array + */ + abstract protected function routePath(): string | array; +} diff --git a/catch/src/Support/Composer.php b/catch/src/Support/Composer.php new file mode 100644 index 0000000..421e78e --- /dev/null +++ b/catch/src/Support/Composer.php @@ -0,0 +1,108 @@ +checkPHPVersion(); + + $command = ['require', $package]; + + return $this->runCommand($command); + } + + /** + * require dev-package + * + * @param string $package + * @return string + * @throws PhpVersionNotSupportedException + */ + public function requireDev(string $package): string + { + $this->checkPHPVersion(); + + $command = ['require', '--dev', $package]; + + return $this->runCommand($command); + } + + + /** + * remove + * + * @param string $package + */ + public function remove(string $package) + { + $this->runCommand([ + 'remove', $package + ]); + } + + /** + * + * @param array $command + * @return string + */ + protected function runCommand(array $command): string + { + $command = array_merge($this->findComposer(), $command); + + if ($this->ignorePlatformReqs) { + $command[] = '--ignore-platform-reqs'; + } + + $process = $this->getProcess($command); + + $process->run(); + + return $process->getOutput(); + } + + /** + * + * @throws PhpVersionNotSupportedException + * @return void + */ + protected function checkPHPVersion(): void + { + $composerJson = json_decode(File::get(base_path().DIRECTORY_SEPARATOR.'composer.json'), true); + + $phpVersion = PHP_VERSION; + + $needPHPVersion = Str::of($composerJson['require']['php'])->remove('^'); + + if (version_compare($phpVersion, $needPHPVersion, '<') && ! $this->ignorePlatformReqs) { + throw new PhpVersionNotSupportedException("PHP $phpVersion 版本太低, 需要 PHP {$needPHPVersion}!如果想忽略版本要求, s可使用 {ignorePlatFormReqs} 方法然后安装"); + } + } + + + /** + * + * @return $this + */ + public function ignorePlatFormReqs(): static + { + $this->ignorePlatformReqs = true; + + return $this; + } +} diff --git a/catch/src/Support/DB/Query.php b/catch/src/Support/DB/Query.php new file mode 100644 index 0000000..be1362b --- /dev/null +++ b/catch/src/Support/DB/Query.php @@ -0,0 +1,55 @@ +sql.' | %s ms'.PHP_EOL, date('Y-m-d H:i'), $query->time) + ); + + static::$log .= vsprintf($sql, $query->bindings); + }); + } + + + /** + * @return void + */ + public static function log(): void + { + if (static::$log) { + $sqlLogPath = storage_path('logs'.DIRECTORY_SEPARATOR.'query'.DIRECTORY_SEPARATOR); + + if (! File::isDirectory($sqlLogPath)) { + File::makeDirectory($sqlLogPath, 0777, true); + } + + $logFile = $sqlLogPath.date('Ymd').'.log'; + + if (! File::exists($logFile)) { + File::put($logFile, '', true); + } + + file_put_contents($logFile, static::$log.PHP_EOL, LOCK_EX | FILE_APPEND); + + static::$log = null; + } + } +} diff --git a/catch/src/Support/DB/SoftDelete.php b/catch/src/Support/DB/SoftDelete.php new file mode 100644 index 0000000..585285f --- /dev/null +++ b/catch/src/Support/DB/SoftDelete.php @@ -0,0 +1,19 @@ +where($model->getQualifiedDeletedAtColumn(), '=', 0); + } +} diff --git a/catch/src/Support/Macros/Blueprint.php b/catch/src/Support/Macros/Blueprint.php new file mode 100644 index 0000000..7bd0ac7 --- /dev/null +++ b/catch/src/Support/Macros/Blueprint.php @@ -0,0 +1,140 @@ +createdAt(); + + $bluePrint->updatedAt(); + + $bluePrint->deletedAt(); + + $bluePrint->status(); + + $bluePrint->creatorId(); + + $bluePrint->unixTimestamp(); + + $bluePrint->parentId(); + + $bluePrint->sort(); + } + + /** + * created unix timestamp + * + * @return void + */ + public function createdAt(): void + { + LaravelBlueprint::macro(__FUNCTION__, function () { + $this->unsignedInteger('created_at')->default(0)->comment('created time'); + }); + } + + /** + * update unix timestamp + * + * @return void + */ + public function updatedAt(): void + { + LaravelBlueprint::macro(__FUNCTION__, function () { + $this->unsignedInteger('updated_at')->default(0)->comment('updated time'); + }); + } + + /** + * soft delete + * + * @return void + */ + public function deletedAt(): void + { + LaravelBlueprint::macro(__FUNCTION__, function () { + $this->unsignedInteger('deleted_at')->default(0)->comment('delete time'); + }); + } + + + /** + * unix timestamp + * + * @param bool $softDeleted + * @return void + */ + public function unixTimestamp(bool $softDeleted = true): void + { + LaravelBlueprint::macro(__FUNCTION__, function () use ($softDeleted) { + $this->createdAt(); + $this->updatedAt(); + + if ($softDeleted) { + $this->deletedAt(); + } + }); + } + + /** + * creator id + * + * @return void + */ + public function creatorId(): void + { + LaravelBlueprint::macro(__FUNCTION__, function () { + $this->unsignedInteger('creator_id')->default(0)->comment('creator id'); + }); + } + + + /** + * parent ID + * + * @return void + */ + public function parentId(): void + { + LaravelBlueprint::macro(__FUNCTION__, function () { + $this->unsignedInteger('parent_id')->default(0)->comment('parent id'); + }); + } + + + /** + * status + * + * @return void + */ + public function status(): void + { + LaravelBlueprint::macro(__FUNCTION__, function ($default = 1) { + $this->tinyInteger('status')->default($default)->comment('1:normal 2: forbidden'); + }); + } + + /** + * sort + * + * @param int $default + * @return void + */ + public function sort(int $default = 1): void + { + LaravelBlueprint::macro(__FUNCTION__, function () use ($default) { + $this->integer('sort')->comment('sort')->default($default); + }); + } +} diff --git a/catch/src/Support/Macros/Builder.php b/catch/src/Support/Macros/Builder.php new file mode 100644 index 0000000..4706554 --- /dev/null +++ b/catch/src/Support/Macros/Builder.php @@ -0,0 +1,107 @@ +whereLike(); + + $builder->quickSearch(); + + $builder->tree(); + } + + /** + * where like + * + * @return void + */ + public function whereLike(): void + { + LaravelBuilder::macro(__FUNCTION__, function ($filed, $value) { + return $this->where($filed, 'like', "%$value%"); + }); + } + + + /** + * quick search + * + * @return void + */ + public function quickSearch(): void + { + LaravelBuilder::macro(__FUNCTION__, function (array $params = []) { + $params = array_merge(request()->all(), $params); + + if (! property_exists($this->model, 'searchable')) { + return $this; + } + + // filter null & empty string + $params = array_filter($params, function ($value) { + return (is_string($value) && strlen($value)) || is_numeric($value); + }); + + $wheres = []; + + if (! empty($this->model->searchable)) { + foreach ($this->model->searchable as $field => $op) { + // 临时变量 + $_field = $field; + // contains alias + if (str_contains($field, '.')) { + [, $_field] = explode('.', $field); + } + + if (isset($params[$_field])) { + $opString = Str::of($op)->lower(); + if ($opString->exactly('op')) { + $value = implode(',', $params[$_field]); + } elseif ($opString->exactly('like')) { + $value = "%{$params[$_field]}%"; + } elseif ($opString->exactly('rlike')) { + $value = "{$params[$_field]}%"; + } elseif ($opString->exactly('llike')) { + $value = "%{$params[$_field]}"; + } else { + $value = $params[$_field]; + } + $wheres[] = [$field, $op, $value]; + } + } + } + + $this->where($wheres); + + return $this; + }); + } + + /** + * where like + * + * @time 2021年08月06日 + * @return void + */ + public function tree(): void + { + LaravelBuilder::macro(__FUNCTION__, function (string $id, string $parentId, ...$fields) { + $fields = array_merge([$id, $parentId], $fields); + + return $this->get($fields)->toTree(0, $parentId); + }); + } +} diff --git a/catch/src/Support/Macros/Collection.php b/catch/src/Support/Macros/Collection.php new file mode 100644 index 0000000..69ff7b9 --- /dev/null +++ b/catch/src/Support/Macros/Collection.php @@ -0,0 +1,65 @@ +toOptions(); + + $collection->toTree(); + } + + /** + * collection to tree + * + * @return void + */ + public function toTree(): void + { + LaravelCollection::macro(__FUNCTION__, function (int $pid = 0, string $pidField = 'parent_id', string $child = 'children') { + return Tree::done($this->all(), $pid, $pidField, $child); + }); + } + + /** + * toOptions + * + * @return void + */ + public function toOptions(): void + { + LaravelCollection::macro(__FUNCTION__, function () { + return $this->transform(function ($item, $key) use (&$options) { + if ($item instanceof Arrayable) { + $item = $item->toArray(); + } + + if (is_array($item)) { + $item = array_values($item); + return [ + 'value' => $item[0], + 'label' => $item[1] + ]; + } else { + return [ + 'value' => $key, + 'label' => $item + ]; + } + })->values(); + }); + } +} diff --git a/catch/src/Support/Macros/Register.php b/catch/src/Support/Macros/Register.php new file mode 100644 index 0000000..1386c5d --- /dev/null +++ b/catch/src/Support/Macros/Register.php @@ -0,0 +1,23 @@ +model = $this->createModuleModel(); + } + + /** + * all + * + * @param array $search + * @return Collection + */ + public function all(array $search): Collection + { + return $this->model::query() + ->when($search['name'] ?? false, function ($query) use ($search) { + $query->where('name', 'like', '%'.$search['name'].'%'); + })->get(); + } + + /** + * create module json + * + * @param array $module + * @return bool|int + */ + public function create(array $module): bool|int + { + $this->hasSameModule($module); + + return $this->model->save([ + 'name' => $module['name'], + 'path' => $module['path'], + 'description' => $module['desc'], + 'keywords' => $module['keywords'], + 'service' => sprintf('\\%s%s', CatchAdmin::getModuleNamespace($module['name']), ucfirst($module['name']).'ServiceProvider'), + ]); + } + + /** + * module info + * + * @param string $name + * @return Collection + */ + public function show(string $name): Collection + { + return $this->model->where('name', $name)->first(); + } + + /** + * update module json + * + * @param string $name + * @param array $module + * @return bool|int + */ + public function update(string $name, array $module): bool|int + { + return $this->model->where('name', $name) + + ->update([ + 'name' => $module['name'], + 'alias' => $module['alias'], + 'description' => $module['desc'], + 'keywords' => $module['keywords'], + ]); + } + + /** + * delete module json + * + * @param string $name + * @return bool|int + */ + public function delete(string $name): bool|int + { + return $this->model->where('name', $name)->delete(); + } + + /** + * disable or enable + * + * @param $name + * @return bool|int + */ + public function disOrEnable($name): bool|int + { + $module = $this->show($name); + + $module->status = (int) $module->status; + + return $module->save(); + } + + /** + * get enabled + * + * @return Collection + */ + public function getEnabled(): Collection + { + // TODO: Implement getEnabled() method. + return $this->model->where('status', Status::Enable->value())->get(); + } + + /** + * + * @param array $module + * @return void + */ + protected function hasSameModule(array $module): void + { + if ($this->model->where('name', $module['name'])->first()) { + throw new FailedException(sprintf('Module [%s] has been created', $module['name'])); + } + + if ($this->model->where('alias', $module['alias'])->first()) { + throw new FailedException(sprintf('Module Alias [%s] has been exised', $module['alias'])); + } + } + + /** + * create model + * @return Model + */ + protected function createModuleModel(): Model + { + return new class () extends Model { + protected $table; + + public function __construct(array $attributes = []) + { + parent::__construct($attributes); + + $this->table = Container::getInstance()->make('config')->get('catch.module.driver.table_name'); + } + }; + } +} diff --git a/catch/src/Support/Module/Driver/FileDriver.php b/catch/src/Support/Module/Driver/FileDriver.php new file mode 100644 index 0000000..0ae6dd9 --- /dev/null +++ b/catch/src/Support/Module/Driver/FileDriver.php @@ -0,0 +1,193 @@ +moduleJson = storage_path('app').DIRECTORY_SEPARATOR.'modules.json'; + } + + /** + * all + * + * @param array $search + * @return Collection + */ + public function all(array $search = []): Collection + { + if (! File::exists($this->moduleJson)) { + return Collection::make([]); + } + + if (! Str::length(File::get($this->moduleJson))) { + return Collection::make([]); + } + + $modules = Collection::make(\json_decode(File::get($this->moduleJson), true))->values(); + + $name = $search['name'] ?? ''; + + if (! $name) { + return $modules; + } + + return $modules->filter(function ($module) use ($name) { + return Str::of($module['name'])->contains($name); + }); + } + + /** + * create module json + * + * @param array $module + * @return bool + */ + public function create(array $module): bool + { + $modules = $this->all(); + + $this->hasSameModule($module, $modules); + + $module['service'] = sprintf('\\%s', CatchAdmin::getModuleServiceProvider($module['path'])); + $module['version'] = '1.0.0'; + $module['enable'] = true; + + File::put($this->moduleJson, $modules->push($module)->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); + + return true; + } + + /** + * module info + * + * @param string $name + * @return Collection + */ + public function show(string $name): Collection + { + foreach ($this->all() as $module) { + if (Str::of($module['name'])->exactly($name)) { + return Collection::make($module); + } + } + + throw new FailedException("Module [$name] not Found"); + } + + /** + * update module json + * + * @param string $name + * @param array $module + * @return bool + */ + public function update(string $name, array $module): bool + { + File::put($this->moduleJson, $this->all()->map(function ($m) use ($module, $name) { + if (Str::of($name)->exactly($m['name'])) { + $m['path'] = $module['path']; + $m['name'] = $module['name']; + $m['description'] = $module['description'] ?? ''; + $m['keywords'] = $module['keywords'] ?? ''; + $m['enable'] = $module['enable']; + } + return $m; + })->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); + + return true; + } + + /** + * delete module json + * + * @param string $name + * @return bool + */ + public function delete(string $name): bool + { + File::put($this->moduleJson, $this->all()->filter(function ($module) use ($name) { + if (! Str::of($name)->exactly($module['name'])) { + return $module; + } + })->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); + + + return true; + } + + /** + * disable or enable + * + * @param $name + * @return bool|int + */ + public function disOrEnable($name): bool|int + { + return File::put($this->moduleJson, $this->all()->map(function ($module) use ($name) { + if (Str::of($module['name'])->exactly($name)) { + $module['enable'] = ! $module['enable']; + } + return $module; + })->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); + } + + /** + * get enabled + * + * @return Collection + */ + public function getEnabled(): Collection + { + // TODO: Implement getEnabled() method. + return $this->all()->where('enable', true)->values(); + } + + /** + * + * @param array $module + * @param Collection $modules + * @return void + */ + protected function hasSameModule(array $module, Collection $modules): void + { + if ($modules->count()) { + if ($modules->pluck('name')->contains($module['name'])) { + throw new FailedException(sprintf('Module [%s] has been created', $module['name'])); + } + + if ($modules->pluck('path')->contains($module['path'])) { + throw new FailedException(sprintf('Module path [%s] has been existed', $module['path'])); + } + } + } +} diff --git a/catch/src/Support/Module/Installer.php b/catch/src/Support/Module/Installer.php new file mode 100644 index 0000000..8b38cbf --- /dev/null +++ b/catch/src/Support/Module/Installer.php @@ -0,0 +1,97 @@ +moduleRepository->delete($this->info()['name']); + + + $this->removePackages(); + } + + /** + * invoke + * + * @return void + */ + public function __invoke(): void + { + // TODO: Implement __invoke() method. + $this->moduleRepository->create($this->info()); + + // migration + + // seed + + $this->requirePackages(); + } + + /** + * composer installer + * + * @return Composer + */ + protected function composer(): Composer + { + return app(Composer::class); + } +} diff --git a/catch/src/Support/Module/ModuleManager.php b/catch/src/Support/Module/ModuleManager.php new file mode 100644 index 0000000..a50033f --- /dev/null +++ b/catch/src/Support/Module/ModuleManager.php @@ -0,0 +1,63 @@ +config->get('catch.module.driver.default'); + } + + /** + * create file driver + * + * @return FileDriver + */ + public function createFileDriver(): FileDriver + { + return new FileDriver(); + } + + /** + * create database driver + * + * @return DatabaseDriver + */ + public function createDatabaseDriver(): DatabaseDriver + { + return new DatabaseDriver(); + } +} diff --git a/catch/src/Support/Module/ModuleRepository.php b/catch/src/Support/Module/ModuleRepository.php new file mode 100644 index 0000000..ba85043 --- /dev/null +++ b/catch/src/Support/Module/ModuleRepository.php @@ -0,0 +1,144 @@ +moduleRepository = $moduleRepository; + } + + /** + * all + * + * @param array $search + * @return Collection + */ + public function all(array $search): Collection + { + return $this->moduleRepository->all($search); + } + + /** + * create module json + * + * @param array $module + * @return bool + */ + public function create(array $module): bool + { + Event::dispatch(new Creating($module)); + + $this->moduleRepository->create($module); + + Event::dispatch(new Created($module)); + + return true; + } + + /** + * module info + * + * @param string $name + * @return Collection + * @throws Exception + */ + public function show(string $name): Collection + { + try { + return $this->moduleRepository->show($name); + } catch (Exception $e) { + throw new $e(); + } + } + + /** + * update module json + * + * @param string $name + * @param array $module + * @return bool + */ + public function update(string $name, array $module): bool + { + Event::dispatch(new Updating($name, $module)); + + $this->moduleRepository->update($name, $module); + + Event::dispatch(new Updated($name, $module)); + + return true; + } + + /** + * delete module json + * + * @param string $name + * @return bool + * @throws Exception + */ + public function delete(string $name): bool + { + $module = $this->show($name); + + $this->moduleRepository->delete($name); + + Event::dispatch(new Deleted($module)); + + return true; + } + + /** + * disable or enable + * + * @param string $name + * @return bool|int + */ + public function disOrEnable(string $name): bool|int + { + return $this->moduleRepository->disOrEnable($name); + } + + /** + * get enabled + * + * @return Collection + */ + public function getEnabled(): Collection + { + // TODO: Implement getEnabled() method. + return $this->moduleRepository->getEnabled(); + } +} diff --git a/catch/src/Support/Tree.php b/catch/src/Support/Tree.php new file mode 100644 index 0000000..74e62e7 --- /dev/null +++ b/catch/src/Support/Tree.php @@ -0,0 +1,61 @@ +archive = $archive ? $archive : new ZipArchive(); + + $res = $this->archive->open($filePath, ($create ? ZipArchive::CREATE : 0)); + if ($res !== true) { + throw new Exception("Error: Failed to open $filePath! Error: ".$this->getErrorMessage($res)); + } + } + + /** + * Add a file to the opened Archive + * + * @param $pathToFile + * @param $pathInArchive + */ + public function addFile($pathToFile, $pathInArchive): void + { + $this->archive->addFile($pathToFile, $pathInArchive); + } + + /** + * Add an empty directory + * + * @param $dirName + */ + public function addEmptyDir($dirName): void + { + $this->archive->addEmptyDir($dirName); + } + + /** + * Add a file to the opened Archive using its contents + * + * @param string $name + * @param $content + */ + public function addFromString(string $name, $content): void + { + $this->archive->addFromString($name, $content); + } + + /** + * Remove a file permanently from the Archive + * + * @param string $pathInArchive + */ + public function removeFile(string $pathInArchive): void + { + $this->archive->deleteName($pathInArchive); + } + + /** + * Get the content of a file + * + * @param string $pathInArchive + * + * @return string + */ + public function getFileContent(string $pathInArchive): string + { + return $this->archive->getFromName($pathInArchive); + } + + /** + * Get the stream of a file + * + * @param string $pathInArchive + * + * @return bool + */ + public function getFileStream(string $pathInArchive): bool + { + return $this->archive->getStream($pathInArchive); + } + + /** + * Will loop over every item in the archive and will execute the callback on them + * Will provide the filename for every item + * + * @param $callback + */ + public function each($callback): void + { + for ($i = 0; $i < $this->archive->numFiles; ++$i) { + //skip if folder + $stats = $this->archive->statIndex($i); + if ($stats['size'] === 0 && $stats['crc'] === 0) { + continue; + } + call_user_func_array($callback, [ + 'file' => $this->archive->getNameIndex($i), + 'stats' => $this->archive->statIndex($i) + ]); + } + } + + /** + * Checks whether the file is in the archive + * + * @param $fileInArchive + * + * @return bool + */ + public function fileExists($fileInArchive): bool + { + return $this->archive->locateName($fileInArchive) !== false; + } + + /** + * Sets the password to be used for decompressing + * function named usePassword for clarity + * + * @param $password + * + * @return bool + */ + public function usePassword($password): bool + { + return $this->archive->setPassword($password); + } + + /** + * Returns the status of the archive as a string + * + * @return string + */ + public function getStatus(): string + { + return $this->archive->getStatusString(); + } + + /** + * Closes the archive and saves it + */ + public function close(): void + { + @$this->archive->close(); + } + + /** + * get error message + * + * @param $resultCode + * @return string + */ + private function getErrorMessage($resultCode): string + { + return match ($resultCode) { + ZipArchive::ER_EXISTS => 'ZipArchive::ER_EXISTS - File already exists.', + ZipArchive::ER_INCONS => 'ZipArchive::ER_INCONS - Zip archive inconsistent.', + ZipArchive::ER_MEMORY => 'ZipArchive::ER_MEMORY - Malloc failure.', + ZipArchive::ER_NOENT => 'ZipArchive::ER_NOENT - No such file.', + ZipArchive::ER_NOZIP => 'ZipArchive::ER_NOZIP - Not a zip archive.', + ZipArchive::ER_OPEN => 'ZipArchive::ER_OPEN - Can\'t open file.', + ZipArchive::ER_READ => 'ZipArchive::ER_READ - Read error.', + ZipArchive::ER_SEEK => 'ZipArchive::ER_SEEK - Seek error.', + default => "An unknown error [$resultCode] has occurred.", + }; + } +} diff --git a/catch/src/Support/Zip/Zipper.php b/catch/src/Support/Zip/Zipper.php new file mode 100644 index 0000000..53b5bc1 --- /dev/null +++ b/catch/src/Support/Zip/Zipper.php @@ -0,0 +1,626 @@ +file = $fs ? $fs : new Filesystem(); + } + + /** + * Destructor + */ + public function __destruct() + { + if (is_object($this->repository)) { + $this->repository->close(); + } + } + + /** + * Create a new zip Archive if the file does not exists + * opens a zip archive if the file exists + * + * @param $pathToFile string The file to open + * @return $this Zipper instance + * @throws Exception + */ + public function make(string $pathToFile): Zipper + { + $new = $this->createArchiveFile($pathToFile); + + $this->repository = new ZipRepository($pathToFile, $new); + + $this->filePath = $pathToFile; + + return $this; + } + + /** + * Create a new zip archive or open an existing one + * + * @param $pathToFile + * + * @return $this + * @throws Exception + * + */ + public function zip($pathToFile): Zipper + { + $this->make($pathToFile); + + return $this; + } + + /** + * Create a new phar file or open one + * + * @param $pathToFile + * + * @return $this + * @throws Exception + * + */ + public function phar($pathToFile): Zipper + { + $this->make($pathToFile, 'phar'); + + return $this; + } + + /** + * Create a new rar file or open one + * + * @param $pathToFile + * + * @return $this + * @throws Exception + * + */ + public function rar($pathToFile): Zipper + { + $this->make($pathToFile, 'rar'); + + return $this; + } + + /** + * Extracts the opened zip archive to the specified location
+ * you can provide an array of files and folders and define if they should be a white list + * or a black list to extract. By default this method compares file names using "string starts with" logic + * + * @param $path string The path to extract to + * @param array $files An array of files + * @param int $methodFlags The Method the files should be treated + * + * @throws Exception + */ + public function extractTo(string $path, array $files = [], int $methodFlags = self::BLACKLIST): void + { + if (! $this->file->exists($path) && ! $this->file->makeDirectory($path, 0755, true)) { + throw new RuntimeException('Failed to create folder'); + } + + if ($methodFlags & self::EXACT_MATCH) { + $matchingMethod = function ($haystack) use ($files) { + return in_array($haystack, $files, true); + }; + } else { + $matchingMethod = function ($haystack) use ($files) { + return Str::startsWith($haystack, $files); + }; + } + + if ($methodFlags & self::WHITELIST) { + $this->extractFilesInternal($path, $matchingMethod); + } else { + // blacklist - extract files that do not match with $matchingMethod + $this->extractFilesInternal($path, function ($filename) use ($matchingMethod) { + return ! $matchingMethod($filename); + }); + } + } + + /** + * Extracts matching files/folders from the opened zip archive to the specified location. + * + * @param string $extractToPath The path to extract to + * @param string $regex regular expression used to match files. See @link http://php.net/manual/en/reference.pcre.pattern.syntax.php + * + * @throws InvalidArgumentException + * @throws RuntimeException + */ + public function extractMatchingRegex(string $extractToPath, string $regex): void + { + if (empty($regex)) { + throw new InvalidArgumentException('Missing pass valid regex parameter'); + } + + $this->extractFilesInternal($extractToPath, function ($filename) use ($regex) { + $match = preg_match($regex, $filename); + if ($match === 1) { + return true; + } elseif ($match === false) { + //invalid pattern for preg_match raises E_WARNING and returns FALSE + //so if you have custom error_handler set to catch and throw E_WARNINGs you never end up here + //but if you have not - this will throw exception + throw new RuntimeException("regular expression match on '$filename' failed with error. Please check if pattern is valid regular expression."); + } + + return false; + }); + } + + /** + * Gets the content of a single file if available + * + * @param $filePath string The full path (including all folders) of the file in the zip + * + * @return string returns the content or throws an exception + * @throws Exception + * + */ + public function getFileContent(string $filePath): string + { + if ($this->repository->fileExists($filePath) === false) { + throw new Exception(sprintf('The file "%s" cannot be found', $filePath)); + } + return $this->repository->getFileContent($filePath); + } + + /** + * Add one or multiple files to the zip. + * + * @param $pathToAdd array|string An array or string of files and folders to add + * @param mixed|null $fileName + * + * @return $this Zipper instance + */ + public function add(array|string $pathToAdd, mixed $fileName = null): Zipper + { + if (is_array($pathToAdd)) { + foreach ($pathToAdd as $key => $dir) { + if (! is_int($key)) { + $this->add($dir, $key); + } else { + $this->add($dir); + } + } + } elseif ($this->file->isFile($pathToAdd)) { + if ($fileName) { + $this->addFile($pathToAdd, $fileName); + } else { + $this->addFile($pathToAdd); + } + } else { + $this->addDir($pathToAdd); + } + + return $this; + } + + /** + * Add an empty directory + * + * @param $dirName + * + * @return Zipper + */ + public function addEmptyDir($dirName): Zipper + { + $this->repository->addEmptyDir($dirName); + + return $this; + } + + /** + * Add a file to the zip using its contents + * + * @param $filename string The name of the file to create + * @param $content string The file contents + * + * @return $this Zipper instance + */ + public function addString(string $filename, string $content): Zipper + { + $this->addFromString($filename, $content); + + return $this; + } + + /** + * Gets the status of the zip. + * + * @return string The status of the internal zip file + */ + public function getStatus(): string + { + return $this->repository->getStatus(); + } + + /** + * Remove a file or array of files and folders from the zip archive + * + * @param $fileToRemove array|string The path/array to the files in the zip + * + * @return $this Zipper instance + */ + public function remove(array|string $fileToRemove): Zipper + { + if (is_array($fileToRemove)) { + $self = $this; + $this->repository->each(function ($file) use ($fileToRemove, $self) { + if (Str::startsWith($file, $fileToRemove)) { + $self->getRepository()->removeFile($file); + } + }); + } else { + $this->repository->removeFile($fileToRemove); + } + + return $this; + } + + /** + * Returns the path of the current zip file if there is one. + * + * @return string The path to the file + */ + public function getFilePath(): string + { + return $this->filePath; + } + + /** + * Sets the password to be used for decompressing + * + * @param $password + * + * @return bool + */ + public function usePassword($password): bool + { + return $this->repository->usePassword($password); + } + + /** + * Closes the zip file and frees all handles + */ + public function close(): void + { + if (null !== $this->repository) { + $this->repository->close(); + } + $this->filePath = ''; + } + + /** + * Sets the internal folder to the given path.
+ * Useful for extracting only a segment of a zip file. + * + * @param string $path + * + * @return $this + */ + public function folder(string $path): Zipper + { + $this->currentFolder = $path; + + return $this; + } + + /** + * Resets the internal folder to the root of the zip file. + * + * @return $this + */ + public function home(): Zipper + { + $this->currentFolder = ''; + + return $this; + } + + /** + * Deletes the archive file + */ + public function delete(): void + { + if (null !== $this->repository) { + $this->repository->close(); + } + + $this->file->delete($this->filePath); + $this->filePath = ''; + } + + /** + * Get the type of the Archive + * + * @return string + */ + public function getArchiveType(): string + { + return get_class($this->repository); + } + + /** + * Get the current internal folder pointer + * + * @return string + */ + public function getCurrentFolderPath(): string + { + return $this->currentFolder; + } + + /** + * Checks if a file is present in the archive + * + * @param $fileInArchive + * + * @return bool + */ + public function contains($fileInArchive): bool + { + return $this->repository->fileExists($fileInArchive); + } + + /** + * @return ZipRepository + */ + public function getRepository(): ZipRepository + { + return $this->repository; + } + + /** + * @return Filesystem + */ + public function getFileHandler(): Filesystem + { + return $this->file; + } + + /** + * Gets the path to the internal folder + * + * @return string + */ + public function getInternalPath(): string + { + return empty($this->currentFolder) ? '' : $this->currentFolder.'/'; + } + + /** + * List all files that are within the archive + * + * @param string|null $regexFilter regular expression to filter returned files/folders. See @link http://php.net/manual/en/reference.pcre.pattern.syntax.php + * + * @throws RuntimeException + * + * @return array + */ + public function listFiles(string $regexFilter = null): array + { + $filesList = []; + if ($regexFilter) { + $filter = function ($file) use (&$filesList, $regexFilter) { + // push/pop an error handler here to to make sure no error/exception thrown if $expected is not a regex + set_error_handler(function () { + }); + $match = preg_match($regexFilter, $file); + restore_error_handler(); + + if ($match === 1) { + $filesList[] = $file; + } elseif ($match === false) { + throw new RuntimeException("regular expression match on '$file' failed with error. Please check if pattern is valid regular expression."); + } + }; + } else { + $filter = function ($file) use (&$filesList) { + $filesList[] = $file; + }; + } + $this->repository->each($filter); + + return $filesList; + } + + private function getCurrentFolderWithTrailingSlash(): string + { + if (empty($this->currentFolder)) { + return ''; + } + + $lastChar = mb_substr($this->currentFolder, -1); + if ($lastChar !== '/' || $lastChar !== '\\') { + return $this->currentFolder.'/'; + } + + return $this->currentFolder; + } + + //---------------------PRIVATE FUNCTIONS------------- + + /** + * @param $pathToZip + * + * @return bool + * @throws Exception + * + */ + private function createArchiveFile($pathToZip): bool + { + if (! $this->file->exists($pathToZip)) { + $dirname = dirname($pathToZip); + if (! $this->file->exists($dirname) && ! $this->file->makeDirectory($dirname, 0755, true)) { + throw new RuntimeException('Failed to create folder'); + } elseif (! $this->file->isWritable($dirname)) { + throw new Exception(sprintf('The path "%s" is not writeable', $pathToZip)); + } + + return true; + } + + return false; + } + + /** + * @param $pathToDir + */ + private function addDir($pathToDir): void + { + // First go over the files in this directory and add them to the repository. + foreach ($this->file->files($pathToDir) as $file) { + $this->addFile($pathToDir.'/'.basename($file)); + } + + // Now let's visit the subdirectories and add them, too. + foreach ($this->file->directories($pathToDir) as $dir) { + $old_folder = $this->currentFolder; + $this->currentFolder = empty($this->currentFolder) ? basename($dir) : $this->currentFolder.'/'.basename($dir); + $this->addDir($pathToDir.'/'.basename($dir)); + $this->currentFolder = $old_folder; + } + } + + /** + * Add the file to the zip + * + * @param string $pathToAdd + * @param string|null $fileName + */ + private function addFile(string $pathToAdd, string $fileName = null): void + { + if (! $fileName) { + $info = pathinfo($pathToAdd); + $fileName = isset($info['extension']) ? + $info['filename'].'.'.$info['extension'] : + $info['filename']; + } + + $this->repository->addFile($pathToAdd, $this->getInternalPath().$fileName); + } + + /** + * Add the file to the zip from content + * + * @param $filename + * @param $content + */ + private function addFromString($filename, $content): void + { + $this->repository->addFromString($this->getInternalPath().$filename, $content); + } + + private function extractFilesInternal($path, callable $matchingMethod): void + { + $self = $this; + $this->repository->each(function ($fileName) use ($path, $matchingMethod, $self) { + $currentPath = $self->getCurrentFolderWithTrailingSlash(); + if (! empty($currentPath) && ! Str::startsWith($fileName, $currentPath)) { + return; + } + + $filename = str_replace($self->getInternalPath(), '', $fileName); + if ($matchingMethod($filename)) { + $self->extractOneFileInternal($fileName, $path); + } + }); + } + + /** + * @param $fileName + * @param $path + * + * @throws RuntimeException + */ + private function extractOneFileInternal($fileName, $path): void + { + $tmpPath = str_replace($this->getInternalPath(), '', $fileName); + + //Prevent Zip traversal attacks + if (str_contains($fileName, '../') || str_contains($fileName, '..\\')) { + throw new RuntimeException('Special characters found within filenames'); + } + + // We need to create the directory first in case it doesn't exist + $dir = pathinfo($path.DIRECTORY_SEPARATOR.$tmpPath, PATHINFO_DIRNAME); + if (! $this->file->exists($dir) && ! $this->file->makeDirectory($dir, 0755, true, true)) { + throw new RuntimeException('Failed to create folders'); + } + + $toPath = $path.DIRECTORY_SEPARATOR.$tmpPath; + $fileStream = $this->getRepository()->getFileStream($fileName); + $this->getFileHandler()->put($toPath, $fileStream); + } +} diff --git a/catch/src/Support/helpers.php b/catch/src/Support/helpers.php new file mode 100644 index 0000000..e766fd6 --- /dev/null +++ b/catch/src/Support/helpers.php @@ -0,0 +1,122 @@ +unique()->filter(function ($path) { + return is_dir($path); + }); + + if ($paths->isEmpty()) { + return; + } + + foreach ((new Finder())->in($paths->toArray())->files() as $command) { + $command = $namespace.str_replace(['/', '.php'], ['\\', ''], Str::after($command->getRealPath(), $searchPath)); + + if (is_subclass_of($command, Command::class) && + ! (new ReflectionClass($command))->isAbstract()) { + Artisan::starting(function ($artisan) use ($command) { + $artisan->resolve($command); + }); + } + } + } +} + +/** + * table prefix + */ +if (! function_exists('withTablePrefix')) { + function withTablePrefix(string $table): string + { + return DB::connection()->getTablePrefix().$table; + } +} + +/** + * get guard name + */ +if (! function_exists('getGuardName')) { + function getGuardName(): string + { + $guardKeys = array_keys(config('catch.auth.guards')); + + if (count($guardKeys)) { + return $guardKeys[0]; + } + + return 'admin'; + } +} + +/** + * get table columns + */ +if (! function_exists('getTableColumns')) { + function getTableColumns(string $table): array + { + $SQL = 'desc '.withTablePrefix($table); + + $columns = []; + + foreach (DB::select($SQL) as $column) { + $columns[] = $column->Field; + } + + return $columns; + } +} + +if (! function_exists('dd_')) { + /** + * @param mixed ...$vars + * @return never + */ + function dd_(...$vars): never + { + if (! in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) && ! headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + + header('Access-Control-Allow-Origin: *'); + header('Access-Control-Allow-Methods: *'); + header('Access-Control-Allow-Headers: *'); + + foreach ($vars as $v) { + VarDumper::dump($v); + } + + exit(1); + } +} diff --git a/catch/src/Traits/DB/BaseOperate.php b/catch/src/Traits/DB/BaseOperate.php new file mode 100644 index 0000000..7c29c80 --- /dev/null +++ b/catch/src/Traits/DB/BaseOperate.php @@ -0,0 +1,239 @@ +mergeCasts($this->mergeCasts); + } + + if (property_exists($this, 'mergeHidden')) { + $this->makeHidden($this->mergeHidden); + } + } + + /** + * @return mixed + */ + public function getList(): mixed + { + $queryBuilder = self::query()->select($this->fieldsInList)->quickSearch(); + + if ($this->isPaginate) { + return $queryBuilder->paginate(Request::get('limit', $this->perPage)); + } + + return $queryBuilder->get(); + } + + /** + * save + * + * @param array $data + * @return false|mixed + */ + public function storeBy(array $data): mixed + { + if ($this->fill($this->filterData($data))->save()) { + return $this->getKey(); + } + + return false; + } + + /** + * create + * + * @param array $data + * @return false|mixed + */ + public function createBy(array $data): mixed + { + $model = $this->newInstance(); + + if ($model->fill($this->filterData($data))->save()) { + return $model->getKey(); + } + + return false; + } + + /** + * update + * + * @param $id + * @param array $data + * @return mixed + */ + public function updateBy($id, array $data): mixed + { + return $this->where($this->getKeyName(), $id)->update($this->filterData($data)); + } + + /** + * filter data/ remove null && empty string + * + * @param array $data + * @return array + */ + protected function filterData(array $data): array + { + // 表单保存的数据集合 + $form = property_exists($this, 'form') ? $this->form : []; + + foreach ($data as $k => $val) { + if (is_null($val) || (is_string($val) && ! $val)) { + unset($data[$k]); + } + + if (! empty($form) && ! in_array($k, $form)) { + unset($data[$k]); + } + } + + return $data; + } + + + /** + * get first by ID + * + * @param $id + * @param string[] $columns + * @return ?Model + */ + public function firstBy($id, array $columns = ['*']): ?Model + { + return static::where($this->getKeyName(), $id)->first($columns); + } + + /** + * delete model + * + * @param $id + * @param bool $force + * @return bool|null + */ + public function deleteBy($id, bool $force = false): ?bool + { + /* @var Model $model */ + $model = self::find($id); + + if ($force) { + return $model->forceDelete(); + } + + return $model->delete(); + } + + /** + * disable or enable + * + * @param $id + * @param string $field + * @return bool + */ + public function disOrEnable($id, string $field = 'status'): bool + { + $model = self::firstBy($id); + + $model->{$field} = $model->{$field} == Status::Enable->value() ? Status::Disable->value() : Status::Enable->value(); + + return $model->save(); + } + + /** + * alias field + * + * @param string|array $fields + * @return string|array + */ + public function aliasField(string|array $fields): string|array + { + $table = $this->getTable(); + + if (is_string($fields)) { + return "{$table}.{$fields}"; + } + + foreach ($fields as &$field) { + $field = "{$table}.{$field}"; + } + + return $fields; + } + + + /** + * get updated at column + * + * @return string|null + */ + public function getUpdatedAtColumn(): ?string + { + $updatedAtColumn = parent::getUpdatedAtColumn(); + + if (! in_array(parent::getUpdatedAtColumn(), $this->getFillable())) { + $updatedAtColumn = null; + } + + return $updatedAtColumn; + } + + /** + * get created at column + * + * @return string|null + */ + public function getCreatedAtColumn(): ?string + { + $createdAtColumn = parent::getCreatedAtColumn(); + + if (! in_array(parent::getUpdatedAtColumn(), $this->getFillable())) { + $createdAtColumn = null; + } + + return $createdAtColumn; + } + + /** + * whit form data + * + * @return $this + */ + public function withoutForm(): static + { + if (property_exists($this, 'form') && ! empty($this->form)) { + $this->form = []; + } + + return $this; + } +} diff --git a/catch/src/Traits/DB/ScopeTrait.php b/catch/src/Traits/DB/ScopeTrait.php new file mode 100644 index 0000000..50c83a0 --- /dev/null +++ b/catch/src/Traits/DB/ScopeTrait.php @@ -0,0 +1,20 @@ +2.2,<2.4" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/coding-standard": "^9", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^4.4 || ^5.4 || ^6", + "symfony/var-exporter": "^4.4 || ^5.4 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "homepage": "https://www.doctrine-project.org/projects/cache.html", + "keywords": [ + "abstraction", + "apcu", + "cache", + "caching", + "couchdb", + "memcached", + "php", + "redis", + "xcache" + ], + "support": { + "issues": "https://github.com/doctrine/cache/issues", + "source": "https://github.com/doctrine/cache/tree/2.2.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", + "type": "tidelift" + } + ], + "time": "2022-05-20T20:07:39+00:00" + }, + { + "name": "doctrine/dbal", + "version": "3.5.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "f38ee8aaca2d58ee88653cb34a6a3880c23f38a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/f38ee8aaca2d58ee88653cb34a6a3880c23f38a5", + "reference": "f38ee8aaca2d58ee88653cb34a6a3880c23f38a5", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/cache": "^1.11|^2.0", + "doctrine/deprecations": "^0.5.3|^1", + "doctrine/event-manager": "^1|^2", + "php": "^7.4 || ^8.0", + "psr/cache": "^1|^2|^3", + "psr/log": "^1|^2|^3" + }, + "require-dev": { + "doctrine/coding-standard": "10.0.0", + "jetbrains/phpstorm-stubs": "2022.2", + "phpstan/phpstan": "1.8.10", + "phpstan/phpstan-strict-rules": "^1.4", + "phpunit/phpunit": "9.5.25", + "psalm/plugin-phpunit": "0.17.0", + "squizlabs/php_codesniffer": "3.7.1", + "symfony/cache": "^5.4|^6.0", + "symfony/console": "^4.4|^5.4|^6.0", + "vimeo/psalm": "4.29.0" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\DBAL\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", + "homepage": "https://www.doctrine-project.org/projects/dbal.html", + "keywords": [ + "abstraction", + "database", + "db2", + "dbal", + "mariadb", + "mssql", + "mysql", + "oci8", + "oracle", + "pdo", + "pgsql", + "postgresql", + "queryobject", + "sasql", + "sql", + "sqlite", + "sqlserver", + "sqlsrv" + ], + "support": { + "issues": "https://github.com/doctrine/dbal/issues", + "source": "https://github.com/doctrine/dbal/tree/3.5.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", + "type": "tidelift" + } + ], + "time": "2022-10-24T07:26:18+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpunit/phpunit": "^7.5|^8.5|^9.5", + "psr/log": "^1|^2|^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/v1.0.0" + }, + "time": "2022-05-02T15:47:09+00:00" + }, + { + "name": "doctrine/event-manager", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/event-manager.git", + "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/750671534e0241a7c50ea5b43f67e23eb5c96f32", + "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "conflict": { + "doctrine/common": "<2.9" + }, + "require-dev": { + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.8.8", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^4.28" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "keywords": [ + "event", + "event dispatcher", + "event manager", + "event system", + "events" + ], + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/2.0.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", + "type": "tidelift" + } + ], + "time": "2022-10-12T20:59:15+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.0.6", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "d9d313a36c872fd6ee06d9a6cbcf713eaa40f024" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/d9d313a36c872fd6ee06d9a6cbcf713eaa40f024", + "reference": "d9d313a36c872fd6ee06d9a6cbcf713eaa40f024", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^8.5 || ^9.5", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "lib/Doctrine/Inflector" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.", + "homepage": "https://www.doctrine-project.org/projects/inflector.html", + "keywords": [ + "inflection", + "inflector", + "lowercase", + "manipulation", + "php", + "plural", + "singular", + "strings", + "uppercase", + "words" + ], + "support": { + "issues": "https://github.com/doctrine/inflector/issues", + "source": "https://github.com/doctrine/inflector/tree/2.0.6" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finflector", + "type": "tidelift" + } + ], + "time": "2022-10-20T09:10:12+00:00" + }, + { + "name": "doctrine/lexer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0", + "phpstan/phpstan": "^1.3", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2022-02-28T11:07:21+00:00" + }, + { + "name": "dragonmantank/cron-expression", + "version": "v3.3.2", + "source": { + "type": "git", + "url": "https://github.com/dragonmantank/cron-expression.git", + "reference": "782ca5968ab8b954773518e9e49a6f892a34b2a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/782ca5968ab8b954773518e9e49a6f892a34b2a8", + "reference": "782ca5968ab8b954773518e9e49a6f892a34b2a8", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0", + "webmozart/assert": "^1.0" + }, + "replace": { + "mtdowling/cron-expression": "^1.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-webmozart-assert": "^1.0", + "phpunit/phpunit": "^7.0|^8.0|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Cron\\": "src/Cron/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Tankersley", + "email": "chris@ctankersley.com", + "homepage": "https://github.com/dragonmantank" + } + ], + "description": "CRON for PHP: Calculate the next or previous run date and determine if a CRON expression is due", + "keywords": [ + "cron", + "schedule" + ], + "support": { + "issues": "https://github.com/dragonmantank/cron-expression/issues", + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.2" + }, + "funding": [ + { + "url": "https://github.com/dragonmantank", + "type": "github" + } + ], + "time": "2022-09-10T18:51:20+00:00" + }, + { + "name": "egulias/email-validator", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "f88dcf4b14af14a98ad96b14b2b317969eab6715" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/f88dcf4b14af14a98ad96b14b2b317969eab6715", + "reference": "f88dcf4b14af14a98ad96b14b2b317969eab6715", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^1.2", + "php": ">=7.2", + "symfony/polyfill-intl-idn": "^1.15" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^8.5.8|^9.3.3", + "vimeo/psalm": "^4" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "support": { + "issues": "https://github.com/egulias/EmailValidator/issues", + "source": "https://github.com/egulias/EmailValidator/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2022-06-18T20:57:19+00:00" + }, + { + "name": "fruitcake/php-cors", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/fruitcake/php-cors.git", + "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/58571acbaa5f9f462c9c77e911700ac66f446d4e", + "reference": "58571acbaa5f9f462c9c77e911700ac66f446d4e", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "symfony/http-foundation": "^4.4|^5.4|^6" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Fruitcake\\Cors\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fruitcake", + "homepage": "https://fruitcake.nl" + }, + { + "name": "Barryvdh", + "email": "barryvdh@gmail.com" + } + ], + "description": "Cross-origin resource sharing library for the Symfony HttpFoundation", + "homepage": "https://github.com/fruitcake/php-cors", + "keywords": [ + "cors", + "laravel", + "symfony" + ], + "support": { + "issues": "https://github.com/fruitcake/php-cors/issues", + "source": "https://github.com/fruitcake/php-cors/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2022-02-20T15:07:15+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/a878d45c1914464426dc94da61c9e1d36ae262a8", + "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.28 || ^9.5.21" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2022-07-30T15:56:11+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.5.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5", + "guzzlehttp/psr7": "^1.9 || ^2.4", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "7.5-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.5.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2022-08-28T15:39:27+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.5.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "b94b2807d85443f9719887892882d0329d1e2598" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598", + "reference": "b94b2807d85443f9719887892882d0329d1e2598", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.5.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2022-08-28T14:55:35+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.4.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "67c26b443f348a51926030c83481b85718457d3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/67c26b443f348a51926030c83481b85718457d3d", + "reference": "67c26b443f348a51926030c83481b85718457d3d", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.4.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2022-10-26T14:07:24+00:00" + }, + { + "name": "laravel/framework", + "version": "v9.41.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "cc902ce61b4ca08ca7449664cfab2fa96a1d1e28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/cc902ce61b4ca08ca7449664cfab2fa96a1d1e28", + "reference": "cc902ce61b4ca08ca7449664cfab2fa96a1d1e28", + "shasum": "" + }, + "require": { + "doctrine/inflector": "^2.0", + "dragonmantank/cron-expression": "^3.3.2", + "egulias/email-validator": "^3.2.1", + "ext-mbstring": "*", + "ext-openssl": "*", + "fruitcake/php-cors": "^1.2", + "laravel/serializable-closure": "^1.2.2", + "league/commonmark": "^2.2", + "league/flysystem": "^3.8.0", + "monolog/monolog": "^2.0", + "nesbot/carbon": "^2.62.1", + "nunomaduro/termwind": "^1.13", + "php": "^8.0.2", + "psr/container": "^1.1.1|^2.0.1", + "psr/log": "^1.0|^2.0|^3.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "ramsey/uuid": "^4.2.2", + "symfony/console": "^6.0.9", + "symfony/error-handler": "^6.0", + "symfony/finder": "^6.0", + "symfony/http-foundation": "^6.0", + "symfony/http-kernel": "^6.0", + "symfony/mailer": "^6.0", + "symfony/mime": "^6.0", + "symfony/process": "^6.0", + "symfony/routing": "^6.0", + "symfony/uid": "^6.0", + "symfony/var-dumper": "^6.0", + "tijsverkoyen/css-to-inline-styles": "^2.2.5", + "vlucas/phpdotenv": "^5.4.1", + "voku/portable-ascii": "^2.0" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "provide": { + "psr/container-implementation": "1.1|2.0", + "psr/simple-cache-implementation": "1.0|2.0|3.0" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/broadcasting": "self.version", + "illuminate/bus": "self.version", + "illuminate/cache": "self.version", + "illuminate/collections": "self.version", + "illuminate/conditionable": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/contracts": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/hashing": "self.version", + "illuminate/http": "self.version", + "illuminate/log": "self.version", + "illuminate/macroable": "self.version", + "illuminate/mail": "self.version", + "illuminate/notifications": "self.version", + "illuminate/pagination": "self.version", + "illuminate/pipeline": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/testing": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version" + }, + "require-dev": { + "ably/ably-php": "^1.0", + "aws/aws-sdk-php": "^3.235.5", + "doctrine/dbal": "^2.13.3|^3.1.4", + "fakerphp/faker": "^1.9.2", + "guzzlehttp/guzzle": "^7.5", + "league/flysystem-aws-s3-v3": "^3.0", + "league/flysystem-ftp": "^3.0", + "league/flysystem-path-prefixing": "^3.3", + "league/flysystem-read-only": "^3.3", + "league/flysystem-sftp-v3": "^3.0", + "mockery/mockery": "^1.5.1", + "orchestra/testbench-core": "^7.11", + "pda/pheanstalk": "^4.0", + "phpstan/phpstan": "^1.4.7", + "phpunit/phpunit": "^9.5.8", + "predis/predis": "^1.1.9|^2.0.2", + "symfony/cache": "^6.0" + }, + "suggest": { + "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.235.5).", + "brianium/paratest": "Required to run tests in parallel (^6.0).", + "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.13.3|^3.1.4).", + "ext-bcmath": "Required to use the multiple_of validation rule.", + "ext-ftp": "Required to use the Flysystem FTP driver.", + "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", + "ext-memcached": "Required to use the memcache cache driver.", + "ext-pcntl": "Required to use all features of the queue worker.", + "ext-posix": "Required to use all features of the queue worker.", + "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", + "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", + "filp/whoops": "Required for friendly error pages in development (^2.14.3).", + "guzzlehttp/guzzle": "Required to use the HTTP Client and the ping methods on schedules (^7.5).", + "laravel/tinker": "Required to use the tinker console command (^2.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.0).", + "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.0).", + "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.3).", + "league/flysystem-read-only": "Required to use read-only disks (^3.3)", + "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.0).", + "mockery/mockery": "Required to use mocking (^1.5.1).", + "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", + "phpunit/phpunit": "Required to use assertions and run tests (^9.5.8).", + "predis/predis": "Required to use the predis connector (^1.1.9|^2.0.2).", + "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^6.0).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^6.0).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^6.0).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^6.0).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^6.0).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.x-dev" + } + }, + "autoload": { + "files": [ + "src/Illuminate/Collections/helpers.php", + "src/Illuminate/Events/functions.php", + "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Support/helpers.php" + ], + "psr-4": { + "Illuminate\\": "src/Illuminate/", + "Illuminate\\Support\\": [ + "src/Illuminate/Macroable/", + "src/Illuminate/Collections/", + "src/Illuminate/Conditionable/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Laravel Framework.", + "homepage": "https://laravel.com", + "keywords": [ + "framework", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2022-11-22T15:10:46+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v1.2.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "47afb7fae28ed29057fdca37e16a84f90cc62fae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/47afb7fae28ed29057fdca37e16a84f90cc62fae", + "reference": "47afb7fae28ed29057fdca37e16a84f90cc62fae", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "nesbot/carbon": "^2.61", + "pestphp/pest": "^1.21.3", + "phpstan/phpstan": "^1.8.2", + "symfony/var-dumper": "^5.4.11" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\SerializableClosure\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "nuno@laravel.com" + } + ], + "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.", + "keywords": [ + "closure", + "laravel", + "serializable" + ], + "support": { + "issues": "https://github.com/laravel/serializable-closure/issues", + "source": "https://github.com/laravel/serializable-closure" + }, + "time": "2022-09-08T13:45:54+00:00" + }, + { + "name": "laravel/tinker", + "version": "v2.7.3", + "source": { + "type": "git", + "url": "https://github.com/laravel/tinker.git", + "reference": "5062061b4924af3392225dd482ca7b4d85d8b8ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/tinker/zipball/5062061b4924af3392225dd482ca7b4d85d8b8ef", + "reference": "5062061b4924af3392225dd482ca7b4d85d8b8ef", + "shasum": "" + }, + "require": { + "illuminate/console": "^6.0|^7.0|^8.0|^9.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0", + "php": "^7.2.5|^8.0", + "psy/psysh": "^0.10.4|^0.11.1", + "symfony/var-dumper": "^4.3.4|^5.0|^6.0" + }, + "require-dev": { + "mockery/mockery": "~1.3.3|^1.4.2", + "phpunit/phpunit": "^8.5.8|^9.3.3" + }, + "suggest": { + "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Tinker\\TinkerServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Tinker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Powerful REPL for the Laravel framework.", + "keywords": [ + "REPL", + "Tinker", + "laravel", + "psysh" + ], + "support": { + "issues": "https://github.com/laravel/tinker/issues", + "source": "https://github.com/laravel/tinker/tree/v2.7.3" + }, + "time": "2022-11-09T15:11:38+00:00" + }, + { + "name": "lcobucci/clock", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/clock.git", + "reference": "fb533e093fd61321bfcbac08b131ce805fe183d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/fb533e093fd61321bfcbac08b131ce805fe183d3", + "reference": "fb533e093fd61321bfcbac08b131ce805fe183d3", + "shasum": "" + }, + "require": { + "php": "^8.0", + "stella-maris/clock": "^0.1.4" + }, + "require-dev": { + "infection/infection": "^0.26", + "lcobucci/coding-standard": "^8.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-deprecation-rules": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpstan/phpstan-strict-rules": "^0.12", + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lcobucci\\Clock\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com" + } + ], + "description": "Yet another clock abstraction", + "support": { + "issues": "https://github.com/lcobucci/clock/issues", + "source": "https://github.com/lcobucci/clock/tree/2.2.0" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2022-04-19T19:34:17+00:00" + }, + { + "name": "lcobucci/jwt", + "version": "4.2.1", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/jwt.git", + "reference": "72ac6d807ee51a70ad376ee03a2387e8646e10f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/72ac6d807ee51a70ad376ee03a2387e8646e10f3", + "reference": "72ac6d807ee51a70ad376ee03a2387e8646e10f3", + "shasum": "" + }, + "require": { + "ext-hash": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-sodium": "*", + "lcobucci/clock": "^2.0", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "infection/infection": "^0.21", + "lcobucci/coding-standard": "^6.0", + "mikey179/vfsstream": "^1.6.7", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/php-invoker": "^3.1", + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Lcobucci\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Luís Cobucci", + "email": "lcobucci@gmail.com", + "role": "Developer" + } + ], + "description": "A simple library to work with JSON Web Token and JSON Web Signature", + "keywords": [ + "JWS", + "jwt" + ], + "support": { + "issues": "https://github.com/lcobucci/jwt/issues", + "source": "https://github.com/lcobucci/jwt/tree/4.2.1" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2022-08-19T23:14:07+00:00" + }, + { + "name": "league/commonmark", + "version": "2.3.7", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "a36bd2be4f5387c0f3a8792a0d76b7d68865abbf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/a36bd2be4f5387c0f3a8792a0d76b7d68865abbf", + "reference": "a36bd2be4f5387c0f3a8792a0d76b7d68865abbf", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.30.0", + "commonmark/commonmark.js": "0.30.0", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "github/gfm": "0.29.0", + "michelf/php-markdown": "^1.4 || ^2.0", + "nyholm/psr7": "^1.5", + "phpstan/phpstan": "^1.8.2", + "phpunit/phpunit": "^9.5.21", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "support": { + "docs": "https://commonmark.thephpleague.com/", + "forum": "https://github.com/thephpleague/commonmark/discussions", + "issues": "https://github.com/thephpleague/commonmark/issues", + "rss": "https://github.com/thephpleague/commonmark/releases.atom", + "source": "https://github.com/thephpleague/commonmark" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2022-11-03T17:29:46+00:00" + }, + { + "name": "league/config", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.90", + "phpunit/phpunit": "^9.5.5", + "scrutinizer/ocular": "^1.8.1", + "unleashedtech/php-coding-standard": "^3.1", + "vimeo/psalm": "^4.7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "support": { + "docs": "https://config.thephpleague.com/", + "issues": "https://github.com/thephpleague/config/issues", + "rss": "https://github.com/thephpleague/config/releases.atom", + "source": "https://github.com/thephpleague/config" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2021-08-14T12:15:32+00:00" + }, + { + "name": "league/flysystem", + "version": "3.10.4", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "a7790f3dd1b27af81d380e6b2afa77c16ab7e181" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a7790f3dd1b27af81d380e6b2afa77c16ab7e181", + "reference": "a7790f3dd1b27af81d380e6b2afa77c16ab7e181", + "shasum": "" + }, + "require": { + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "aws/aws-sdk-php": "3.209.31 || 3.210.0", + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1", + "phpseclib/phpseclib": "3.0.15", + "symfony/http-client": "<5.2" + }, + "require-dev": { + "async-aws/s3": "^1.5", + "async-aws/simple-s3": "^1.1", + "aws/aws-sdk-php": "^3.198.1", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "microsoft/azure-storage-blob": "^1.1", + "phpseclib/phpseclib": "^3.0.14", + "phpstan/phpstan": "^0.12.26", + "phpunit/phpunit": "^9.5.11", + "sabre/dav": "^4.3.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "File storage abstraction for PHP", + "keywords": [ + "WebDAV", + "aws", + "cloud", + "file", + "files", + "filesystem", + "filesystems", + "ftp", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/3.10.4" + }, + "funding": [ + { + "url": "https://ecologi.com/frankdejonge", + "type": "custom" + }, + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2022-11-26T19:48:01+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.11.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2022-04-17T13:12:02+00:00" + }, + { + "name": "monolog/monolog", + "version": "2.8.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "720488632c590286b88b80e62aa3d3d551ad4a50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/720488632c590286b88b80e62aa3d3d551ad4a50", + "reference": "720488632c590286b88b80e62aa3d3d551ad4a50", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2", + "guzzlehttp/guzzle": "^7.4", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "phpspec/prophecy": "^1.15", + "phpstan/phpstan": "^0.12.91", + "phpunit/phpunit": "^8.5.14", + "predis/predis": "^1.1 || ^2.0", + "rollbar/rollbar": "^1.3 || ^2 || ^3", + "ruflin/elastica": "^7", + "swiftmailer/swiftmailer": "^5.3|^6.0", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/2.8.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2022-07-24T11:55:47+00:00" + }, + { + "name": "namshi/jose", + "version": "7.2.3", + "source": { + "type": "git", + "url": "https://github.com/namshi/jose.git", + "reference": "89a24d7eb3040e285dd5925fcad992378b82bcff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/namshi/jose/zipball/89a24d7eb3040e285dd5925fcad992378b82bcff", + "reference": "89a24d7eb3040e285dd5925fcad992378b82bcff", + "shasum": "" + }, + "require": { + "ext-date": "*", + "ext-hash": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-spl": "*", + "php": ">=5.5", + "symfony/polyfill-php56": "^1.0" + }, + "require-dev": { + "phpseclib/phpseclib": "^2.0", + "phpunit/phpunit": "^4.5|^5.0", + "satooshi/php-coveralls": "^1.0" + }, + "suggest": { + "ext-openssl": "Allows to use OpenSSL as crypto engine.", + "phpseclib/phpseclib": "Allows to use Phpseclib as crypto engine, use version ^2.0." + }, + "type": "library", + "autoload": { + "psr-4": { + "Namshi\\JOSE\\": "src/Namshi/JOSE/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Nadalin", + "email": "alessandro.nadalin@gmail.com" + }, + { + "name": "Alessandro Cinelli (cirpo)", + "email": "alessandro.cinelli@gmail.com" + } + ], + "description": "JSON Object Signing and Encryption library for PHP.", + "keywords": [ + "JSON Web Signature", + "JSON Web Token", + "JWS", + "json", + "jwt", + "token" + ], + "support": { + "issues": "https://github.com/namshi/jose/issues", + "source": "https://github.com/namshi/jose/tree/master" + }, + "time": "2016-12-05T07:27:31+00:00" + }, + { + "name": "nesbot/carbon", + "version": "2.63.0", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "ad35dd71a6a212b98e4b87e97389b6fa85f0e347" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/ad35dd71a6a212b98e4b87e97389b6fa85f0e347", + "reference": "ad35dd71a6a212b98e4b87e97389b6fa85f0e347", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^7.1.8 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php80": "^1.16", + "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0" + }, + "require-dev": { + "doctrine/dbal": "^2.0 || ^3.0", + "doctrine/orm": "^2.7", + "friendsofphp/php-cs-fixer": "^3.0", + "kylekatarnls/multi-tester": "^2.0", + "ondrejmirtes/better-reflection": "*", + "phpmd/phpmd": "^2.9", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12.99 || ^1.7.14", + "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6", + "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20", + "squizlabs/php_codesniffer": "^3.4" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-3.x": "3.x-dev", + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "https://markido.com" + }, + { + "name": "kylekatarnls", + "homepage": "https://github.com/kylekatarnls" + } + ], + "description": "An API extension for DateTime that supports 281 different languages.", + "homepage": "https://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbon.nesbot.com/docs", + "issues": "https://github.com/briannesbitt/Carbon/issues", + "source": "https://github.com/briannesbitt/Carbon" + }, + "funding": [ + { + "url": "https://github.com/sponsors/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon#sponsor", + "type": "opencollective" + }, + { + "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme", + "type": "tidelift" + } + ], + "time": "2022-10-30T18:34:28+00:00" + }, + { + "name": "nette/schema", + "version": "v1.2.3", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/abbdbb70e0245d5f3bf77874cea1dfb0c930d06f", + "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f", + "shasum": "" + }, + "require": { + "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", + "php": ">=7.1 <8.3" + }, + "require-dev": { + "nette/tester": "^2.3 || ^2.4", + "phpstan/phpstan-nette": "^1.0", + "tracy/tracy": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.2.3" + }, + "time": "2022-10-13T01:24:26+00:00" + }, + { + "name": "nette/utils", + "version": "v3.2.8", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "02a54c4c872b99e4ec05c4aec54b5a06eb0f6368" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/02a54c4c872b99e4ec05c4aec54b5a06eb0f6368", + "reference": "02a54c4c872b99e4ec05c4aec54b5a06eb0f6368", + "shasum": "" + }, + "require": { + "php": ">=7.2 <8.3" + }, + "conflict": { + "nette/di": "<3.0.6" + }, + "require-dev": { + "nette/tester": "~2.0", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v3.2.8" + }, + "time": "2022-09-12T23:36:20+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v4.15.2", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc", + "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2" + }, + "time": "2022-11-12T15:38:23+00:00" + }, + { + "name": "nunomaduro/termwind", + "version": "v1.14.2", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "9a8218511eb1a0965629ff820dda25985440aefc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/9a8218511eb1a0965629ff820dda25985440aefc", + "reference": "9a8218511eb1a0965629ff820dda25985440aefc", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^8.0", + "symfony/console": "^5.3.0|^6.0.0" + }, + "require-dev": { + "ergebnis/phpstan-rules": "^1.0.", + "illuminate/console": "^8.0|^9.0", + "illuminate/support": "^8.0|^9.0", + "laravel/pint": "^1.0.0", + "pestphp/pest": "^1.21.0", + "pestphp/pest-plugin-mock": "^1.0", + "phpstan/phpstan": "^1.4.6", + "phpstan/phpstan-strict-rules": "^1.1.0", + "symfony/var-dumper": "^5.2.7|^6.0.0", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/Functions.php" + ], + "psr-4": { + "Termwind\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Its like Tailwind CSS, but for the console.", + "keywords": [ + "cli", + "console", + "css", + "package", + "php", + "style" + ], + "support": { + "issues": "https://github.com/nunomaduro/termwind/issues", + "source": "https://github.com/nunomaduro/termwind/tree/v1.14.2" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://github.com/xiCO2k", + "type": "github" + } + ], + "time": "2022-10-28T22:51:32+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dc5ff11e274a90cc1c743f66c9ad700ce50db9ab", + "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8", + "phpunit/phpunit": "^8.5.28 || ^9.5.21" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2022-07-30T15:51:26+00:00" + }, + { + "name": "psr/cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/3.0.0" + }, + "time": "2021-02-03T23:26:27+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "psy/psysh", + "version": "v0.11.9", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "1acec99d6684a54ff92f8b548a4e41b566963778" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/1acec99d6684a54ff92f8b548a4e41b566963778", + "reference": "1acec99d6684a54ff92f8b548a4e41b566963778", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "nikic/php-parser": "^4.0 || ^3.1", + "php": "^8.0 || ^7.0.8", + "symfony/console": "^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^6.0 || ^5.0 || ^4.0 || ^3.4" + }, + "conflict": { + "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.2" + }, + "suggest": { + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-pdo-sqlite": "The doc command requires SQLite to work.", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.", + "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history." + }, + "bin": [ + "bin/psysh" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.11.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Psy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "An interactive shell for modern PHP.", + "homepage": "http://psysh.org", + "keywords": [ + "REPL", + "console", + "interactive", + "shell" + ], + "support": { + "issues": "https://github.com/bobthecow/psysh/issues", + "source": "https://github.com/bobthecow/psysh/tree/v0.11.9" + }, + "time": "2022-11-06T15:29:46+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "ramsey/collection", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/cccc74ee5e328031b15640b51056ee8d3bb66c0a", + "reference": "cccc74ee5e328031b15640b51056ee8d3bb66c0a", + "shasum": "" + }, + "require": { + "php": "^7.3 || ^8", + "symfony/polyfill-php81": "^1.23" + }, + "require-dev": { + "captainhook/captainhook": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "ergebnis/composer-normalize": "^2.6", + "fakerphp/faker": "^1.5", + "hamcrest/hamcrest-php": "^2", + "jangregor/phpstan-prophecy": "^0.8", + "mockery/mockery": "^1.3", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/extension-installer": "^1", + "phpstan/phpstan": "^0.12.32", + "phpstan/phpstan-mockery": "^0.12.5", + "phpstan/phpstan-phpunit": "^0.12.11", + "phpunit/phpunit": "^8.5 || ^9", + "psy/psysh": "^0.10.4", + "slevomat/coding-standard": "^6.3", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/1.2.2" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", + "type": "tidelift" + } + ], + "time": "2021-10-10T03:01:02+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.6.0", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "ad63bc700e7d021039e30ce464eba384c4a1d40f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/ad63bc700e7d021039e30ce464eba384c4a1d40f", + "reference": "ad63bc700e7d021039e30ce464eba384c4a1d40f", + "shasum": "" + }, + "require": { + "brick/math": "^0.8.8 || ^0.9 || ^0.10", + "ext-json": "*", + "php": "^8.0", + "ramsey/collection": "^1.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.10", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.8", + "ergebnis/composer-normalize": "^2.15", + "mockery/mockery": "^1.3", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.2", + "php-mock/php-mock-mockery": "^1.3", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpbench/phpbench": "^1.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^8.5 || ^9", + "ramsey/composer-repl": "^1.4", + "slevomat/coding-standard": "^8.4", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.9" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.6.0" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", + "type": "tidelift" + } + ], + "time": "2022-11-05T23:03:38+00:00" + }, + { + "name": "stella-maris/clock", + "version": "0.1.7", + "source": { + "type": "git", + "url": "https://github.com/stella-maris-solutions/clock.git", + "reference": "fa23ce16019289a18bb3446fdecd45befcdd94f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stella-maris-solutions/clock/zipball/fa23ce16019289a18bb3446fdecd45befcdd94f8", + "reference": "fa23ce16019289a18bb3446fdecd45befcdd94f8", + "shasum": "" + }, + "require": { + "php": "^7.0|^8.0", + "psr/clock": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "StellaMaris\\Clock\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Heigl", + "role": "Maintainer" + } + ], + "description": "A pre-release of the proposed PSR-20 Clock-Interface", + "homepage": "https://gitlab.com/stella-maris/clock", + "keywords": [ + "clock", + "datetime", + "point in time", + "psr20" + ], + "support": { + "source": "https://github.com/stella-maris-solutions/clock/tree/0.1.7" + }, + "time": "2022-11-25T16:15:06+00:00" + }, + { + "name": "symfony/console", + "version": "v6.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "a1282bd0c096e0bdb8800b104177e2ce404d8815" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/a1282bd0c096e0bdb8800b104177e2ce404d8815", + "reference": "a1282bd0c096e0bdb8800b104177e2ce404d8815", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.4|^6.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/lock": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v6.1.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-10-26T21:42:49+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v6.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "0dd5e36b80e1de97f8f74ed7023ac2b837a36443" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/0dd5e36b80e1de97f8f74ed7023ac2b837a36443", + "reference": "0dd5e36b80e1de97f8f74ed7023ac2b837a36443", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v6.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-27T17:24:16+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918", + "reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-02-25T11:15:52+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v6.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "699a26ce5ec656c198bf6e26398b0f0818c7e504" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/699a26ce5ec656c198bf6e26398b0f0818c7e504", + "reference": "699a26ce5ec656c198bf6e26398b0f0818c7e504", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^5.4|^6.0" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/serializer": "^5.4|^6.0" + }, + "bin": [ + "Resources/bin/patch-type-declarations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\ErrorHandler\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to manage errors and ease debugging PHP code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/error-handler/tree/v6.1.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-10-28T16:23:08+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v6.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "a0449a7ad7daa0f7c0acd508259f80544ab5a347" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a0449a7ad7daa0f7c0acd508259f80544ab5a347", + "reference": "a0449a7ad7daa0f7c0acd508259f80544ab5a347", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2|^3" + }, + "conflict": { + "symfony/dependency-injection": "<5.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/error-handler": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/stopwatch": "^5.4|^6.0" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v6.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-05T16:51:07+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "02ff5eea2f453731cfbc6bc215e456b781480448" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/02ff5eea2f453731cfbc6bc215e456b781480448", + "reference": "02ff5eea2f453731cfbc6bc215e456b781480448", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "suggest": { + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-02-25T11:15:52+00:00" + }, + { + "name": "symfony/finder", + "version": "v6.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "39696bff2c2970b3779a5cac7bf9f0b88fc2b709" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/39696bff2c2970b3779a5cac7bf9f0b88fc2b709", + "reference": "39696bff2c2970b3779a5cac7bf9f0b88fc2b709", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v6.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-07-29T07:42:06+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v6.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "792a1856d2b95273f0e1c3435785f1d01a60ecc6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/792a1856d2b95273f0e1c3435785f1d01a60ecc6", + "reference": "792a1856d2b95273f0e1c3435785f1d01a60ecc6", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "predis/predis": "~1.0", + "symfony/cache": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4", + "symfony/mime": "^5.4|^6.0", + "symfony/rate-limiter": "^5.2|^6.0" + }, + "suggest": { + "symfony/mime": "To use the file extension guesser" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v6.1.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-10-12T09:44:59+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v6.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "8fc1ffe753948c47a103a809cdd6a4a8458b3254" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/8fc1ffe753948c47a103a809cdd6a4a8458b3254", + "reference": "8fc1ffe753948c47a103a809cdd6a4a8458b3254", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/error-handler": "^6.1", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/browser-kit": "<5.4", + "symfony/cache": "<5.4", + "symfony/config": "<6.1", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<6.1", + "symfony/doctrine-bridge": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<5.4", + "symfony/mailer": "<5.4", + "symfony/messenger": "<5.4", + "symfony/translation": "<5.4", + "symfony/twig-bridge": "<5.4", + "symfony/validator": "<5.4", + "twig/twig": "<2.13" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^5.4|^6.0", + "symfony/config": "^6.1", + "symfony/console": "^5.4|^6.0", + "symfony/css-selector": "^5.4|^6.0", + "symfony/dependency-injection": "^6.1", + "symfony/dom-crawler": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-client-contracts": "^1.1|^2|^3", + "symfony/process": "^5.4|^6.0", + "symfony/routing": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.0", + "symfony/translation": "^5.4|^6.0", + "symfony/translation-contracts": "^1.1|^2|^3", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpKernel\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a structured process for converting a Request into a Response", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-kernel/tree/v6.1.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-10-28T18:06:36+00:00" + }, + { + "name": "symfony/mailer", + "version": "v6.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailer.git", + "reference": "7e19813c0b43387c55665780c4caea505cc48391" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailer/zipball/7e19813c0b43387c55665780c4caea505cc48391", + "reference": "7e19813c0b43387c55665780c4caea505cc48391", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.1.10|^3", + "php": ">=8.1", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/mime": "^5.4|^6.0", + "symfony/service-contracts": "^1.1|^2|^3" + }, + "conflict": { + "symfony/http-kernel": "<5.4" + }, + "require-dev": { + "symfony/http-client-contracts": "^1.1|^2|^3", + "symfony/messenger": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mailer\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Helps sending emails", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/mailer/tree/v6.1.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-10-28T16:23:08+00:00" + }, + { + "name": "symfony/mime", + "version": "v6.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "f440f066d57691088d998d6e437ce98771144618" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/f440f066d57691088d998d6e437ce98771144618", + "reference": "f440f066d57691088d998d6e437ce98771144618", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/mailer": "<5.4" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/property-access": "^5.4|^6.0", + "symfony/property-info": "^5.4|^6.0", + "symfony/serializer": "^5.2|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows manipulating MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "support": { + "source": "https://github.com/symfony/mime/tree/v6.1.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-10-19T08:10:53+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "511a08c03c1960e08a883f4cffcacd219b758354" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", + "reference": "511a08c03c1960e08a883f4cffcacd219b758354", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "639084e360537a19f9ee352433b84ce831f3d2da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/639084e360537a19f9ee352433b84ce831f3d2da", + "reference": "639084e360537a19f9ee352433b84ce831f3d2da", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-php56", + "version": "v1.20.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php56.git", + "reference": "54b8cd7e6c1643d78d011f3be89f3ef1f9f4c675" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/54b8cd7e6c1643d78d011f3be89f3ef1f9f4c675", + "reference": "54b8cd7e6c1643d78d011f3be89f3ef1f9f4c675", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "metapackage", + "extra": { + "branch-alias": { + "dev-main": "1.20-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php56/tree/v1.20.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-23T14:02:19+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "869329b1e9894268a8a61dabb69153029b7a8c97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97", + "reference": "869329b1e9894268a8a61dabb69153029b7a8c97", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a", + "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "f3cf1a645c2734236ed1e2e671e273eeb3586166" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/f3cf1a645c2734236ed1e2e671e273eeb3586166", + "reference": "f3cf1a645c2734236ed1e2e671e273eeb3586166", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/process", + "version": "v6.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "a6506e99cfad7059b1ab5cab395854a0a0c21292" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/a6506e99cfad7059b1ab5cab395854a0a0c21292", + "reference": "a6506e99cfad7059b1ab5cab395854a0a0c21292", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v6.1.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-27T17:24:16+00:00" + }, + { + "name": "symfony/routing", + "version": "v6.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "95effeb9d6e2cec861cee06bf5bbf82d09aea7f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/95effeb9d6e2cec861cee06bf5bbf82d09aea7f5", + "reference": "95effeb9d6e2cec861cee06bf5bbf82d09aea7f5", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "conflict": { + "doctrine/annotations": "<1.12", + "symfony/config": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/yaml": "<5.4" + }, + "require-dev": { + "doctrine/annotations": "^1.12", + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/http-foundation": "^5.4|^6.0", + "symfony/yaml": "^5.4|^6.0" + }, + "suggest": { + "symfony/config": "For using the all-in-one router or any loader", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps an HTTP request to a set of configuration variables", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "support": { + "source": "https://github.com/symfony/routing/tree/v6.1.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-10-18T13:12:43+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/925e713fe8fcacf6bc05e936edd8dd5441a21239", + "reference": "925e713fe8fcacf6bc05e936edd8dd5441a21239", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^2.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-30T19:18:58+00:00" + }, + { + "name": "symfony/string", + "version": "v6.1.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "823f143370880efcbdfa2dbca946b3358c4707e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/823f143370880efcbdfa2dbca946b3358c4707e5", + "reference": "823f143370880efcbdfa2dbca946b3358c4707e5", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.0" + }, + "require-dev": { + "symfony/error-handler": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/translation-contracts": "^2.0|^3.0", + "symfony/var-exporter": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v6.1.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-10-10T09:34:31+00:00" + }, + { + "name": "symfony/translation", + "version": "v6.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "e6cd330e5a072518f88d65148f3f165541807494" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/e6cd330e5a072518f88d65148f3f165541807494", + "reference": "e6cd330e5a072518f88d65148f3f165541807494", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.3|^3.0" + }, + "conflict": { + "symfony/config": "<5.4", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/twig-bundle": "<5.4", + "symfony/yaml": "<5.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/console": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/finder": "^5.4|^6.0", + "symfony/http-client-contracts": "^1.1|^2.0|^3.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/intl": "^5.4|^6.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^5.4|^6.0", + "symfony/service-contracts": "^1.1.2|^2|^3", + "symfony/yaml": "^5.4|^6.0" + }, + "suggest": { + "psr/log-implementation": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v6.1.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-10-07T08:04:03+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "606be0f48e05116baef052f7f3abdb345c8e02cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/606be0f48e05116baef052f7f3abdb345c8e02cc", + "reference": "606be0f48e05116baef052f7f3abdb345c8e02cc", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.1-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/translation-contracts/tree/v3.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-06-27T17:24:16+00:00" + }, + { + "name": "symfony/uid", + "version": "v6.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "e03519f7b1ce1d3c0b74f751892bb41d549a2d98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/e03519f7b1ce1d3c0b74f751892bb41d549a2d98", + "reference": "e03519f7b1ce1d3c0b74f751892bb41d549a2d98", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/uid/tree/v6.1.5" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-09-09T09:34:27+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v6.1.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "0f0adde127f24548e23cbde83bcaeadc491c551f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/0f0adde127f24548e23cbde83bcaeadc491c551f", + "reference": "0f0adde127f24548e23cbde83bcaeadc491c551f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "phpunit/phpunit": "<5.4.3", + "symfony/console": "<5.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/uid": "^5.4|^6.0", + "twig/twig": "^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v6.1.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-10-07T08:04:03+00:00" + }, + { + "name": "tijsverkoyen/css-to-inline-styles", + "version": "2.2.5", + "source": { + "type": "git", + "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", + "reference": "4348a3a06651827a27d989ad1d13efec6bb49b19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/4348a3a06651827a27d989ad1d13efec6bb49b19", + "reference": "4348a3a06651827a27d989ad1d13efec6bb49b19", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "php": "^5.5 || ^7.0 || ^8.0", + "symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^7.5 || ^8.5.21 || ^9.5.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "TijsVerkoyen\\CssToInlineStyles\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Tijs Verkoyen", + "email": "css_to_inline_styles@verkoyen.eu", + "role": "Developer" + } + ], + "description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.", + "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", + "support": { + "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.5" + }, + "time": "2022-09-12T13:28:28+00:00" + }, + { + "name": "tymon/jwt-auth", + "version": "dev-develop", + "source": { + "type": "git", + "url": "https://github.com/tymondesigns/jwt-auth.git", + "reference": "014be8d493d228d14bbc291b24e835d330c092a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tymondesigns/jwt-auth/zipball/014be8d493d228d14bbc291b24e835d330c092a0", + "reference": "014be8d493d228d14bbc291b24e835d330c092a0", + "shasum": "" + }, + "require": { + "illuminate/auth": "^5.2|^6|^7|^8|^9", + "illuminate/contracts": "^5.2|^6|^7|^8|^9", + "illuminate/http": "^5.2|^6|^7|^8|^9", + "illuminate/support": "^5.2|^6|^7|^8|^9", + "lcobucci/jwt": "^3.4|^4.0", + "namshi/jose": "^7.0", + "nesbot/carbon": "^1.0|^2.0", + "php": "^7.4|^8.0" + }, + "require-dev": { + "illuminate/console": "^5.2|^6|^7|^8|^9", + "illuminate/database": "^5.2|^6|^7|^8|^9", + "illuminate/routing": "^5.2|^6|^7|^8|^9", + "mockery/mockery": ">=0.9.9", + "phpunit/phpunit": "^8.5|^9.4", + "yoast/phpunit-polyfills": "^0.2.0" + }, + "default-branch": true, + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "1.0-dev" + }, + "laravel": { + "aliases": { + "JWTAuth": "Tymon\\JWTAuth\\Facades\\JWTAuth", + "JWTFactory": "Tymon\\JWTAuth\\Facades\\JWTFactory" + }, + "providers": [ + "Tymon\\JWTAuth\\Providers\\LaravelServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Tymon\\JWTAuth\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sean Tymon", + "email": "tymon148@gmail.com", + "homepage": "https://tymon.xyz", + "role": "Developer" + } + ], + "description": "JSON Web Token Authentication for Laravel and Lumen", + "homepage": "https://github.com/tymondesigns/jwt-auth", + "keywords": [ + "Authentication", + "JSON Web Token", + "auth", + "jwt", + "laravel" + ], + "support": { + "issues": "https://github.com/tymondesigns/jwt-auth/issues", + "source": "https://github.com/tymondesigns/jwt-auth" + }, + "funding": [ + { + "url": "https://www.patreon.com/seantymon", + "type": "patreon" + } + ], + "time": "2022-04-27T08:53:50+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.5.0", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", + "reference": "1a7ea2afc49c3ee6d87061f5a233e3a035d0eae7", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.0.2", + "php": "^7.1.3 || ^8.0", + "phpoption/phpoption": "^1.8", + "symfony/polyfill-ctype": "^1.23", + "symfony/polyfill-mbstring": "^1.23.1", + "symfony/polyfill-php80": "^1.23.1" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-filter": "*", + "phpunit/phpunit": "^7.5.20 || ^8.5.30 || ^9.5.25" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": true + }, + "branch-alias": { + "dev-master": "5.5-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.5.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2022-10-16T01:01:54+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "b56450eed252f6801410d810c8e1727224ae0743" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b56450eed252f6801410d810c8e1727224ae0743", + "reference": "b56450eed252f6801410d810c8e1727224ae0743", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "http://www.moelleken.org/" + } + ], + "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", + "homepage": "https://github.com/voku/portable-ascii", + "keywords": [ + "ascii", + "clean", + "php" + ], + "support": { + "issues": "https://github.com/voku/portable-ascii/issues", + "source": "https://github.com/voku/portable-ascii/tree/2.0.1" + }, + "funding": [ + { + "url": "https://www.paypal.me/moelleken", + "type": "custom" + }, + { + "url": "https://github.com/voku", + "type": "github" + }, + { + "url": "https://opencollective.com/portable-ascii", + "type": "open_collective" + }, + { + "url": "https://www.patreon.com/voku", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/voku/portable-ascii", + "type": "tidelift" + } + ], + "time": "2022-03-08T17:03:00+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", + "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.22" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2022-03-03T08:28:38+00:00" + }, + { + "name": "fakerphp/faker", + "version": "v1.20.0", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "37f751c67a5372d4e26353bd9384bc03744ec77b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/37f751c67a5372d4e26353bd9384bc03744ec77b", + "reference": "37f751c67a5372d4e26353bd9384bc03744ec77b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "symfony/phpunit-bridge": "^4.4 || ^5.2" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "v1.20-dev" + } + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.20.0" + }, + "time": "2022-07-20T13:12:54+00:00" + }, + { + "name": "filp/whoops", + "version": "2.14.6", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "f7948baaa0330277c729714910336383286305da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/f7948baaa0330277c729714910336383286305da", + "reference": "f7948baaa0330277c729714910336383286305da", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.6" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-11-02T16:23:29+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3", + "shasum": "" + }, + "require": { + "php": "^5.3|^7.0|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.0.1" + }, + "time": "2020-07-09T08:09:16+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "e92dcc83d5a51851baf5f5591d32cb2b16e3684e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/e92dcc83d5a51851baf5f5591d32cb2b16e3684e", + "reference": "e92dcc83d5a51851baf5f5591d32cb2b16e3684e", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": "^7.3 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Mockery": "library/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "http://blog.astrumfutura.com" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "http://davedevelopment.co.uk" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "issues": "https://github.com/mockery/mockery/issues", + "source": "https://github.com/mockery/mockery/tree/1.5.1" + }, + "time": "2022-09-07T15:32:08+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", + "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2022-03-03T13:19:32+00:00" + }, + { + "name": "nette/php-generator", + "version": "v4.0.5", + "source": { + "type": "git", + "url": "https://github.com/nette/php-generator.git", + "reference": "a8d6abeae5d8c7202cd69600e086a7a72877fc86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/php-generator/zipball/a8d6abeae5d8c7202cd69600e086a7a72877fc86", + "reference": "a8d6abeae5d8c7202cd69600e086a7a72877fc86", + "shasum": "" + }, + "require": { + "nette/utils": "^3.2.7 || ^4.0", + "php": ">=8.0 <8.3" + }, + "require-dev": { + "nette/tester": "^2.4", + "nikic/php-parser": "^4.15", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.8" + }, + "suggest": { + "nikic/php-parser": "to use ClassType::from(withBodies: true) & ClassType::fromCode()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.2 features.", + "homepage": "https://nette.org", + "keywords": [ + "code", + "nette", + "php", + "scaffolding" + ], + "support": { + "issues": "https://github.com/nette/php-generator/issues", + "source": "https://github.com/nette/php-generator/tree/v4.0.5" + }, + "time": "2022-11-02T20:37:46+00:00" + }, + { + "name": "nunomaduro/collision", + "version": "v6.3.1", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/collision.git", + "reference": "0f6349c3ed5dd28467087b08fb59384bb458a22b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/0f6349c3ed5dd28467087b08fb59384bb458a22b", + "reference": "0f6349c3ed5dd28467087b08fb59384bb458a22b", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.14.5", + "php": "^8.0.0", + "symfony/console": "^6.0.2" + }, + "require-dev": { + "brianium/paratest": "^6.4.1", + "laravel/framework": "^9.26.1", + "laravel/pint": "^1.1.1", + "nunomaduro/larastan": "^1.0.3", + "nunomaduro/mock-final-classes": "^1.1.0", + "orchestra/testbench": "^7.7", + "phpunit/phpunit": "^9.5.23", + "spatie/ignition": "^1.4.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "6.x-dev" + }, + "laravel": { + "providers": [ + "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "NunoMaduro\\Collision\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Cli error handling for console/command-line PHP applications.", + "keywords": [ + "artisan", + "cli", + "command-line", + "console", + "error", + "handling", + "laravel", + "laravel-zero", + "php", + "symfony" + ], + "support": { + "issues": "https://github.com/nunomaduro/collision/issues", + "source": "https://github.com/nunomaduro/collision" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2022-09-29T12:29:49+00:00" + }, + { + "name": "pestphp/pest", + "version": "v1.22.2", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest.git", + "reference": "339414e34842f9463f33641b00559d4bf227e478" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest/zipball/339414e34842f9463f33641b00559d4bf227e478", + "reference": "339414e34842f9463f33641b00559d4bf227e478", + "shasum": "" + }, + "require": { + "nunomaduro/collision": "^5.11.0|^6.3.0", + "pestphp/pest-plugin": "^1.1.0", + "php": "^7.3 || ^8.0", + "phpunit/phpunit": "^9.5.26" + }, + "require-dev": { + "illuminate/console": "^8.83.26", + "illuminate/support": "^8.83.26", + "laravel/dusk": "^6.25.2", + "pestphp/pest-dev-tools": "^1.0.0", + "pestphp/pest-plugin-parallel": "^1.2" + }, + "bin": [ + "bin/pest" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + }, + "pest": { + "plugins": [ + "Pest\\Plugins\\Coverage", + "Pest\\Plugins\\Init", + "Pest\\Plugins\\Version", + "Pest\\Plugins\\Environment" + ] + }, + "laravel": { + "providers": [ + "Pest\\Laravel\\PestServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/Functions.php", + "src/Pest.php" + ], + "psr-4": { + "Pest\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "An elegant PHP Testing Framework.", + "keywords": [ + "framework", + "pest", + "php", + "test", + "testing", + "unit" + ], + "support": { + "issues": "https://github.com/pestphp/pest/issues", + "source": "https://github.com/pestphp/pest/tree/v1.22.2" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/lukeraymonddowning", + "type": "github" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://github.com/olivernybroe", + "type": "github" + }, + { + "url": "https://github.com/owenvoke", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2022-11-09T21:10:57+00:00" + }, + { + "name": "pestphp/pest-plugin", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin.git", + "reference": "606c5f79c6a339b49838ffbee0151ca519efe378" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin/zipball/606c5f79c6a339b49838ffbee0151ca519efe378", + "reference": "606c5f79c6a339b49838ffbee0151ca519efe378", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1.0 || ^2.0.0", + "php": "^7.3 || ^8.0" + }, + "conflict": { + "pestphp/pest": "<1.0" + }, + "require-dev": { + "composer/composer": "^2.4.2", + "pestphp/pest": "^1.22.1", + "pestphp/pest-dev-tools": "^1.0.0" + }, + "type": "composer-plugin", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + }, + "class": "Pest\\Plugin\\Manager" + }, + "autoload": { + "psr-4": { + "Pest\\Plugin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Pest plugin manager", + "keywords": [ + "framework", + "manager", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin/tree/v1.1.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2022-09-18T13:18:17+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", + "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.3" + }, + "time": "2021-07-20T11:28:43+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.19", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c77b56b63e3d2031bd8997fcec43c1925ae46559", + "reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.14", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "*", + "ext-xdebug": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.19" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-11-18T07:47:47+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.5.26", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/851867efcbb6a1b992ec515c71cdcf20d895e9d2", + "reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.13", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "*", + "ext-xdebug": "*" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.26" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2022-10-28T06:00:21+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:08:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T12:41:17+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", + "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.7", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T15:52:27+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:10:38+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-04-03T09:37:03+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T06:03:37+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-02-14T08:28:10+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.6", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-11-28T06:42:11+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", + "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:17:30+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:45:17+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-12T14:47:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", + "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2021-07-28T10:34:58+00:00" + } + ], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": { + "tymon/jwt-auth": 20 + }, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": "^8.1", + "ext-pdo": "*", + "ext-zip": "*" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/config/app.php b/config/app.php new file mode 100644 index 0000000..e76734a --- /dev/null +++ b/config/app.php @@ -0,0 +1,215 @@ + 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 + | your application so that it is used when running Artisan tasks. + | + */ + + 'url' => env('APP_URL', 'http://localhost'), + + 'asset_url' => env('ASSET_URL'), + + /* + |-------------------------------------------------------------------------- + | Application Timezone + |-------------------------------------------------------------------------- + | + | Here you may specify the default timezone for your application, which + | will be used by the PHP date and date-time functions. We have gone + | ahead and set this to a sensible default for you out of the box. + | + */ + + 'timezone' => 'UTC', + + /* + |-------------------------------------------------------------------------- + | Application Locale Configuration + |-------------------------------------------------------------------------- + | + | The application locale determines the default locale that will be used + | by the translation service provider. You are free to set this value + | to any of the locales which will be supported by the application. + | + */ + + 'locale' => 'en', + + /* + |-------------------------------------------------------------------------- + | Application Fallback Locale + |-------------------------------------------------------------------------- + | + | The fallback locale determines the locale to use when the current one + | is not available. You may change the value to correspond to any of + | the language folders that are provided through your application. + | + */ + + 'fallback_locale' => 'en', + + /* + |-------------------------------------------------------------------------- + | Faker Locale + |-------------------------------------------------------------------------- + | + | This locale will be used by the Faker PHP library when generating fake + | data for your database seeds. For example, this will be used to get + | localized telephone numbers, street address information and more. + | + */ + + 'faker_locale' => 'en_US', + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is used by the Illuminate encrypter service and should be set + | to a random, 32 character string, otherwise these encrypted strings + | will not be safe. Please do this before deploying an application! + | + */ + + 'key' => env('APP_KEY'), + + 'cipher' => 'AES-256-CBC', + + /* + |-------------------------------------------------------------------------- + | 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' => 'file', + // 'store' => 'redis', + ], + + /* + |-------------------------------------------------------------------------- + | Autoloaded Service Providers + |-------------------------------------------------------------------------- + | + | The service providers listed here will be automatically loaded on the + | request to your application. Feel free to add your own services to + | this array to grant expanded functionality to your applications. + | + */ + + 'providers' => [ + + /* + * Laravel Framework Service Providers... + */ + Illuminate\Auth\AuthServiceProvider::class, + Illuminate\Broadcasting\BroadcastServiceProvider::class, + Illuminate\Bus\BusServiceProvider::class, + Illuminate\Cache\CacheServiceProvider::class, + Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, + Illuminate\Cookie\CookieServiceProvider::class, + Illuminate\Database\DatabaseServiceProvider::class, + Illuminate\Encryption\EncryptionServiceProvider::class, + Illuminate\Filesystem\FilesystemServiceProvider::class, + Illuminate\Foundation\Providers\FoundationServiceProvider::class, + Illuminate\Hashing\HashServiceProvider::class, + Illuminate\Mail\MailServiceProvider::class, + Illuminate\Notifications\NotificationServiceProvider::class, + Illuminate\Pagination\PaginationServiceProvider::class, + Illuminate\Pipeline\PipelineServiceProvider::class, + Illuminate\Queue\QueueServiceProvider::class, + Illuminate\Redis\RedisServiceProvider::class, + Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, + Illuminate\Session\SessionServiceProvider::class, + Illuminate\Translation\TranslationServiceProvider::class, + Illuminate\Validation\ValidationServiceProvider::class, + Illuminate\View\ViewServiceProvider::class, + + /* + * Package Service Providers... + */ + \Catch\Providers\CatchAdminServiceProvider::class, + + /* + * Application Service Providers... + */ + App\Providers\AppServiceProvider::class, + App\Providers\AuthServiceProvider::class, + // App\Providers\BroadcastServiceProvider::class, + App\Providers\EventServiceProvider::class, + App\Providers\RouteServiceProvider::class, + ], + + /* + |-------------------------------------------------------------------------- + | Class Aliases + |-------------------------------------------------------------------------- + | + | This array of class aliases will be registered when this application + | is started. However, feel free to register as many as you wish as + | the aliases are "lazy" loaded so they don't hinder performance. + | + */ + + 'aliases' => Facade::defaultAliases()->merge([ + // 'ExampleClass' => App\Example\ExampleClass::class, + ])->toArray(), + +]; diff --git a/config/auth.php b/config/auth.php new file mode 100644 index 0000000..5dae01b --- /dev/null +++ b/config/auth.php @@ -0,0 +1,111 @@ + [ + 'guard' => 'web', + 'passwords' => 'users', + ], + + /* + |-------------------------------------------------------------------------- + | Authentication Guards + |-------------------------------------------------------------------------- + | + | Next, you may define every authentication guard for your application. + | Of course, a great default configuration has been defined for you + | here which uses session storage and the Eloquent user provider. + | + | All authentication drivers have a user provider. This defines how the + | users are actually retrieved out of your database or other storage + | mechanisms used by this application to persist your user's data. + | + | Supported: "session" + | + */ + + 'guards' => [ + 'web' => [ + 'driver' => 'session', + 'provider' => 'users', + ] + ], + + /* + |-------------------------------------------------------------------------- + | User Providers + |-------------------------------------------------------------------------- + | + | All authentication drivers have a user provider. This defines how the + | users are actually retrieved out of your database or other storage + | mechanisms used by this application to persist your user's data. + | + | If you have multiple user tables or models you may configure multiple + | sources which represent each model / table. These sources may then + | be assigned to any extra authentication guards you have defined. + | + | Supported: "database", "eloquent" + | + */ + + 'providers' => [ + 'users' => [ + 'driver' => 'eloquent', + 'model' => App\Models\User::class, + ] + + // 'users' => [ + // 'driver' => 'database', + // 'table' => 'users', + // ], + ], + + /* + |-------------------------------------------------------------------------- + | Resetting Passwords + |-------------------------------------------------------------------------- + | + | You may specify multiple password reset configurations if you have more + | than one user table or model in the application and you want to have + | separate password reset settings based on the specific user types. + | + | The expire 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. + | + */ + + 'passwords' => [ + 'users' => [ + 'provider' => 'users', + 'table' => 'password_resets', + 'expire' => 60, + 'throttle' => 60, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Password Confirmation Timeout + |-------------------------------------------------------------------------- + | + | Here you may define the amount of seconds before a password confirmation + | times out and the user is prompted to re-enter their password via the + | confirmation screen. By default, the timeout lasts for three hours. + | + */ + + 'password_timeout' => 10800, + +]; diff --git a/config/broadcasting.php b/config/broadcasting.php new file mode 100644 index 0000000..9e4d4aa --- /dev/null +++ b/config/broadcasting.php @@ -0,0 +1,70 @@ + env('BROADCAST_DRIVER', 'null'), + + /* + |-------------------------------------------------------------------------- + | Broadcast Connections + |-------------------------------------------------------------------------- + | + | Here you may define all of the broadcast connections that will be used + | to broadcast events to other systems or over websockets. Samples of + | each available type of connection are provided inside this array. + | + */ + + 'connections' => [ + + 'pusher' => [ + 'driver' => 'pusher', + 'key' => env('PUSHER_APP_KEY'), + 'secret' => env('PUSHER_APP_SECRET'), + 'app_id' => env('PUSHER_APP_ID'), + 'options' => [ + 'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com', + 'port' => env('PUSHER_PORT', 443), + 'scheme' => env('PUSHER_SCHEME', 'https'), + 'encrypted' => true, + 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https', + ], + 'client_options' => [ + // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html + ], + ], + + 'ably' => [ + 'driver' => 'ably', + 'key' => env('ABLY_KEY'), + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => 'default', + ], + + 'log' => [ + 'driver' => 'log', + ], + + 'null' => [ + 'driver' => 'null', + ], + + ], + +]; diff --git a/config/cache.php b/config/cache.php new file mode 100644 index 0000000..33bb295 --- /dev/null +++ b/config/cache.php @@ -0,0 +1,110 @@ + env('CACHE_DRIVER', 'file'), + + /* + |-------------------------------------------------------------------------- + | 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: "apc", "array", "database", "file", + | "memcached", "redis", "dynamodb", "octane", "null" + | + */ + + 'stores' => [ + + 'apc' => [ + 'driver' => 'apc', + ], + + 'array' => [ + 'driver' => 'array', + 'serialize' => false, + ], + + 'database' => [ + 'driver' => 'database', + 'table' => 'cache', + 'connection' => null, + 'lock_connection' => null, + ], + + 'file' => [ + 'driver' => 'file', + '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' => '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, or 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_'), + +]; diff --git a/config/catch.php b/config/catch.php new file mode 100644 index 0000000..3f633f8 --- /dev/null +++ b/config/catch.php @@ -0,0 +1,145 @@ + [ + + ], + + /* + |-------------------------------------------------------------------------- + | catch-admin catch_auth_middleware_alias + |-------------------------------------------------------------------------- + | + | where you can set default middlewares + | + */ + 'catch_auth_middleware_alias' => [ + + ], + + /* + |-------------------------------------------------------------------------- + | catch-admin super admin id + |-------------------------------------------------------------------------- + | + | where you can set super admin id + | + */ + 'super_admin' => 1, + + /* + |-------------------------------------------------------------------------- + | catch-admin module setting + |-------------------------------------------------------------------------- + | + | the root where module generate + | the namespace is module root namespace + | the default dirs is module generate default dirs + */ + 'module' => [ + 'root' => 'modules', + + 'namespace' => 'Modules', + + 'default' => ['develop', 'user'], + + 'default_dirs' => [ + 'Http'.DIRECTORY_SEPARATOR, + + 'Http'.DIRECTORY_SEPARATOR.'Requests'.DIRECTORY_SEPARATOR, + + 'Http'.DIRECTORY_SEPARATOR.'Controllers'.DIRECTORY_SEPARATOR, + + 'Models'.DIRECTORY_SEPARATOR, + + 'views'.DIRECTORY_SEPARATOR, + ], + + // storage module information + // which driver should be used? + 'driver' => [ + // currently, catchadmin support file and database + // the default is driver + 'default' => 'file', + + // use database driver + 'table_name' => 'admin_modules' + ] + ], + + /* + |-------------------------------------------------------------------------- + | catch-admin response + |-------------------------------------------------------------------------- + */ + 'response' => [ + // it's a controller middleware, it's set in CatchController + // if you not need json response, don't extend CatchController + 'always_json' => \Catch\Middleware\JsonResponseMiddleware::class, + + // response listener + // it listens [RequestHandled] event, if you don't need this + // you can change this config + 'request_handled_listener' => \Catch\Listeners\RequestHandledListener::class + ], + + /* + |-------------------------------------------------------------------------- + | catch-admin auth setting + |-------------------------------------------------------------------------- + */ + 'auth' => [ + 'guards' => [ + 'admin' => [ + 'driver' => 'jwt', + 'provider' => 'admin_users', + ], + ], + + 'providers' => [ + 'admin_users' => [ + 'driver' => 'eloquent', + 'model' => \Modules\User\Models\Users::class + ] + ] + ], + + /* + |-------------------------------------------------------------------------- + | database sql log + |-------------------------------------------------------------------------- + */ + 'listen_db_log' => true, + + /* + |-------------------------------------------------------------------------- + | route config + |-------------------------------------------------------------------------- + */ + 'route' => [ + 'prefix' => 'api', + + 'middlewares' => [ + \Catch\Middleware\AuthMiddleware::class, + \Catch\Middleware\JsonResponseMiddleware::class + ] + ], +]; diff --git a/config/cors.php b/config/cors.php new file mode 100644 index 0000000..8a39e6d --- /dev/null +++ b/config/cors.php @@ -0,0 +1,34 @@ + ['api/*', 'sanctum/csrf-cookie'], + + 'allowed_methods' => ['*'], + + 'allowed_origins' => ['*'], + + 'allowed_origins_patterns' => [], + + 'allowed_headers' => ['*'], + + 'exposed_headers' => [], + + 'max_age' => 0, + + 'supports_credentials' => false, + +]; diff --git a/config/database.php b/config/database.php new file mode 100644 index 0000000..ce22c7a --- /dev/null +++ b/config/database.php @@ -0,0 +1,151 @@ + env('DB_CONNECTION', 'mysql'), + + /* + |-------------------------------------------------------------------------- + | Database Connections + |-------------------------------------------------------------------------- + | + | Here are each of the database connections setup for your application. + | Of course, examples of configuring each database platform that is + | supported by Laravel is shown below to make development simple. + | + | + | All database work in Laravel is done through the PHP PDO facilities + | so make sure you have the driver for your particular database of + | choice installed on your machine before you begin development. + | + */ + + 'connections' => [ + + 'sqlite' => [ + 'driver' => 'sqlite', + 'url' => env('DATABASE_URL'), + 'database' => env('DB_DATABASE', database_path('database.sqlite')), + 'prefix' => '', + 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), + ], + + 'mysql' => [ + 'driver' => 'mysql', + 'url' => env('DATABASE_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'prefix' => env('DB_PREFIX', ''), + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => 'InnoDB', + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + 'pgsql' => [ + 'driver' => 'pgsql', + 'url' => env('DATABASE_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => 'utf8', + 'prefix' => '', + 'prefix_indexes' => true, + 'search_path' => 'public', + 'sslmode' => 'prefer', + ], + + 'sqlsrv' => [ + 'driver' => 'sqlsrv', + 'url' => env('DATABASE_URL'), + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', '1433'), + 'database' => env('DB_DATABASE', 'forge'), + 'username' => env('DB_USERNAME', 'forge'), + 'password' => env('DB_PASSWORD', ''), + '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 in the database. + | + */ + + 'migrations' => 'migrations', + + /* + |-------------------------------------------------------------------------- + | 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 APC or Memcached. Laravel makes it easy to dig right in. + | + */ + + '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'), + ], + + ], + +]; diff --git a/config/filesystems.php b/config/filesystems.php new file mode 100644 index 0000000..e9d9dbd --- /dev/null +++ b/config/filesystems.php @@ -0,0 +1,76 @@ + env('FILESYSTEM_DISK', 'local'), + + /* + |-------------------------------------------------------------------------- + | Filesystem Disks + |-------------------------------------------------------------------------- + | + | Here you may configure as many filesystem "disks" as you wish, and you + | may even configure multiple disks of the same driver. Defaults have + | been set up for each driver as an example of the required values. + | + | 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'), + ], + +]; diff --git a/config/hashing.php b/config/hashing.php new file mode 100644 index 0000000..bcd3be4 --- /dev/null +++ b/config/hashing.php @@ -0,0 +1,52 @@ + 'bcrypt', + + /* + |-------------------------------------------------------------------------- + | Bcrypt Options + |-------------------------------------------------------------------------- + | + | Here you may specify the configuration options that should be used when + | passwords are hashed using the Bcrypt algorithm. This will allow you + | to control the amount of time it takes to hash the given password. + | + */ + + 'bcrypt' => [ + 'rounds' => env('BCRYPT_ROUNDS', 10), + ], + + /* + |-------------------------------------------------------------------------- + | Argon Options + |-------------------------------------------------------------------------- + | + | Here you may specify the configuration options that should be used when + | passwords are hashed using the Argon algorithm. These will allow you + | to control the amount of time it takes to hash the given password. + | + */ + + 'argon' => [ + 'memory' => 65536, + 'threads' => 1, + 'time' => 4, + ], + +]; diff --git a/config/jwt.php b/config/jwt.php new file mode 100644 index 0000000..99e3ca1 --- /dev/null +++ b/config/jwt.php @@ -0,0 +1,301 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +return [ + + /* + |-------------------------------------------------------------------------- + | JWT Authentication Secret + |-------------------------------------------------------------------------- + | + | Don't forget to set this in your .env file, as it will be used to sign + | your tokens. A helper command is provided for this: + | `php artisan jwt:secret` + | + | Note: This will be used for Symmetric algorithms only (HMAC), + | since RSA and ECDSA use a private/public key combo (See below). + | + */ + + 'secret' => env('JWT_SECRET'), + + /* + |-------------------------------------------------------------------------- + | JWT Authentication Keys + |-------------------------------------------------------------------------- + | + | The algorithm you are using, will determine whether your tokens are + | signed with a random string (defined in `JWT_SECRET`) or using the + | following public & private keys. + | + | Symmetric Algorithms: + | HS256, HS384 & HS512 will use `JWT_SECRET`. + | + | Asymmetric Algorithms: + | RS256, RS384 & RS512 / ES256, ES384 & ES512 will use the keys below. + | + */ + + 'keys' => [ + + /* + |-------------------------------------------------------------------------- + | Public Key + |-------------------------------------------------------------------------- + | + | A path or resource to your public key. + | + | E.g. 'file://path/to/public/key' + | + */ + + 'public' => env('JWT_PUBLIC_KEY'), + + /* + |-------------------------------------------------------------------------- + | Private Key + |-------------------------------------------------------------------------- + | + | A path or resource to your private key. + | + | E.g. 'file://path/to/private/key' + | + */ + + 'private' => env('JWT_PRIVATE_KEY'), + + /* + |-------------------------------------------------------------------------- + | Passphrase + |-------------------------------------------------------------------------- + | + | The passphrase for your private key. Can be null if none set. + | + */ + + 'passphrase' => env('JWT_PASSPHRASE'), + + ], + + /* + |-------------------------------------------------------------------------- + | JWT time to live + |-------------------------------------------------------------------------- + | + | Specify the length of time (in minutes) that the token will be valid for. + | Defaults to 1 hour. + | + | You can also set this to null, to yield a never expiring token. + | Some people may want this behaviour for e.g. a mobile app. + | This is not particularly recommended, so make sure you have appropriate + | systems in place to revoke the token if necessary. + | Notice: If you set this to null you should remove 'exp' element from 'required_claims' list. + | + */ + + 'ttl' => env('JWT_TTL', 30 * 24 * 3600), + + /* + |-------------------------------------------------------------------------- + | Refresh time to live + |-------------------------------------------------------------------------- + | + | Specify the length of time (in minutes) that the token can be refreshed + | within. I.E. The user can refresh their token within a 2 week window of + | the original token being created until they must re-authenticate. + | Defaults to 2 weeks. + | + | You can also set this to null, to yield an infinite refresh time. + | Some may want this instead of never expiring tokens for e.g. a mobile app. + | This is not particularly recommended, so make sure you have appropriate + | systems in place to revoke the token if necessary. + | + */ + + 'refresh_ttl' => env('JWT_REFRESH_TTL', 20160), + + /* + |-------------------------------------------------------------------------- + | JWT hashing algorithm + |-------------------------------------------------------------------------- + | + | Specify the hashing algorithm that will be used to sign the token. + | + */ + + 'algo' => env('JWT_ALGO', Tymon\JWTAuth\Providers\JWT\Provider::ALGO_HS256), + + /* + |-------------------------------------------------------------------------- + | Required Claims + |-------------------------------------------------------------------------- + | + | Specify the required claims that must exist in any token. + | A TokenInvalidException will be thrown if any of these claims are not + | present in the payload. + | + */ + + 'required_claims' => [ + 'iss', + 'iat', + 'exp', + 'nbf', + 'sub', + 'jti', + ], + + /* + |-------------------------------------------------------------------------- + | Persistent Claims + |-------------------------------------------------------------------------- + | + | Specify the claim keys to be persisted when refreshing a token. + | `sub` and `iat` will automatically be persisted, in + | addition to the these claims. + | + | Note: If a claim does not exist then it will be ignored. + | + */ + + 'persistent_claims' => [ + // 'foo', + // 'bar', + ], + + /* + |-------------------------------------------------------------------------- + | Lock Subject + |-------------------------------------------------------------------------- + | + | This will determine whether a `prv` claim is automatically added to + | the token. The purpose of this is to ensure that if you have multiple + | authentication models e.g. `App\User` & `App\OtherPerson`, then we + | should prevent one authentication request from impersonating another, + | if 2 tokens happen to have the same id across the 2 different models. + | + | Under specific circumstances, you may want to disable this behaviour + | e.g. if you only have one authentication model, then you would save + | a little on token size. + | + */ + + 'lock_subject' => true, + + /* + |-------------------------------------------------------------------------- + | Leeway + |-------------------------------------------------------------------------- + | + | This property gives the jwt timestamp claims some "leeway". + | Meaning that if you have any unavoidable slight clock skew on + | any of your servers then this will afford you some level of cushioning. + | + | This applies to the claims `iat`, `nbf` and `exp`. + | + | Specify in seconds - only if you know you need it. + | + */ + + 'leeway' => env('JWT_LEEWAY', 0), + + /* + |-------------------------------------------------------------------------- + | Blacklist Enabled + |-------------------------------------------------------------------------- + | + | In order to invalidate tokens, you must have the blacklist enabled. + | If you do not want or need this functionality, then set this to false. + | + */ + + 'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true), + + /* + | ------------------------------------------------------------------------- + | Blacklist Grace Period + | ------------------------------------------------------------------------- + | + | When multiple concurrent requests are made with the same JWT, + | it is possible that some of them fail, due to token regeneration + | on every request. + | + | Set grace period in seconds to prevent parallel request failure. + | + */ + + 'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 0), + + /* + |-------------------------------------------------------------------------- + | Cookies encryption + |-------------------------------------------------------------------------- + | + | By default Laravel encrypt cookies for security reason. + | If you decide to not decrypt cookies, you will have to configure Laravel + | to not encrypt your cookie token by adding its name into the $except + | array available in the middleware "EncryptCookies" provided by Laravel. + | see https://laravel.com/docs/master/responses#cookies-and-encryption + | for details. + | + | Set it to true if you want to decrypt cookies. + | + */ + + 'decrypt_cookies' => false, + + /* + |-------------------------------------------------------------------------- + | Providers + |-------------------------------------------------------------------------- + | + | Specify the various providers used throughout the package. + | + */ + + 'providers' => [ + + /* + |-------------------------------------------------------------------------- + | JWT Provider + |-------------------------------------------------------------------------- + | + | Specify the provider that is used to create and decode the tokens. + | + */ + + 'jwt' => Tymon\JWTAuth\Providers\JWT\Lcobucci::class, + + /* + |-------------------------------------------------------------------------- + | Authentication Provider + |-------------------------------------------------------------------------- + | + | Specify the provider that is used to authenticate users. + | + */ + + 'auth' => Tymon\JWTAuth\Providers\Auth\Illuminate::class, + + /* + |-------------------------------------------------------------------------- + | Storage Provider + |-------------------------------------------------------------------------- + | + | Specify the provider that is used to store tokens in the blacklist. + | + */ + + 'storage' => Tymon\JWTAuth\Providers\Storage\Illuminate::class, + + ], + +]; diff --git a/config/logging.php b/config/logging.php new file mode 100644 index 0000000..5aa1dbb --- /dev/null +++ b/config/logging.php @@ -0,0 +1,122 @@ + 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' => false, + ], + + /* + |-------------------------------------------------------------------------- + | Log Channels + |-------------------------------------------------------------------------- + | + | Here you may configure the log channels for your application. Out of + | the box, Laravel uses the Monolog PHP logging library. This gives + | you a variety of powerful log handlers / formatters to utilize. + | + | Available Drivers: "single", "daily", "slack", "syslog", + | "errorlog", "monolog", + | "custom", "stack" + | + */ + + 'channels' => [ + 'stack' => [ + 'driver' => 'stack', + 'channels' => ['single'], + 'ignore_exceptions' => false, + ], + + 'single' => [ + 'driver' => 'single', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + ], + + 'daily' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => 14, + ], + + 'slack' => [ + 'driver' => 'slack', + 'url' => env('LOG_SLACK_WEBHOOK_URL'), + 'username' => 'Laravel Log', + 'emoji' => ':boom:', + 'level' => env('LOG_LEVEL', 'critical'), + ], + + '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'), + ], + ], + + 'stderr' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => StreamHandler::class, + 'formatter' => env('LOG_STDERR_FORMATTER'), + 'with' => [ + 'stream' => 'php://stderr', + ], + ], + + 'syslog' => [ + 'driver' => 'syslog', + 'level' => env('LOG_LEVEL', 'debug'), + ], + + 'errorlog' => [ + 'driver' => 'errorlog', + 'level' => env('LOG_LEVEL', 'debug'), + ], + + 'null' => [ + 'driver' => 'monolog', + 'handler' => NullHandler::class, + ], + + 'emergency' => [ + 'path' => storage_path('logs/laravel.log'), + ], + ], + +]; diff --git a/config/mail.php b/config/mail.php new file mode 100644 index 0000000..534395a --- /dev/null +++ b/config/mail.php @@ -0,0 +1,118 @@ + env('MAIL_MAILER', 'smtp'), + + /* + |-------------------------------------------------------------------------- + | 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 to be used while + | sending an e-mail. You will specify which one you are using for your + | mailers below. You are free to add additional mailers as required. + | + | Supported: "smtp", "sendmail", "mailgun", "ses", + | "postmark", "log", "array", "failover" + | + */ + + 'mailers' => [ + 'smtp' => [ + 'transport' => 'smtp', + 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), + 'port' => env('MAIL_PORT', 587), + 'encryption' => env('MAIL_ENCRYPTION', 'tls'), + 'username' => env('MAIL_USERNAME'), + 'password' => env('MAIL_PASSWORD'), + 'timeout' => null, + 'local_domain' => env('MAIL_EHLO_DOMAIN'), + ], + + 'ses' => [ + 'transport' => 'ses', + ], + + 'mailgun' => [ + 'transport' => 'mailgun', + ], + + 'postmark' => [ + 'transport' => 'postmark', + ], + + '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', + ], + ], + ], + + /* + |-------------------------------------------------------------------------- + | Global "From" Address + |-------------------------------------------------------------------------- + | + | You may wish for all e-mails 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 e-mails that are sent by your application. + | + */ + + 'from' => [ + 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), + 'name' => env('MAIL_FROM_NAME', 'Example'), + ], + + /* + |-------------------------------------------------------------------------- + | Markdown Mail Settings + |-------------------------------------------------------------------------- + | + | If you are using Markdown based email rendering, you may configure your + | theme and component paths here, allowing you to customize the design + | of the emails. Or, you may simply stick with the Laravel defaults! + | + */ + + 'markdown' => [ + 'theme' => 'default', + + 'paths' => [ + resource_path('views/vendor/mail'), + ], + ], + +]; diff --git a/config/queue.php b/config/queue.php new file mode 100644 index 0000000..25ea5a8 --- /dev/null +++ b/config/queue.php @@ -0,0 +1,93 @@ + env('QUEUE_CONNECTION', 'sync'), + + /* + |-------------------------------------------------------------------------- + | Queue Connections + |-------------------------------------------------------------------------- + | + | Here you may configure the connection information for each server that + | is used by your application. A default configuration has been added + | for each back-end shipped with Laravel. You are free to add more. + | + | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" + | + */ + + 'connections' => [ + + 'sync' => [ + 'driver' => 'sync', + ], + + 'database' => [ + 'driver' => 'database', + 'table' => 'jobs', + 'queue' => 'default', + 'retry_after' => 90, + 'after_commit' => false, + ], + + 'beanstalkd' => [ + 'driver' => 'beanstalkd', + 'host' => 'localhost', + 'queue' => 'default', + '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' => 'default', + 'queue' => env('REDIS_QUEUE', 'default'), + 'retry_after' => 90, + 'block_for' => null, + 'after_commit' => false, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Failed Queue Jobs + |-------------------------------------------------------------------------- + | + | These options configure the behavior of failed queue job logging so you + | can control which database and table are used to store the jobs that + | have failed. You may change them to any database / table you wish. + | + */ + + 'failed' => [ + 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), + 'database' => env('DB_CONNECTION', 'mysql'), + 'table' => 'failed_jobs', + ], + +]; diff --git a/config/services.php b/config/services.php new file mode 100644 index 0000000..0ace530 --- /dev/null +++ b/config/services.php @@ -0,0 +1,34 @@ + [ + 'domain' => env('MAILGUN_DOMAIN'), + 'secret' => env('MAILGUN_SECRET'), + 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), + 'scheme' => 'https', + ], + + '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'), + ], + +]; diff --git a/config/session.php b/config/session.php new file mode 100644 index 0000000..8fed97c --- /dev/null +++ b/config/session.php @@ -0,0 +1,201 @@ + env('SESSION_DRIVER', 'file'), + + /* + |-------------------------------------------------------------------------- + | 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 immediately expire on the browser closing, set that option. + | + */ + + 'lifetime' => env('SESSION_LIFETIME', 120), + + 'expire_on_close' => false, + + /* + |-------------------------------------------------------------------------- + | Session Encryption + |-------------------------------------------------------------------------- + | + | This option allows you to easily specify that all of your session data + | should be encrypted before it is stored. All encryption will be run + | automatically by Laravel and you can use the Session like normal. + | + */ + + 'encrypt' => false, + + /* + |-------------------------------------------------------------------------- + | Session File Location + |-------------------------------------------------------------------------- + | + | When using the native session driver, we need a location where session + | files may be stored. A default has been set for you but a different + | location may be specified. This is only needed for file sessions. + | + */ + + '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 we + | should use to manage the sessions. Of course, a sensible default is + | provided for you; however, you are free to change this as needed. + | + */ + + 'table' => 'sessions', + + /* + |-------------------------------------------------------------------------- + | Session Cache Store + |-------------------------------------------------------------------------- + | + | While using one of the framework's cache driven session backends you may + | list a cache store that should be used for these sessions. This value + | must match with one of the application's configured 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 cookie used to identify a session + | instance by ID. The name specified here will get used every time a + | new session cookie is created by the framework for every driver. + | + */ + + '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 are free to change this when necessary. + | + */ + + 'path' => '/', + + /* + |-------------------------------------------------------------------------- + | Session Cookie Domain + |-------------------------------------------------------------------------- + | + | Here you may change the domain of the cookie used to identify a session + | in your application. This will determine which domains the cookie is + | available to in your application. A sensible default has been set. + | + */ + + '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. You are free to modify this option if needed. + | + */ + + '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" since this is a secure default value. + | + | Supported: "lax", "strict", "none", null + | + */ + + 'same_site' => 'lax', + +]; diff --git a/config/view.php b/config/view.php new file mode 100644 index 0000000..22b8a18 --- /dev/null +++ b/config/view.php @@ -0,0 +1,36 @@ + [ + resource_path('views'), + ], + + /* + |-------------------------------------------------------------------------- + | Compiled View Path + |-------------------------------------------------------------------------- + | + | This option determines where all the compiled Blade templates will be + | stored for your application. Typically, this is within the storage + | directory. However, as usual, you are free to change this value. + | + */ + + 'compiled' => env( + 'VIEW_COMPILED_PATH', + realpath(storage_path('framework/views')) + ), + +]; diff --git a/database/.gitignore b/database/.gitignore new file mode 100644 index 0000000..9b19b93 --- /dev/null +++ b/database/.gitignore @@ -0,0 +1 @@ +*.sqlite* diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php new file mode 100644 index 0000000..41f8ae8 --- /dev/null +++ b/database/factories/UserFactory.php @@ -0,0 +1,40 @@ + + */ +class UserFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition() + { + return [ + 'name' => fake()->name(), + 'email' => fake()->unique()->safeEmail(), + 'email_verified_at' => now(), + 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password + 'remember_token' => Str::random(10), + ]; + } + + /** + * Indicate that the model's email address should be unverified. + * + * @return static + */ + public function unverified() + { + return $this->state(fn (array $attributes) => [ + 'email_verified_at' => null, + ]); + } +} diff --git a/database/migrations/2019_08_19_000000_create_failed_jobs_table.php b/database/migrations/2019_08_19_000000_create_failed_jobs_table.php new file mode 100644 index 0000000..399c78f --- /dev/null +++ b/database/migrations/2019_08_19_000000_create_failed_jobs_table.php @@ -0,0 +1,37 @@ +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. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('failed_jobs'); + } +}; diff --git a/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php new file mode 100644 index 0000000..6c81fd2 --- /dev/null +++ b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php @@ -0,0 +1,37 @@ +id(); + $table->morphs('tokenable'); + $table->string('name'); + $table->string('token', 64)->unique(); + $table->text('abilities')->nullable(); + $table->timestamp('last_used_at')->nullable(); + $table->timestamp('expires_at')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('personal_access_tokens'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php new file mode 100644 index 0000000..76d96dc --- /dev/null +++ b/database/seeders/DatabaseSeeder.php @@ -0,0 +1,24 @@ +create(); + + // \App\Models\User::factory()->create([ + // 'name' => 'Test User', + // 'email' => 'test@example.com', + // ]); + } +} diff --git a/database/seeders/good.php b/database/seeders/good.php new file mode 100644 index 0000000..af00d4d --- /dev/null +++ b/database/seeders/good.php @@ -0,0 +1,19 @@ + 'These credentials do not match our records.', + 'password' => 'The provided password is incorrect.', + 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', + +]; diff --git a/lang/en/pagination.php b/lang/en/pagination.php new file mode 100644 index 0000000..d481411 --- /dev/null +++ b/lang/en/pagination.php @@ -0,0 +1,19 @@ + '« Previous', + 'next' => 'Next »', + +]; diff --git a/lang/en/passwords.php b/lang/en/passwords.php new file mode 100644 index 0000000..2345a56 --- /dev/null +++ b/lang/en/passwords.php @@ -0,0 +1,22 @@ + 'Your password has been reset!', + 'sent' => 'We have emailed your password reset link!', + 'throttled' => 'Please wait before retrying.', + 'token' => 'This password reset token is invalid.', + 'user' => "We can't find a user with that email address.", + +]; diff --git a/lang/en/validation.php b/lang/en/validation.php new file mode 100644 index 0000000..5ea01fa --- /dev/null +++ b/lang/en/validation.php @@ -0,0 +1,174 @@ + 'The :attribute must be accepted.', + 'accepted_if' => 'The :attribute must be accepted when :other is :value.', + 'active_url' => 'The :attribute is not a valid URL.', + 'after' => 'The :attribute must be a date after :date.', + 'after_or_equal' => 'The :attribute must be a date after or equal to :date.', + 'alpha' => 'The :attribute must only contain letters.', + 'alpha_dash' => 'The :attribute must only contain letters, numbers, dashes and underscores.', + 'alpha_num' => 'The :attribute must only contain letters and numbers.', + 'array' => 'The :attribute must be an array.', + 'before' => 'The :attribute must be a date before :date.', + 'before_or_equal' => 'The :attribute must be a date before or equal to :date.', + 'between' => [ + 'array' => 'The :attribute must have between :min and :max items.', + 'file' => 'The :attribute must be between :min and :max kilobytes.', + 'numeric' => 'The :attribute must be between :min and :max.', + 'string' => 'The :attribute must be between :min and :max characters.', + ], + 'boolean' => 'The :attribute field must be true or false.', + 'confirmed' => 'The :attribute confirmation does not match.', + 'current_password' => 'The password is incorrect.', + 'date' => 'The :attribute is not a valid date.', + 'date_equals' => 'The :attribute must be a date equal to :date.', + 'date_format' => 'The :attribute does not match the format :format.', + 'declined' => 'The :attribute must be declined.', + 'declined_if' => 'The :attribute must be declined when :other is :value.', + 'different' => 'The :attribute and :other must be different.', + 'digits' => 'The :attribute must be :digits digits.', + 'digits_between' => 'The :attribute must be between :min and :max digits.', + 'dimensions' => 'The :attribute has invalid image dimensions.', + 'distinct' => 'The :attribute field has a duplicate value.', + 'doesnt_end_with' => 'The :attribute may not end with one of the following: :values.', + 'doesnt_start_with' => 'The :attribute may not start with one of the following: :values.', + 'email' => 'The :attribute must be a valid email address.', + 'ends_with' => 'The :attribute must end with one of the following: :values.', + 'enum' => 'The selected :attribute is invalid.', + 'exists' => 'The selected :attribute is invalid.', + 'file' => 'The :attribute must be a file.', + 'filled' => 'The :attribute field must have a value.', + 'gt' => [ + 'array' => 'The :attribute must have more than :value items.', + 'file' => 'The :attribute must be greater than :value kilobytes.', + 'numeric' => 'The :attribute must be greater than :value.', + 'string' => 'The :attribute must be greater than :value characters.', + ], + 'gte' => [ + 'array' => 'The :attribute must have :value items or more.', + 'file' => 'The :attribute must be greater than or equal to :value kilobytes.', + 'numeric' => 'The :attribute must be greater than or equal to :value.', + 'string' => 'The :attribute must be greater than or equal to :value characters.', + ], + 'image' => 'The :attribute must be an image.', + 'in' => 'The selected :attribute is invalid.', + 'in_array' => 'The :attribute field does not exist in :other.', + 'integer' => 'The :attribute must be an integer.', + 'ip' => 'The :attribute must be a valid IP address.', + 'ipv4' => 'The :attribute must be a valid IPv4 address.', + 'ipv6' => 'The :attribute must be a valid IPv6 address.', + 'json' => 'The :attribute must be a valid JSON string.', + 'lt' => [ + 'array' => 'The :attribute must have less than :value items.', + 'file' => 'The :attribute must be less than :value kilobytes.', + 'numeric' => 'The :attribute must be less than :value.', + 'string' => 'The :attribute must be less than :value characters.', + ], + 'lte' => [ + 'array' => 'The :attribute must not have more than :value items.', + 'file' => 'The :attribute must be less than or equal to :value kilobytes.', + 'numeric' => 'The :attribute must be less than or equal to :value.', + 'string' => 'The :attribute must be less than or equal to :value characters.', + ], + 'mac_address' => 'The :attribute must be a valid MAC address.', + 'max' => [ + 'array' => 'The :attribute must not have more than :max items.', + 'file' => 'The :attribute must not be greater than :max kilobytes.', + 'numeric' => 'The :attribute must not be greater than :max.', + 'string' => 'The :attribute must not be greater than :max characters.', + ], + 'max_digits' => 'The :attribute must not have more than :max digits.', + 'mimes' => 'The :attribute must be a file of type: :values.', + 'mimetypes' => 'The :attribute must be a file of type: :values.', + 'min' => [ + 'array' => 'The :attribute must have at least :min items.', + 'file' => 'The :attribute must be at least :min kilobytes.', + 'numeric' => 'The :attribute must be at least :min.', + 'string' => 'The :attribute must be at least :min characters.', + ], + 'min_digits' => 'The :attribute must have at least :min digits.', + 'multiple_of' => 'The :attribute must be a multiple of :value.', + 'not_in' => 'The selected :attribute is invalid.', + 'not_regex' => 'The :attribute format is invalid.', + 'numeric' => 'The :attribute must be a number.', + 'password' => [ + 'letters' => 'The :attribute must contain at least one letter.', + 'mixed' => 'The :attribute must contain at least one uppercase and one lowercase letter.', + 'numbers' => 'The :attribute must contain at least one number.', + 'symbols' => 'The :attribute must contain at least one symbol.', + 'uncompromised' => 'The given :attribute has appeared in a data leak. Please choose a different :attribute.', + ], + 'present' => 'The :attribute field must be present.', + 'prohibited' => 'The :attribute field is prohibited.', + 'prohibited_if' => 'The :attribute field is prohibited when :other is :value.', + 'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.', + 'prohibits' => 'The :attribute field prohibits :other from being present.', + 'regex' => 'The :attribute format is invalid.', + 'required' => 'The :attribute field is required.', + 'required_array_keys' => 'The :attribute field must contain entries for: :values.', + 'required_if' => 'The :attribute field is required when :other is :value.', + 'required_if_accepted' => 'The :attribute field is required when :other is accepted.', + 'required_unless' => 'The :attribute field is required unless :other is in :values.', + 'required_with' => 'The :attribute field is required when :values is present.', + 'required_with_all' => 'The :attribute field is required when :values are present.', + 'required_without' => 'The :attribute field is required when :values is not present.', + 'required_without_all' => 'The :attribute field is required when none of :values are present.', + 'same' => 'The :attribute and :other must match.', + 'size' => [ + 'array' => 'The :attribute must contain :size items.', + 'file' => 'The :attribute must be :size kilobytes.', + 'numeric' => 'The :attribute must be :size.', + 'string' => 'The :attribute must be :size characters.', + ], + 'starts_with' => 'The :attribute must start with one of the following: :values.', + 'string' => 'The :attribute must be a string.', + 'timezone' => 'The :attribute must be a valid timezone.', + 'unique' => 'The :attribute has already been taken.', + 'uploaded' => 'The :attribute failed to upload.', + 'url' => 'The :attribute must be a valid URL.', + 'uuid' => 'The :attribute must be a valid UUID.', + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => [ + 'attribute-name' => [ + 'rule-name' => 'custom-message', + ], + ], + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap our attribute placeholder + | with something more reader friendly such as "E-Mail Address" instead + | of "email". This simply helps us make our message more expressive. + | + */ + + 'attributes' => [], + +]; diff --git a/modules/Develop/Http/Controllers/GenerateController.php b/modules/Develop/Http/Controllers/GenerateController.php new file mode 100644 index 0000000..d887e2e --- /dev/null +++ b/modules/Develop/Http/Controllers/GenerateController.php @@ -0,0 +1,21 @@ +setParams($request->all())->generate(); + } +} diff --git a/modules/Develop/Http/Controllers/ModuleController.php b/modules/Develop/Http/Controllers/ModuleController.php new file mode 100644 index 0000000..2255eb9 --- /dev/null +++ b/modules/Develop/Http/Controllers/ModuleController.php @@ -0,0 +1,91 @@ +repository = $repository; + } + + /** + * index + * + * @param Request $request + * @return Collection + */ + public function index(Request $request): Collection + { + return $this->repository->all($request->all()); + } + + /** + * store + * + * @param Request $request + * @return bool|int + */ + public function store(Request $request): bool|int + { + return $this->repository->create($request->all()); + } + + /** + * show + * + * @param string $name + * @return Collection + * @throws \Exception + */ + public function show(mixed $name): Collection + { + return $this->repository->show($name); + } + + /** + * update + * + * @param $name + * @param Request $request + * @return bool|int + */ + public function update($name, Request $request): bool|int + { + return $this->repository->update($name, $request->all()); + } + + + /** + * update + * + * @param $name + * @return bool|int + */ + public function enable($name): bool|int + { + return $this->repository->disOrEnable($name); + } + + /** + * destroy + * + * @param $name + * @return bool|int + * @throws \Exception + */ + public function destroy($name): bool|int + { + return $this->repository->delete($name); + } +} diff --git a/modules/Develop/Http/Controllers/SchemaController.php b/modules/Develop/Http/Controllers/SchemaController.php new file mode 100644 index 0000000..288b5db --- /dev/null +++ b/modules/Develop/Http/Controllers/SchemaController.php @@ -0,0 +1,61 @@ +schemas->getList(); + } + + /** + * store + * + * @param Request $request + * @throws \Exception + * @return bool + */ + public function store(Request $request) + { + return $this->schemas->storeBy($request->all()); + } + + /** + * show + * + * @param $id + * @return mixed + */ + public function show($id) + { + return $this->schemas->show($id); + } + + + /** + * destroy + * + * @param $id + * @return bool|null + */ + public function destroy($id) + { + return $this->schemas->deleteBy($id); + } +} diff --git a/modules/Develop/Listeners/CreatedListener.php b/modules/Develop/Listeners/CreatedListener.php new file mode 100644 index 0000000..af6821d --- /dev/null +++ b/modules/Develop/Listeners/CreatedListener.php @@ -0,0 +1,39 @@ +module; + + (new Module( + $module['path'], + $module['dirs']['controllers'], + $module['dirs']['models'], + $module['dirs']['requests'], + $module['dirs']['database'] + ) + )->create(); + } +} diff --git a/modules/Develop/Listeners/DeletedListener.php b/modules/Develop/Listeners/DeletedListener.php new file mode 100644 index 0000000..a277d03 --- /dev/null +++ b/modules/Develop/Listeners/DeletedListener.php @@ -0,0 +1,30 @@ +module['path']); + } +} diff --git a/modules/Develop/Models/Schemas.php b/modules/Develop/Models/Schemas.php new file mode 100644 index 0000000..467512f --- /dev/null +++ b/modules/Develop/Models/Schemas.php @@ -0,0 +1,129 @@ + 'like', 'name' => 'like']; + + /** + * @var string[] + */ + protected array $mergeCasts = [ + 'is_soft_delete' => Status::class + ]; + + /** + * + * @param array $data + * @return boolean + * @throws Exception + */ + public function storeBy(array $data): bool + { + $schema = $data['schema']; + + $structures = $data['structures']; + + $schemaId = parent::storeBy([ + 'module' => $schema['module'], + + 'name' => $schema['name'], + + 'columns' => implode(',', array_column($structures, 'field')), + + 'is_soft_delete' => $schema['deleted_at'] ? Status::Enable : Status::Disable + ]); + + try { + $schemaCreate = new Schema($schema['name'], $schema['engine'], $schema['charset'], $schema['collection'], $schema['comment']); + + $schemaCreate->setStructures($structures) + ->setModule($schema['module']) + ->setCreatedAt($schema['created_at']) + ->setCreatorId($schema['creator_id']) + ->setUpdatedAt($schema['updated_at']) + ->setDeletedAt($schema['deleted_at']) + ->create(); + } catch (Exception $e) { + parent::deleteBy($schemaId, true); + + throw $e; + } + + return true; + } + + + /** + * @param $id + * @return Model + */ + public function show($id): Model + { + $schema = parent::firstBy($id); + + $columns = []; + + foreach (getTableColumns($schema->name) as $columnString) { + $column = DB::connection()->getDoctrineColumn(DB::connection()->getTablePrefix().$schema->name, $columnString); + $columns[] = [ + 'name' => $column->getName(), + 'type' => $column->getType()->getName(), + 'nullable' => ! $column->getNotnull(), + 'default' => $column->getDefault(), + 'comment' => $column->getComment() + ]; + } + + $schema->columns = $columns; + + return $schema; + } + + /** + * delete + * + * @param $id + * @param bool $force + * @return bool|null + */ + public function deleteBy($id, bool $force = false): ?bool + { + $schema = parent::firstBy($id); + + if ($schema->delete()) { + SchemaFacade::dropIfExists($schema->name); + } + + return true; + } +} diff --git a/modules/Develop/Providers/DevelopServiceProvider.php b/modules/Develop/Providers/DevelopServiceProvider.php new file mode 100644 index 0000000..372a649 --- /dev/null +++ b/modules/Develop/Providers/DevelopServiceProvider.php @@ -0,0 +1,30 @@ + CreatedListener::class, + + Deleted::class => DeletedListener::class + ]; + + /** + * route path + * + * @return string|array + */ + public function routePath(): string|array + { + // TODO: Implement path() method. + return CatchAdmin::getModuleRoutePath('develop'); + } +} diff --git a/modules/Develop/Providers/Install.php b/modules/Develop/Providers/Install.php new file mode 100644 index 0000000..181a781 --- /dev/null +++ b/modules/Develop/Providers/Install.php @@ -0,0 +1,40 @@ +module).$this->getControllerName().$this->ext; + } + + public function getContent(): string|bool + { + // TODO: Implement getContent() method. + return Str::of(File::get($this->getControllerStub()))->replace($this->replace, [ + $this->getControllerNamespace(), + + $this->getUses(), + + $this->getControllerName(), + + $this->model, + + $this->request ?: 'Request' + ])->toString(); + } + + /** + * get controller name + * + * @return string + */ + protected function getControllerName(): string + { + return Str::of($this->controller)->whenContains('Controller', function ($value) { + return Str::of($value)->ucfirst(); + }, function ($value) { + return Str::of($value)->append('Controller')->ucfirst(); + })->toString(); + } + + /** + * get uses + * + * @return string + */ + protected function getUses(): string + { + return Str::of('use ') + ->append(CatchAdmin::getModuleModelNamespace($this->module).$this->model) + ->append(';') + ->newLine() + ->append('use ') + ->when($this->request, function ($str) { + return $str->append(CatchAdmin::getModuleRequestNamespace($this->module).$this->request); + }, function ($str) { + return $str->append("Illuminate\Http\Request"); + })->append(';')->newLine()->toString(); + } + + /** + * get controller stub + * + * @return string + */ + protected function getControllerStub(): string + { + return dirname(__DIR__).DIRECTORY_SEPARATOR.'stubs'.DIRECTORY_SEPARATOR.'controller.stub'; + } + + /** + * get controller namespace + * + * @return string + */ + protected function getControllerNamespace(): string + { + return Str::of(CatchAdmin::getModuleControllerNamespace($this->module))->rtrim('\\')->append(';')->toString(); + } +} diff --git a/modules/Develop/Support/Generate/Create/Creator.php b/modules/Develop/Support/Generate/Create/Creator.php new file mode 100644 index 0000000..383b9a8 --- /dev/null +++ b/modules/Develop/Support/Generate/Create/Creator.php @@ -0,0 +1,112 @@ +put(); + } + + /** + * the file which content put in + * + * @return string + */ + abstract public function getFile(): string; + + /** + * get content + * @return string|bool + */ + abstract public function getContent(): string|bool; + + /** + * @return string|bool + * @throws FileNotFoundException + */ + protected function put(): string|bool + { + if (! $this->getContent()) { + return false; + } + + $this->file = $this->getFile(); + + File::put($this->file, $this->getContent()); + + if (File::exists($this->file)) { + return $this->file; + } + + throw new FileNotFoundException("create [$this->file] failed"); + } + + + /** + * set ext + * + * @param string $ext + * @return $this + */ + protected function setExt(string $ext): static + { + $this->ext = $ext; + + return $this; + } + + + /** + * set module + * + * @param string $module + * @return $this + */ + public function setModule(string $module): static + { + $this->module = $module; + + return $this; + } + + /** + * get file + * + * @return string + */ + public function getGenerateFile(): string + { + return $this->file; + } +} diff --git a/modules/Develop/Support/Generate/Create/FrontForm.php b/modules/Develop/Support/Generate/Create/FrontForm.php new file mode 100644 index 0000000..faf5679 --- /dev/null +++ b/modules/Develop/Support/Generate/Create/FrontForm.php @@ -0,0 +1,188 @@ +getFormStub()))->replace($this->formItems, $this->getFormContent())->toString(); + } + + /** + * get file + * + * @return string + */ + public function getFile(): string + { + // TODO: Implement getFile() method. + return CatchAdmin::makeDir(CatchAdmin::getModuleViewsPath($this->module).Str::of($this->controller)->replace('Controller', '')->lcfirst()).DIRECTORY_SEPARATOR.'create.vue'; + } + + /** + * get form content + * + * @return string + */ + protected function getFormContent(): string + { + $form = Str::of(''); + + $formComponents = $this->formComponents(); + + foreach ($this->structures as $structure) { + if ($structure['label'] && $structure['form_component'] && $structure['form']) { + if (isset($formComponents[$structure['form_component']])) { + $form = $form->append( + Str::of($formComponents[$structure['form_component']]) + ->replace( + [$this->label, $this->prop, $this->modelValue], + [$structure['label'], $structure['field'], sprintf('formData.%s', $structure['field'])] + ) + ); + } + } + } + + return $form->trim(PHP_EOL)->toString(); + } + + /** + * form components + * + * @return array + */ + protected function formComponents(): array + { + $components = []; + + foreach (File::glob( + $this->getFormItemStub() + ) as $stub) { + $components[File::name($stub)] = File::get($stub); + } + + return $components; + } + + + /** + * get formItem stub + * + * @return string + */ + protected function getFormItemStub(): string + { + return dirname(__DIR__).DIRECTORY_SEPARATOR.'stubs' + + .DIRECTORY_SEPARATOR.'vue'.DIRECTORY_SEPARATOR + + .'formItems'.DIRECTORY_SEPARATOR.'*.stub'; + } + + + /** + * get form stub + * + * @return string + */ + public function getFormStub(): string + { + return dirname(__DIR__).DIRECTORY_SEPARATOR.'stubs' + + .DIRECTORY_SEPARATOR.'vue'.DIRECTORY_SEPARATOR.'form.stub'; + } + + /** + * set structures + * + * @param array $structures + * @return $this + */ + public function setStructures(array $structures): static + { + $this->structures = $structures; + + return $this; + } +} diff --git a/modules/Develop/Support/Generate/Create/FrontTable.php b/modules/Develop/Support/Generate/Create/FrontTable.php new file mode 100644 index 0000000..aebf7d6 --- /dev/null +++ b/modules/Develop/Support/Generate/Create/FrontTable.php @@ -0,0 +1,252 @@ +getTableStub()))->replace([ + $this->table, $this->search, $this->api, $this->paginate, $this->useList + ], [ + $this->getTableContent(), $this->getSearchContent(), + "'{$this->apiString}'", $this->getPaginateStubContent(), $this->getUseList() + ])->toString(); + } + + /** + * get file + * + * @return string + */ + public function getFile(): string + { + // TODO: Implement getFile() method. + return CatchAdmin::makeDir(CatchAdmin::getModuleViewsPath($this->module).Str::of($this->controller)->replace('Controller', '')->lcfirst()).DIRECTORY_SEPARATOR.'index.vue'; + } + + + /** + * get search content + * + * @return string + */ + protected function getSearchContent(): string + { + $search = Str::of(''); + + $formComponents = $this->formComponents(); + + foreach ($this->structures as $structure) { + if ($structure['label'] && $structure['form_component'] && $structure['search']) { + if (isset($formComponents[$structure['form_component']])) { + $search = $search->append( + Str::of($formComponents[$structure['form_component']]) + ->replace( + [$this->label, $this->prop, $this->modelValue], + [$structure['label'], $structure['field'], sprintf('query.%s', $structure['field'])] + ) + ); + } + } + } + + return $search->trim(PHP_EOL)->toString(); + } + + /** + * get list content; + * + * @return string + */ + protected function getTableContent(): string + { + $tableColumn = << +HTML; + + $table = Str::of(''); + + foreach ($this->structures as $structure) { + if ($structure['field'] && $structure['label'] && $structure['list']) { + $table = $table->append( + Str::of($tableColumn)->replace([$this->label, $this->prop], [$structure['label'], $structure['field']]) + )->newLine(); + } + } + + return $table->trim(PHP_EOL)->toString(); + } + + /** + * form components + * + * @return array + */ + protected function formComponents(): array + { + $components = []; + + foreach (File::glob( + $this->getFormItemStub() + ) as $stub) { + $components[File::name($stub)] = File::get($stub); + } + + return $components; + } + + + /** + * get formItem stub + * + * @return string + */ + protected function getFormItemStub(): string + { + return dirname(__DIR__).DIRECTORY_SEPARATOR.'stubs' + + .DIRECTORY_SEPARATOR.'vue'.DIRECTORY_SEPARATOR + + .'formItems'.DIRECTORY_SEPARATOR.'*.stub'; + } + + + /** + * get table stub + * + * @return string + */ + protected function getTableStub(): string + { + return dirname(__DIR__).DIRECTORY_SEPARATOR.'stubs' + + .DIRECTORY_SEPARATOR.'vue'.DIRECTORY_SEPARATOR.'table.stub'; + } + + /** + * get paginate stub content + * + * @return string + */ + protected function getPaginateStubContent(): string + { + return $this->hasPaginate ? + File::get( + dirname(__DIR__).DIRECTORY_SEPARATOR + + .'stubs'.DIRECTORY_SEPARATOR.'vue'. + + DIRECTORY_SEPARATOR.'paginate.stub' + ) + : ''; + } + + /** + * get use List + * @return string + */ + protected function getUseList(): string + { + return $this->hasPaginate ? + 'const { data, query, search, reset, changePage, changeLimit, loading } = useGetList(api)' : + 'const { data, query, search, reset, loading } = useGetList(api)'; + } + + /** + * set structures + * + * @param array $structures + * @return $this + */ + public function setStructures(array $structures): static + { + $this->structures = $structures; + + return $this; + } +} diff --git a/modules/Develop/Support/Generate/Create/Model.php b/modules/Develop/Support/Generate/Create/Model.php new file mode 100644 index 0000000..2a45eda --- /dev/null +++ b/modules/Develop/Support/Generate/Create/Model.php @@ -0,0 +1,276 @@ +softDelete = in_array($model->getDeletedAtColumn(), SchemaFacade::getColumnListing($this->tableName)); + } + + /** + * get file + * + * @return string + */ + public function getFile(): string + { + // TODO: Implement getFile() method. + return CatchAdmin::getModuleModelPath($this->module).$this->getModelName().$this->ext; + } + + /** + * get content + * + * @return string + */ + public function getContent(): string + { + $modelStub = File::get($this->getModelStub()); + + + return Str::of($modelStub)->replace($this->replace, [$this->getUses(), + $this->getProperties(), + $this->getModelNamespace(), + $this->getModelName(), + $this->getTraits(), + $this->tableName, + $this->getFillable(), + $this->getSearchable(), + $this->getFieldsInList(), + $this->isPaginate(), + $this->getInForm() + ])->toString(); + } + + /** + * get model namespace + * + * @return string + */ + public function getModelNamespace(): string + { + return Str::of(CatchAdmin::getModuleModelNamespace($this->module))->trim('\\')->append(';')->toString(); + } + + /** + * get model name + * + * @return string + */ + public function getModelName(): string + { + $modelName = Str::of($this->modelName); + + if (! $modelName->length()) { + $modelName = Str::of($this->tableName)->camel(); + } + + return $modelName->ucfirst()->whenContains('Model', function ($value) { + return Str::of($value); + }, function ($value) { + return Str::of($value)->append('Model'); + })->toString(); + } + + /** + * get uses + * + * @return string + */ + protected function getUses(): string + { + if (! $this->softDelete) { + return <<softDelete ? '' : 'use BaseOperate, Trans, ScopeTrait;'; + } + + /** + * + * @return string + */ + protected function getProperties(): string + { + $comment = Str::of('/**')->newLine(); + + foreach ($this->getTableColumns() as $column) { + $comment = $comment->append(sprintf(' * @property $%s', $column))->newLine(); + } + + return $comment->append('*/')->toString(); + } + + /** + * get fillable + * + * @return string + */ + protected function getFillable(): string + { + $fillable = Str::of(''); + + foreach ($this->getTableColumns() as $column) { + $fillable = $fillable->append(" '{$column}'")->append(','); + } + + return $fillable->rtrim(',')->toString(); + } + + + /** + * + * @return array + */ + protected function getTableColumns(): array + { + return getTableColumns($this->tableName); + } + + /** + * get field in list + * + * @return string + */ + protected function getFieldsInList(): string + { + $str = Str::of(''); + foreach ($this->structures as $structure) { + if ($structure['list']) { + $str = $str->append("'{$structure['field']}'")->append(','); + } + } + + return $str->rtrim(',')->toString(); + } + + /** + * get field in list + * + * @return string + */ + protected function getInForm(): string + { + $str = Str::of(''); + foreach ($this->structures as $structure) { + if ($structure['form']) { + $str = $str->append("'{$structure['field']}'")->append(','); + } + } + + return $str->rtrim(',')->toString(); + } + + /** + * searchable + * + * @return string + */ + protected function getSearchable(): string + { + $searchable = Str::of(''); + + foreach ($this->structures as $structure) { + if ($structure['search'] && $structure['field'] && $structure['search_op']) { + $searchable = $searchable->append(sprintf("'%s' => '%s'", $structure['field'], $structure['search_op']))->append(',')->newLine(); + } + } + + return $searchable->toString(); + } + + /** + * @return string + */ + protected function isPaginate(): string + { + return $this->isPaginate ? '' : Str::of('protected bool $isPaginate = false;')->toString(); + } + + /** + * @param array $structures + * @return $this + */ + public function setStructures(array $structures): static + { + $this->structures = $structures; + + return $this; + } + + /** + * get stub + * + * @return string + */ + protected function getModelStub(): string + { + return dirname(__DIR__).DIRECTORY_SEPARATOR.'stubs'.DIRECTORY_SEPARATOR.'model.stub'; + } +} diff --git a/modules/Develop/Support/Generate/Create/Request.php b/modules/Develop/Support/Generate/Create/Request.php new file mode 100644 index 0000000..6c1dec3 --- /dev/null +++ b/modules/Develop/Support/Generate/Create/Request.php @@ -0,0 +1,138 @@ +module).$this->getRequestName().$this->ext; + } + + /** + * get content + * + * @return string|bool + */ + public function getContent(): string|bool + { + $rule = $this->getRulesString(); + + if (! $rule) { + return false; + } + + return Str::of( + File::get(dirname(__DIR__).DIRECTORY_SEPARATOR.'stubs'.DIRECTORY_SEPARATOR.'request.stub') + )->replace($this->replace, [$this->getNamespace(), $this->getRequestName(), $rule])->toString(); + } + + /** + * get namespace + * + * @return string + */ + protected function getNamespace(): string + { + return Str::of(CatchAdmin::getModuleRequestNamespace($this->module))->rtrim('\\')->append(';')->toString(); + } + + /** + * get request name + * + * @return ?string + */ + public function getRequestName(): ?string + { + if ($this->getRules()) { + return Str::of($this->controller)->remove('Controller')->append('Request')->ucfirst()->toString(); + } + + return null; + } + + /** + * get rule + * + * @return string|bool + */ + public function getRulesString(): string|bool + { + $rules = $this->getRules(); + + if (! count($rules)) { + return false; + } + + $rule = Str::of(''); + + foreach ($rules as $field => $validates) { + $rule = $rule->append("'{$field}'") + ->append(' => ') + ->append('\'') + ->append(Arr::join($validates, '|')) + ->append('\',') + ->newLine(); + } + + return $rule->toString(); + } + + /** + * get rules + * + * @return array + */ + protected function getRules(): array + { + $rules = []; + + foreach ($this->structures as $structure) { + if ($structure['field'] && count($structure['validates'])) { + $rules[$structure['field']] = $structure['validates']; + } + } + + return $rules; + } + + /** + * set structures + * + * @param array $structures + * @return $this + */ + public function setStructures(array $structures): static + { + $this->structures = $structures; + + return $this; + } +} diff --git a/modules/Develop/Support/Generate/Create/Route.php b/modules/Develop/Support/Generate/Create/Route.php new file mode 100644 index 0000000..e4a0b73 --- /dev/null +++ b/modules/Develop/Support/Generate/Create/Route.php @@ -0,0 +1,123 @@ +module); + } + + /** + * get content + * + * @return string + */ + public function getContent(): string + { + // route 主要添加两个点 + // use Controller + // 添加路由 + $route = Str::of(''); + + $originContent = File::get(CatchAdmin::getModuleRoutePath($this->module)); + + // 如果已经有 controller,就不再追加路由 + if (Str::of($originContent)->contains($this->getUserController())) { + return $originContent; + } + + File::lines(CatchAdmin::getModuleRoutePath($this->module)) + ->each(function ($line) use (&$route) { + if (Str::of($line)->contains('Route::prefix')) { + $route = $route->trim(PHP_EOL) + ->newLine() + ->append($this->getUserController()) + ->append(';') + ->newLine(2) + ->append($line) + ->newLine(); + } else { + $route = $route->append($line)->newLine(); + } + }); + + $apiResource = "Route::apiResource('{api}', {controller}::class);"; + + return Str::of($route->toString())->replace( + ['{module}', '//next'], + [ + lcfirst($this->module), + Str::of($apiResource)->replace(['{api}', '{controller}'], [$this->getApiString(), $this->getControllerName()]) + ->prepend("\t") + ->prepend(PHP_EOL) + ->newLine()->append("\t//next")] + )->toString(); + } + + /** + * get api + * + * @return string + */ + public function getApiString(): string + { + return Str::of($this->getControllerName())->remove('Controller')->snake('_')->replace('_', '/')->toString(); + } + + /** + * get api route + * + * @return string + */ + public function getApiRute(): string + { + return lcfirst($this->module).'/'.$this->getApiString(); + } + + /** + * use controller + * + * @return string + */ + protected function getUserController(): string + { + return 'use '.CatchAdmin::getModuleControllerNamespace($this->module).$this->getControllerName(); + } + + /** + * get controller name + * + * @return string + */ + protected function getControllerName(): string + { + return Str::of($this->controller)->whenContains('Controller', function ($value) { + return Str::of($value)->ucfirst(); + }, function ($value) { + return Str::of($value)->append('Controller')->ucfirst(); + })->toString(); + } +} diff --git a/modules/Develop/Support/Generate/Create/Schema.php b/modules/Develop/Support/Generate/Create/Schema.php new file mode 100644 index 0000000..c8fcd9d --- /dev/null +++ b/modules/Develop/Support/Generate/Create/Schema.php @@ -0,0 +1,317 @@ +structures)) { + return false; + } + + if (MigrationSchema::hasTable($this->table)) { + throw new Exception(sprintf('[%s] 表已经存在', $this->table)); + } + + try { + $this->createTable(); + + if (MigrationSchema::hasTable($this->table)) { + return parent::create(); + } + + return false; + } catch (Exception $e) { + MigrationSchema::dropIfExists($this->table); + throw new Exception("由于{$e->getMessage()}, 表[{$this->table}]创建失败"); + } + } + + /** + * get file + * + * @return string + */ + public function getFile(): string + { + // TODO: Implement getFile() method. + return CatchAdmin::getModuleMigrationPath($this->module).date('Y_m_d_his_').'create_'.$this->table.'.php'; + } + + /** + * create table + * + * @throws Exception + */ + protected function createTable(): void + { + MigrationSchema::create($this->table, function (Blueprint $table) { + foreach ($this->structures as $structure) { + // if field && type hava value + if ($structure['type'] && $structure['field']) { + if ($structure['type'] == 'string') { + $column = $table->string($structure['field'], $structure['length'] ?: 255); + } elseif ($structure['type'] == 'char') { + $column = $table->char($structure['field'], $structure['length']); + } else { + $column = $table->{$structure['type']}($structure['field']); + } + + $column = $column->nullable($structure['nullable']); + + if ($structure['default']) { + $column = $column->default($structure['default']); + } + + if ($structure['comment']) { + $column = $column->comment($structure['comment']); + } + + if ($structure['unique']) { + $column->unique($structure['unique']); + } + } + } + + if ($this->creatorId) { + $table->creatorId(); + } + + if ($this->createdAt) { + $table->createdAt(); + } + + if ($this->updatedAt) { + $table->updatedAt(); + } + + if ($this->deletedAt) { + $table->deletedAt(); + } + + $table->charset = $this->charset; + $table->engine = $this->engine; + $table->collation = $this->collection; + $table->comment($this->comment); + }); + } + + /** + * get migration content + * + * @return string + */ + public function getContent(): string + { + $stub = File::get($this->getStub()); + + return Str::of($stub)->replace(['{method}','{table}', '{content}'], ['create', $this->table, $this->getMigrationContent()])->toString(); + } + + /** + * get content + * + * @return string + */ + public function getMigrationContent(): string + { + $content = Str::of(''); + + foreach ($this->structures as $structure) { + $begin = Str::of('$table->'); + $type = Str::of($structure['type']); + + if ($type->exactly('string')) { + $begin = $begin->append(sprintf("string('%s'%s)", $structure['field'], $structure['length'] ? ", {$structure['length']}" : '')); + } elseif ($type->exactly('char')) { + $begin = $begin->append(sprintf("char('%s', %s)", $structure['field'], $structure['length'])); + } elseif ($type->exactly('id')) { + $begin = $begin->append(Str::of($structure['field'])->exactly('id') ? 'id()' : sprintf("id('%s')", $structure['field'])); + } else { + $begin = $begin->append(sprintf("%s('%s')", $structure['type'], $structure['field'])); + } + + $content = $content->append($begin) + ->when($structure['nullable'], function ($str) { + return $str->append('->nullable()'); + }) + ->when(isset($structure['default']), function ($str, $default) { + if (is_numeric($default)) { + return $str->append("->default({$default})"); + } + + if ($default) { + return $str->append("->default('{$default}')"); + } + + return $str; + }) + ->when($structure['unique'], function ($str) { + return $str->append("->unique()"); + }) + ->when($structure['comment'], function ($str, $comment) { + return $str->append("->comment('{$comment}')"); + }) + ->append(';') + ->newLine(); + } + + if ($this->creatorId) { + $content = $content->append(Str::of('$table->')->append('creatorId();'))->newLine(); + } + + if ($this->createdAt) { + $content = $content->append(Str::of('$table->')->append('createdAt();'))->newLine(); + } + + if ($this->updatedAt) { + $content = $content->append(Str::of('$table->')->append('updatedAt();'))->newLine(); + } + + if ($this->deletedAt) { + $content = $content->append(Str::of('$table->')->append('deletedAt();'))->newLine(); + } + + return $content->newLine() + ->append("\$table->engine='{$this->engine}'") + ->append(';') + ->newLine() + ->append("\$table->comment('{$this->comment}')") + ->append(';') + ->toString(); + } + + /** + * @param bool $createdAt + * @return $this + */ + public function setCreatedAt(bool $createdAt): static + { + $this->createdAt = $createdAt; + + return $this; + } + + /** + * @param bool $updatedAt + * @return $this + */ + public function setUpdatedAt(bool $updatedAt): static + { + $this->updatedAt = $updatedAt; + + return $this; + } + + /** + * @param bool $deletedAt + * @return $this + */ + public function setDeletedAt(bool $deletedAt): static + { + $this->deletedAt = $deletedAt; + + return $this; + } + + /** + * @param bool $creatorId + * @return $this + */ + public function setCreatorId(bool $creatorId): static + { + $this->creatorId = $creatorId; + + return $this; + } + + /** + * @param array $structures + * @return $this + */ + public function setStructures(array $structures): static + { + $this->structures = $structures; + + return $this; + } + + /** + * get stub + * + * @return string + */ + protected function getStub(): string + { + return dirname(__DIR__).DIRECTORY_SEPARATOR.'stubs'.DIRECTORY_SEPARATOR.'migration.stub'; + } +} diff --git a/modules/Develop/Support/Generate/Generator.php b/modules/Develop/Support/Generate/Generator.php new file mode 100644 index 0000000..e172245 --- /dev/null +++ b/modules/Develop/Support/Generate/Generator.php @@ -0,0 +1,214 @@ +files[] = $this->createModel(); + + $this->files[] = $this->createRequest(); + + $this->files[] = $this->createController(); + + $this->files[] = $this->createFrontTable(); + + $this->files[] = $this->createFrontForm(); + + $this->files[] = $this->createRoute(); + } catch (Exception $e) { + $this->rollback(); + throw new FailedException($e->getMessage()); + } + + $this->files = []; + + return true; + } + + /** + * create route + * + * @throws FileNotFoundException + * @return bool|string + */ + public function createRoute(): bool|string + { + // 保存之前的 route 文件 + $route = new Route($this->gen['controller']); + + return $route->setModule($this->gen['module'])->create(); + } + + /** + * create font + * + * @throws FileNotFoundException + * @return bool|string|null + */ + public function createFrontTable(): bool|string|null + { + $table = new FrontTable($this->gen['controller'], $this->gen['paginate'], (new Route($this->gen['controller']))->setModule($this->gen['module'])->getApiRute()); + + return $table->setModule($this->gen['module'])->setStructures($this->structures)->create(); + } + + /** + * create font + * + * @throws FileNotFoundException + * @return bool|string|null + */ + public function createFrontForm(): bool|string|null + { + $form = new FrontForm($this->gen['controller']); + + return $form->setModule($this->gen['module'])->setStructures($this->structures)->create(); + } + + + /** + * create model + * + * @throws FileNotFoundException + * @return bool|string + */ + protected function createModel(): bool|string + { + $model = new Model($this->gen['model'], $this->gen['schema'], $this->gen['module']); + + $this->modelName = $model->getModelName(); + + return $model->setModule($this->gen['module'])->setStructures($this->structures)->create(); + } + + /** + * create request + * + * @throws FileNotFoundException + * @return bool|string + */ + protected function createRequest(): bool|string + { + $request = new Request($this->gen['controller']); + + $file = $request->setStructures($this->structures)->setModule($this->gen['module'])->create(); + + $this->requestName = $request->getRequestName(); + + return $file; + } + + /** + * create controller + * + * @throws FileNotFoundException + * @return bool|string + */ + protected function createController(): bool|string + { + $controller = new Controller($this->gen['controller'], $this->modelName, $this->requestName); + + return $controller->setModule($this->gen['module'])->create(); + } + + /** + * rollback + * + * @return void + */ + protected function rollback(): void + { + // delete controller & model & migration file + foreach ($this->files as $file) { + unlink($file); + } + + // 回填之前的 route 文件 + } + + + /** + * set params + * + * @param array $params + * @return $this + */ + public function setParams(array $params): Generator + { + $this->gen = $params['codeGen']; + + $this->structures = $params['structures']; + + return $this; + } +} diff --git a/modules/Develop/Support/Generate/Module.php b/modules/Develop/Support/Generate/Module.php new file mode 100644 index 0000000..4278cc9 --- /dev/null +++ b/modules/Develop/Support/Generate/Module.php @@ -0,0 +1,86 @@ +controller) { + CatchAdmin::getModuleControllerPath($this->module); + } + + if ($this->models) { + CatchAdmin::getModuleModelPath($this->module); + } + + if ($this->requests) { + CatchAdmin::getModuleRequestPath($this->module); + } + + if ($this->database) { + CatchAdmin::getModuleMigrationPath($this->module); + CatchAdmin::getModuleSeederPath($this->module); + } + + $this->createProvider(); + + $this->createRoute(); + } + + + /** + * delete + * + * @return void + */ + public function delete(): void + { + } + + /** + * create provider + * + * @return void + */ + protected function createProvider(): void + { + CatchAdmin::getModuleProviderPath($this->module); + + File::put( + CatchAdmin::getModuleProviderPath($this->module).sprintf('%sServiceProvider.php', ucfirst($this->module)), + Str::of( + File::get(__DIR__.DIRECTORY_SEPARATOR.'stubs'.DIRECTORY_SEPARATOR.'provider.stub') + )->replace(['{Module}', '{module}'], [ucfirst($this->module), $this->module]) + ); + } + + + /** + * create route + * + * @return void + */ + protected function createRoute(): void + { + File::copy(__DIR__.DIRECTORY_SEPARATOR.'stubs'.DIRECTORY_SEPARATOR.'route.stub', CatchAdmin::getModuleRoutePath($this->module)); + } +} diff --git a/modules/Develop/Support/Generate/stubs/controller.stub b/modules/Develop/Support/Generate/stubs/controller.stub new file mode 100644 index 0000000..dc68af8 --- /dev/null +++ b/modules/Develop/Support/Generate/stubs/controller.stub @@ -0,0 +1,43 @@ +model->getList(); + } + + public function store({request} $request) + { + return $this->model->storeBy($request->all()); + } + + public function show($id) + { + return $this->model->firstBy($id); + } + + public function update($id, {request} $request) + { + return $this->model->updateBy($id, $request->all()); + } + + public function destroy($id) + { + return $this->model->deleteBy($id); + } +} diff --git a/modules/Develop/Support/Generate/stubs/migration.stub b/modules/Develop/Support/Generate/stubs/migration.stub new file mode 100644 index 0000000..3766219 --- /dev/null +++ b/modules/Develop/Support/Generate/stubs/migration.stub @@ -0,0 +1,30 @@ +group(function(){ + //next +}); \ No newline at end of file diff --git a/modules/Develop/Support/Generate/stubs/vue/form.stub b/modules/Develop/Support/Generate/stubs/vue/form.stub new file mode 100644 index 0000000..79683af --- /dev/null +++ b/modules/Develop/Support/Generate/stubs/vue/form.stub @@ -0,0 +1,38 @@ + + + diff --git a/modules/Develop/Support/Generate/stubs/vue/formItems/cascader.stub b/modules/Develop/Support/Generate/stubs/vue/formItems/cascader.stub new file mode 100644 index 0000000..1f25e6e --- /dev/null +++ b/modules/Develop/Support/Generate/stubs/vue/formItems/cascader.stub @@ -0,0 +1,3 @@ + + + diff --git a/modules/Develop/Support/Generate/stubs/vue/formItems/date.stub b/modules/Develop/Support/Generate/stubs/vue/formItems/date.stub new file mode 100644 index 0000000..26db0d1 --- /dev/null +++ b/modules/Develop/Support/Generate/stubs/vue/formItems/date.stub @@ -0,0 +1,9 @@ + + + diff --git a/modules/Develop/Support/Generate/stubs/vue/formItems/datetime.stub b/modules/Develop/Support/Generate/stubs/vue/formItems/datetime.stub new file mode 100644 index 0000000..e0e7552 --- /dev/null +++ b/modules/Develop/Support/Generate/stubs/vue/formItems/datetime.stub @@ -0,0 +1,9 @@ + + + diff --git a/modules/Develop/Support/Generate/stubs/vue/formItems/input-number.stub b/modules/Develop/Support/Generate/stubs/vue/formItems/input-number.stub new file mode 100644 index 0000000..141cd19 --- /dev/null +++ b/modules/Develop/Support/Generate/stubs/vue/formItems/input-number.stub @@ -0,0 +1,3 @@ + + + diff --git a/modules/Develop/Support/Generate/stubs/vue/formItems/input.stub b/modules/Develop/Support/Generate/stubs/vue/formItems/input.stub new file mode 100644 index 0000000..bdcf83c --- /dev/null +++ b/modules/Develop/Support/Generate/stubs/vue/formItems/input.stub @@ -0,0 +1,3 @@ + + + diff --git a/modules/Develop/Support/Generate/stubs/vue/formItems/radio.stub b/modules/Develop/Support/Generate/stubs/vue/formItems/radio.stub new file mode 100644 index 0000000..68b9365 --- /dev/null +++ b/modules/Develop/Support/Generate/stubs/vue/formItems/radio.stub @@ -0,0 +1,5 @@ + + + {{ item.label }} + + diff --git a/modules/Develop/Support/Generate/stubs/vue/formItems/rate.stub b/modules/Develop/Support/Generate/stubs/vue/formItems/rate.stub new file mode 100644 index 0000000..cf1d7b5 --- /dev/null +++ b/modules/Develop/Support/Generate/stubs/vue/formItems/rate.stub @@ -0,0 +1,3 @@ + + + diff --git a/modules/Develop/Support/Generate/stubs/vue/formItems/select.stub b/modules/Develop/Support/Generate/stubs/vue/formItems/select.stub new file mode 100644 index 0000000..2da70cd --- /dev/null +++ b/modules/Develop/Support/Generate/stubs/vue/formItems/select.stub @@ -0,0 +1,10 @@ + + + + + diff --git a/modules/Develop/Support/Generate/stubs/vue/formItems/switch.stub b/modules/Develop/Support/Generate/stubs/vue/formItems/switch.stub new file mode 100644 index 0000000..84759bc --- /dev/null +++ b/modules/Develop/Support/Generate/stubs/vue/formItems/switch.stub @@ -0,0 +1,3 @@ + + + diff --git a/modules/Develop/Support/Generate/stubs/vue/formItems/tree-select.stub b/modules/Develop/Support/Generate/stubs/vue/formItems/tree-select.stub new file mode 100644 index 0000000..c758a79 --- /dev/null +++ b/modules/Develop/Support/Generate/stubs/vue/formItems/tree-select.stub @@ -0,0 +1,9 @@ + + + diff --git a/modules/Develop/Support/Generate/stubs/vue/formItems/tree.stub b/modules/Develop/Support/Generate/stubs/vue/formItems/tree.stub new file mode 100644 index 0000000..381c398 --- /dev/null +++ b/modules/Develop/Support/Generate/stubs/vue/formItems/tree.stub @@ -0,0 +1,8 @@ + + + + diff --git a/modules/Develop/Support/Generate/stubs/vue/paginate.stub b/modules/Develop/Support/Generate/stubs/vue/paginate.stub new file mode 100644 index 0000000..7058654 --- /dev/null +++ b/modules/Develop/Support/Generate/stubs/vue/paginate.stub @@ -0,0 +1,12 @@ +
+ +
diff --git a/modules/Develop/Support/Generate/stubs/vue/table.stub b/modules/Develop/Support/Generate/stubs/vue/table.stub new file mode 100644 index 0000000..0e507b1 --- /dev/null +++ b/modules/Develop/Support/Generate/stubs/vue/table.stub @@ -0,0 +1,78 @@ + + + diff --git a/modules/Develop/database/migrations/Schemas.php b/modules/Develop/database/migrations/Schemas.php new file mode 100644 index 0000000..3044d41 --- /dev/null +++ b/modules/Develop/database/migrations/Schemas.php @@ -0,0 +1,28 @@ +increments('id'); + + $table->string('module')->nullable(false)->comment('模块名称'); + + $table->string('name')->nullable(false)->comment('schema 名称'); + + $table->string('columns')->nullable(false)->comment('字段'); + + $table->boolean('is_soft_delete')->default(1)->comment('1 是 2 否'); + + $table->createdAt(); + + $table->updatedAt(); + + $table->deletedAt(); + }); + } +}; diff --git a/modules/Develop/route.php b/modules/Develop/route.php new file mode 100644 index 0000000..f4575de --- /dev/null +++ b/modules/Develop/route.php @@ -0,0 +1,14 @@ +only(['index', 'show', 'store', 'destroy']); diff --git a/modules/Develop/views/generate/components/codeGen.vue b/modules/Develop/views/generate/components/codeGen.vue new file mode 100644 index 0000000..09c2d25 --- /dev/null +++ b/modules/Develop/views/generate/components/codeGen.vue @@ -0,0 +1,109 @@ + + diff --git a/modules/Develop/views/generate/components/store.ts b/modules/Develop/views/generate/components/store.ts new file mode 100644 index 0000000..8bee987 --- /dev/null +++ b/modules/Develop/views/generate/components/store.ts @@ -0,0 +1,112 @@ +import { defineStore } from 'pinia' + +/** + * 表结构信息 + */ +export interface Structure { + field: string + label: string + form_component: string + list: boolean + form: boolean + search: boolean + search_op: string + validates: string[] +} + +/** + * CodeGen + */ +export interface CodeGen { + module: string + controller: string + model: string + paginate: true + schema: string +} + +/** + * generate + */ +interface generate { + schemaId: number + structures: Structure[] + codeGen: CodeGen +} + +/** + * useGenerateStore + */ +export const useGenerateStore = defineStore('generateStore', { + state(): generate { + return { + // schema id + schemaId: 0, + // structures + structures: [] as Structure[], + // codeGen + codeGen: Object.assign({ + module: '', + controller: '', + model: '', + paginate: true, + schema: '', + }), + } + }, + + // store getters + getters: { + getSchemaId(): any { + return this.schemaId + }, + + getStructures(): Structure[] { + return this.structures + }, + + getCodeGen(): CodeGen { + return this.codeGen + }, + }, + + // store actions + actions: { + // set schema + setSchemaId(schemaId: any): void { + this.schemaId = schemaId + }, + // reset + resetStructures(): void { + this.structures = [] + }, + // filter structures + filterStructures(field: string) { + this.structures = this.structures.filter((s: Structure) => { + return !(s.field === field) + }) + }, + + // init structure + initStructures(fields: Array): void { + const unSupportFields = ['deleted_at', 'creator_id'] + + fields.forEach(field => { + if (!unSupportFields.includes(field.name)) { + this.structures.push( + Object.assign({ + field: field.name, + label: '', + form_component: 'input', + list: true, + form: true, + search: false, + search_op: '', + validates: [], + }), + ) + } + }) + }, + }, +}) diff --git a/modules/Develop/views/generate/components/structure.vue b/modules/Develop/views/generate/components/structure.vue new file mode 100644 index 0000000..c5b5972 --- /dev/null +++ b/modules/Develop/views/generate/components/structure.vue @@ -0,0 +1,99 @@ + + diff --git a/modules/Develop/views/generate/index.vue b/modules/Develop/views/generate/index.vue new file mode 100644 index 0000000..9d7b697 --- /dev/null +++ b/modules/Develop/views/generate/index.vue @@ -0,0 +1,10 @@ + + diff --git a/modules/Develop/views/module/create.vue b/modules/Develop/views/module/create.vue new file mode 100644 index 0000000..b744625 --- /dev/null +++ b/modules/Develop/views/module/create.vue @@ -0,0 +1,89 @@ + + + diff --git a/modules/Develop/views/module/index.vue b/modules/Develop/views/module/index.vue new file mode 100644 index 0000000..53a3a52 --- /dev/null +++ b/modules/Develop/views/module/index.vue @@ -0,0 +1,103 @@ + + + diff --git a/modules/Develop/views/router.ts b/modules/Develop/views/router.ts new file mode 100644 index 0000000..a65646d --- /dev/null +++ b/modules/Develop/views/router.ts @@ -0,0 +1,32 @@ +import { RouteRecordRaw } from 'vue-router' + +// @ts-ignore +const router: RouteRecordRaw[] = [ + { + path: '/develop', + component: () => import('/admin/layout/index.vue'), + meta: { title: '开发工具', icon: 'wrench-screwdriver', hidden: false }, + children: [ + { + path: 'modules', + name: 'modules', + meta: { title: '模块管理', icon: 'home', hidden: false }, + component: () => import('./module/index.vue'), + }, + { + path: 'schemas', + name: 'schemas', + meta: { title: 'Schemas', icon: 'home', hidden: false }, + component: () => import('./schema/index.vue'), + }, + { + path: 'generate/:schema', + name: 'generate', + meta: { title: '代码生成', hidden: true, is_inner: true }, + component: () => import('./generate/index.vue'), + }, + ], + }, +] + +export default router diff --git a/modules/Develop/views/schema/create.vue b/modules/Develop/views/schema/create.vue new file mode 100644 index 0000000..49832df --- /dev/null +++ b/modules/Develop/views/schema/create.vue @@ -0,0 +1,35 @@ + + diff --git a/modules/Develop/views/schema/index.vue b/modules/Develop/views/schema/index.vue new file mode 100644 index 0000000..39bc476 --- /dev/null +++ b/modules/Develop/views/schema/index.vue @@ -0,0 +1,120 @@ + + + diff --git a/modules/Develop/views/schema/show.vue b/modules/Develop/views/schema/show.vue new file mode 100644 index 0000000..39f90b2 --- /dev/null +++ b/modules/Develop/views/schema/show.vue @@ -0,0 +1,38 @@ + + + + + diff --git a/modules/Develop/views/schema/steps/schema.vue b/modules/Develop/views/schema/steps/schema.vue new file mode 100644 index 0000000..9ebff65 --- /dev/null +++ b/modules/Develop/views/schema/steps/schema.vue @@ -0,0 +1,107 @@ + + diff --git a/modules/Develop/views/schema/steps/structure.vue b/modules/Develop/views/schema/steps/structure.vue new file mode 100644 index 0000000..8bf8028 --- /dev/null +++ b/modules/Develop/views/schema/steps/structure.vue @@ -0,0 +1,231 @@ + + diff --git a/modules/Develop/views/schema/store/index.ts b/modules/Develop/views/schema/store/index.ts new file mode 100644 index 0000000..4ace1c5 --- /dev/null +++ b/modules/Develop/views/schema/store/index.ts @@ -0,0 +1,146 @@ +import { defineStore } from 'pinia' + +/** + * 表信息 + */ +export interface Schema { + module: string + name: string + comment: string + engine: string + charset: string + collaction: string + created_at: boolean + creator_id: boolean + updated_at: boolean + deleted_at: boolean +} + +/** + * 表结构信息 + */ +export interface Structure { + id: number + field: string + length: number + type: string + nullable: boolean + unique: boolean + default: number | string + comment: string +} + +/** + * generate + */ +interface CreateSchema { + schema: Schema + structures: Structure[] + is_finished: boolean +} + +/** + * useSchemaStore + */ +export const useSchemaStore = defineStore('schemaStore', { + state(): CreateSchema { + return { + // schema + schema: Object.assign({ + module: '', + name: '', + comment: '', + engine: 'InnoDB', + charset: 'utf8mb4', + collection: 'utf8mb4_unicode_ci', + created_at: true, + creator_id: true, + updated_at: true, + deleted_at: true, + }), + // structures + structures: [] as Structure[], + + // is finished + is_finished: false, + } + }, + + // store getters + getters: { + getSchema(): Schema { + return this.schema + }, + + getStructures(): Structure[] { + return this.structures + }, + + getFinished(): boolean { + return this.is_finished + }, + }, + + // store actions + actions: { + // set schema + setSchema(schema: Schema): void { + this.schema = schema + }, + + setStructures(structures: Array): void { + this.structures = structures + }, + // add structure + addStructure(structure: Structure): void { + if (structure.id) { + this.structures = this.structures.filter((s: Structure) => { + if (s.id === structure.id) { + s = structure + } + return s + }) + } else { + structure.id = this.structures.length + 1 + this.structures.push(structure) + } + }, + + // filter structures + filterStructures(id: number) { + this.structures = this.structures.filter((s: Structure) => { + return !(s.id === id) + }) + }, + + // init structure + initStructure(): Structure { + return Object.assign({ + id: 0, + field: '', + label: '', + type: '', + length: 0, + nullable: false, + unique: false, + default: '', + comment: '', + }) + }, + + /** + * finished + */ + finished(): void { + this.$reset() + this.is_finished = true + }, + + /** + * unfinished + */ + start(): void { + this.is_finished = false + }, + }, +}) diff --git a/modules/Options/Http/OptionController.php b/modules/Options/Http/OptionController.php new file mode 100644 index 0000000..2a3ec52 --- /dev/null +++ b/modules/Options/Http/OptionController.php @@ -0,0 +1,20 @@ +make($name)->get(); + } +} diff --git a/modules/Options/README.md b/modules/Options/README.md new file mode 100644 index 0000000..43091d9 --- /dev/null +++ b/modules/Options/README.md @@ -0,0 +1,2 @@ +## 介绍 +这是一个公共模块,不耦合其他项目,用于给前端提供统一 select options 数据接口 diff --git a/modules/Options/Repository/DataRange.php b/modules/Options/Repository/DataRange.php new file mode 100644 index 0000000..9b91a6e --- /dev/null +++ b/modules/Options/Repository/DataRange.php @@ -0,0 +1,38 @@ + DataRangeEnum::All_Data->name(), + 'value' => DataRangeEnum::All_Data->value() + ], + + [ + 'label' => DataRangeEnum::Personal_Choose->name(), + 'value' => DataRangeEnum::Personal_Choose->value() + ], + + [ + 'label' => DataRangeEnum::Personal_Data->name(), + 'value' => DataRangeEnum::Personal_Data->value() + ], + + [ + 'label' => DataRangeEnum::Department_Data->name(), + 'value' => DataRangeEnum::Department_Data->value() + ], + + [ + 'label' => DataRangeEnum::Department_DOWN_Data->name(), + 'value' => DataRangeEnum::Department_DOWN_Data->value() + ] + ]; + } +} diff --git a/modules/Options/Repository/Factory.php b/modules/Options/Repository/Factory.php new file mode 100644 index 0000000..92ccc9b --- /dev/null +++ b/modules/Options/Repository/Factory.php @@ -0,0 +1,28 @@ +ucfirst()->toString(); + + $class = new $className(); + + if (! $class instanceof OptionInterface) { + throw new Exception('option must be implement [OptionInterface]'); + } + + return $class; + } +} diff --git a/modules/Options/Repository/Modules.php b/modules/Options/Repository/Modules.php new file mode 100644 index 0000000..b9890cd --- /dev/null +++ b/modules/Options/Repository/Modules.php @@ -0,0 +1,25 @@ +all([]) + + ->each(function ($module) use (&$modules) { + $modules[] = [ + 'label' => $module['name'], + + 'value' => $module['path'] + ]; + }); + + return $modules; + } +} diff --git a/modules/Options/Repository/OptionInterface.php b/modules/Options/Repository/OptionInterface.php new file mode 100644 index 0000000..f1d8343 --- /dev/null +++ b/modules/Options/Repository/OptionInterface.php @@ -0,0 +1,11 @@ + StatusEnum::Enable->name(), + 'value' => StatusEnum::Enable->value() + ], + + [ + 'label' => StatusEnum::Disable->name(), + 'value' => StatusEnum::Disable->value() + ] + ]; + } +} diff --git a/modules/Permissions/Enums/DataRange.php b/modules/Permissions/Enums/DataRange.php new file mode 100644 index 0000000..cd4fc38 --- /dev/null +++ b/modules/Permissions/Enums/DataRange.php @@ -0,0 +1,38 @@ + 1, + self::Personal_Choose => 2, + self::Personal_Data => 3, + self::Department_Data => 4, + self::Department_DOWN_Data => 5, + }; + } + + public function name(): string + { + // TODO: Implement name() method. + return match ($this) { + self::All_Data => '全部数据', + self::Personal_Choose => '自定义数据', + self::Personal_Data => '本人数据', + self::Department_Data => '部门数据', + self::Department_DOWN_Data => '部门及以下数据', + }; + } +} diff --git a/modules/Permissions/Http/Controllers/RolesController.php b/modules/Permissions/Http/Controllers/RolesController.php new file mode 100644 index 0000000..b542ef7 --- /dev/null +++ b/modules/Permissions/Http/Controllers/RolesController.php @@ -0,0 +1,45 @@ +model->getList(); + } + + public function store(RoleRequest $request) + { + return $this->model->storeBy($request->all()); + } + + public function show($id) + { + return $this->model->firstBy($id); + } + + public function update($id, RoleRequest $request) + { + return $this->model->updateBy($id, $request->all()); + } + + public function destroy($id) + { + return $this->model->deleteBy($id); + } +} diff --git a/modules/Permissions/Http/Requests/RoleRequest.php b/modules/Permissions/Http/Requests/RoleRequest.php new file mode 100644 index 0000000..641f5bd --- /dev/null +++ b/modules/Permissions/Http/Requests/RoleRequest.php @@ -0,0 +1,44 @@ + sprintf('required|unique:%s,%s,%s', RolesModel::class, 'role_name', $this->get('id')), + + 'identify' => sprintf('required|alpha|unique:%s,%s,%s', RolesModel::class, 'role_name', $this->get('id')), + ]; + } + + + /** + * messages + * + * @return string[] + */ + public function messages(): array + { + return [ + 'role_name.required' => '角色名称必须填写', + + 'role_name.unique' => '角色名称已存在', + + 'identify.required' => '角色标识必须填写', + + 'identify.alpha' => '角色名称只允许字母组成', + + 'identify.unique' => '角色标识已存在', + ]; + } +} \ No newline at end of file diff --git a/modules/Permissions/Models/RolesModel.php b/modules/Permissions/Models/RolesModel.php new file mode 100644 index 0000000..228c3b8 --- /dev/null +++ b/modules/Permissions/Models/RolesModel.php @@ -0,0 +1,53 @@ + 'like', + ]; + + + /** + * @return mixed + */ + public function getList(): mixed + { + return self::query()->select($this->fieldsInList)->quickSearch()->get()->toTree(); + } + +} diff --git a/modules/Permissions/Providers/PermissionsServiceProvider.php b/modules/Permissions/Providers/PermissionsServiceProvider.php new file mode 100644 index 0000000..0712f8b --- /dev/null +++ b/modules/Permissions/Providers/PermissionsServiceProvider.php @@ -0,0 +1,20 @@ +increments('id'); + $table->string('role_name', 30)->comment('角色名称'); + $table->string('identify', 30)->nullable()->comment('角色的标识,用英文表示'); + $table->integer('parent_id')->default(0)->comment('父级ID'); + $table->string('description')->nullable()->comment('角色描述'); + $table->smallInteger('data_range')->default(0)->comment('1 全部数据 2 自定义数据 3 仅本人数据 4 部门数据 5 部门及以下数据'); + $table->creatorId(); + $table->createdAt(); + $table->updatedAt(); + $table->deletedAt(); + + $table->engine = 'InnoDB'; + $table->comment('角色表'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('roles'); + } +}; diff --git a/modules/Permissions/route.php b/modules/Permissions/route.php new file mode 100644 index 0000000..d0ec417 --- /dev/null +++ b/modules/Permissions/route.php @@ -0,0 +1,11 @@ +group(function () { + + Route::apiResource('roles', RolesController::class); + //next +}); + diff --git a/modules/Permissions/views/roles/create.vue b/modules/Permissions/views/roles/create.vue new file mode 100644 index 0000000..851e946 --- /dev/null +++ b/modules/Permissions/views/roles/create.vue @@ -0,0 +1,95 @@ + + + diff --git a/modules/Permissions/views/roles/index.vue b/modules/Permissions/views/roles/index.vue new file mode 100644 index 0000000..b16cf4c --- /dev/null +++ b/modules/Permissions/views/roles/index.vue @@ -0,0 +1,80 @@ + + + diff --git a/modules/Permissions/views/router.ts b/modules/Permissions/views/router.ts new file mode 100644 index 0000000..0d4cf16 --- /dev/null +++ b/modules/Permissions/views/router.ts @@ -0,0 +1,20 @@ +import { RouteRecordRaw } from 'vue-router' + +// @ts-ignore +const router: RouteRecordRaw[] = [ + { + path: '/permission', + component: () => import('/admin/layout/index.vue'), + meta: { title: '权限管理', icon: 'user' }, + children: [ + { + path: 'roles', + name: 'roles', + meta: { title: '角色管理', icon: 'home' }, + component: () => import('./roles/index.vue'), + }, + ], + }, +] + +export default router diff --git a/modules/User/Events/Login.php b/modules/User/Events/Login.php new file mode 100644 index 0000000..8866f35 --- /dev/null +++ b/modules/User/Events/Login.php @@ -0,0 +1,24 @@ +attempt($request->only(['email', 'password'])); + + Event::dispatch(new Login($request, $token)); + + if (! $token) { + throw new FailedException('登录失败!请检查邮箱或者密码'); + } + + return compact('token'); + } + + + /** + * logout + * + * @return bool + */ + public function logout() + { + // Auth::guard(Helper::getGuardName())->logout(); + + return true; + } +} diff --git a/modules/User/Http/Controllers/UserController.php b/modules/User/Http/Controllers/UserController.php new file mode 100644 index 0000000..704879c --- /dev/null +++ b/modules/User/Http/Controllers/UserController.php @@ -0,0 +1,113 @@ +user->getList(); + } + + /** + * store + * + * @param Request $request + * @return false|mixed + */ + public function store(Request $request) + { + return $this->user->storeBy($request->all()); + } + + /** + * show + * + * @param $id + * @return mixed + */ + public function show($id) + { + return $this->user->firstBy($id)->makeHidden('password'); + } + + /** + * update + * + * @param $id + * @param Request $request + * @return mixed + */ + public function update($id, Request $request) + { + return $this->user->updateBy($id, $request->all()); + } + + /** + * destroy + * + * @param $id + * @return bool|null + */ + public function destroy($id) + { + return $this->user->deleteBy($id); + } + + /** + * enable + * + * @param $id + * @return bool + */ + public function enable($id) + { + return $this->user->disOrEnable($id); + } + + /** + * online user + * + * @return Authenticatable + */ + public function online(Request $request) + { + /* @var Users $user */ + $user = $this->getLoginUser(); + + if ($request->isMethod('post')) { + return $user->updateBy($user->id, $request->all()); + } + + return $user; + } + + + /** + * login log + * @param LogLogin $logLogin + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + * @throws \Psr\Container\ContainerExceptionInterface + * @throws \Psr\Container\NotFoundExceptionInterface + */ + public function loginLog(LogLogin $logLogin) + { + return $logLogin->getUserLogBy($this->getLoginUser()->email); + } +} diff --git a/modules/User/Listeners/Login.php b/modules/User/Listeners/Login.php new file mode 100644 index 0000000..92925b1 --- /dev/null +++ b/modules/User/Listeners/Login.php @@ -0,0 +1,96 @@ +request; + + $this->log($request, (bool) $event->token); + + if ($event->token) { + /* @var Users $user */ + $user = Auth::guard(getGuardName())->user(); + + $user->login_ip = $request->ip(); + $user->login_at = time(); + $user->remember_token = $event->token; + $user->save(); + } + } + + + /** + * login log + * + * @param Request $request + * @param int $isSuccess + * @return void + */ + protected function log(Request $request, int $isSuccess): void + { + LogLogin::insert([ + 'account' => $request->get('email'), + 'login_ip' => $request->ip(), + 'browser' => $this->getBrowserFrom(Str::of($request->userAgent())), + 'platform' => $this->getPlatformFrom(Str::of($request->userAgent())), + 'login_at' => time(), + 'status' => $isSuccess ? Status::Enable : Status::Disable + ]); + } + + + /** + * get platform + * + * @param Stringable $userAgent + * @return string + */ + protected function getBrowserFrom(Stringable $userAgent): string + { + return match (true) { + $userAgent->contains('MSIE', true) => 'IE', + $userAgent->contains('Firefox', true) => 'Firefox', + $userAgent->contains('Chrome', true) => 'Chrome', + $userAgent->contains('Opera', true) => 'Opera', + $userAgent->contains('Safari', true) => 'Safari', + default => 'unknown' + }; + } + + + /** + * get os name + * + * @param Stringable $userAgent + * @return string + */ + protected function getPlatformFrom(Stringable $userAgent): string + { + return match (true) { + $userAgent->contains('win', true) => 'Windows', + $userAgent->contains('mac', true) => 'Mac OS', + $userAgent->contains('linux', true) => 'Linux', + $userAgent->contains('iphone', true) => 'iphone', + $userAgent->contains('android', true) => 'Android', + default => 'unknown' + }; + } +} diff --git a/modules/User/Models/LogLogin.php b/modules/User/Models/LogLogin.php new file mode 100644 index 0000000..d6e99fd --- /dev/null +++ b/modules/User/Models/LogLogin.php @@ -0,0 +1,37 @@ + 'datetime:Y-m-d H:i' + ]; + + /** + * + * @param string $email + * @return LengthAwarePaginator + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + */ + public function getUserLogBy(string $email): LengthAwarePaginator + { + return self::query()->where('account', $email) + ->paginate(request()->get('limit', 10)); + } +} diff --git a/modules/User/Models/Users.php b/modules/User/Models/Users.php new file mode 100644 index 0000000..5d2e436 --- /dev/null +++ b/modules/User/Models/Users.php @@ -0,0 +1,98 @@ + 'like', + 'email' => 'like', + 'status' => '=' + ]; + + /** + * @var string + */ + protected $table = 'users'; + + /** + * @var array|string[] + */ + protected array $form = ['username', 'email', 'password']; + + /** + * + * @return mixed + */ + public function getJWTIdentifier(): mixed + { + return $this->getKey(); + } + + /** + * Return a key value array, containing any custom claims to be added to the JWT. + * + * @return array + */ + public function getJWTCustomClaims(): array + { + return []; + } + + /** + * password + * + * @return Attribute + */ + protected function password(): Attribute + { + return new Attribute( + // get: fn($value) => '', + set: fn ($value) => bcrypt($value), + ); + } + + /** + * update + * @param $id + * @param array $data + * @return mixed + */ + public function updateBy($id, array $data): mixed + { + if (isset($data['password']) && ! $data['password']) { + unset($data['password']); + } + + return parent::updateBy($id, $data); + } +} diff --git a/modules/User/Providers/UserServiceProvider.php b/modules/User/Providers/UserServiceProvider.php new file mode 100644 index 0000000..b39b540 --- /dev/null +++ b/modules/User/Providers/UserServiceProvider.php @@ -0,0 +1,32 @@ + LoginListener::class + ]; + + /** + * route path + * + * @return string|array + */ + public function routePath(): string|array + { + // TODO: Implement path() method. + return CatchAdmin::getModuleRoutePath('user'); + } + + + public function registerEvents(array $events): void + { + parent::registerEvents($events); // TODO: Change the autogenerated stub + } +} diff --git a/modules/User/database/migrations/2022_12_04_060250_create_users.php b/modules/User/database/migrations/2022_12_04_060250_create_users.php new file mode 100644 index 0000000..44bd3fe --- /dev/null +++ b/modules/User/database/migrations/2022_12_04_060250_create_users.php @@ -0,0 +1,54 @@ +increments('id'); + + $table->string('username')->comment('昵称'); + + $table->string('password')->comment('密码'); + + $table->string('email')->comment('邮箱'); + + $table->string('avatar')->comment('头像'); + + $table->string('remember_token', 1000)->comment('token'); + + $table->integer('creator_id'); + + $table->status(); + + $table->string('login_ip')->comment('登录IP'); + + $table->integer('login_at')->comment('登录时间'); + + $table->createdAt(); + + $table->updatedAt(); + + $table->deletedAt(); + + $table->comment('用户表'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down(): void + { + } +}; diff --git a/modules/User/database/migrations/2022_12_04_062539_create_log_login.php b/modules/User/database/migrations/2022_12_04_062539_create_log_login.php new file mode 100644 index 0000000..c31f6d2 --- /dev/null +++ b/modules/User/database/migrations/2022_12_04_062539_create_log_login.php @@ -0,0 +1,40 @@ +increments('id'); + + $table->string('account')->comment('登录账户'); + + $table->string('login_ip')->comment('登录的IP'); + + $table->string('browser')->comment('浏览器'); + + $table->string('platform')->comment('平台'); + + $table->integer('login_at')->comment('平台'); + + $table->status(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down(): void + { + } +}; diff --git a/modules/User/route.php b/modules/User/route.php new file mode 100644 index 0000000..a3ec8ac --- /dev/null +++ b/modules/User/route.php @@ -0,0 +1,15 @@ +withoutMiddleware(config('catch.route.middlewares')); +Route::post('logout', [AuthController::class, 'logout']); + +// users route +Route::apiResource('users', UserController::class); +Route::put('users/enable/{id}', [UserController::class, 'enable']); +Route::match(['post', 'get'], 'user/online', [UserController::class, 'online']); +Route::get('user/login/log', [UserController::class, 'loginLog']); diff --git a/modules/User/views/router.ts b/modules/User/views/router.ts new file mode 100644 index 0000000..a563712 --- /dev/null +++ b/modules/User/views/router.ts @@ -0,0 +1,26 @@ +import { RouteRecordRaw } from 'vue-router' + +// @ts-ignore +const router: RouteRecordRaw[] = [ + { + path: '/users', + component: () => import('/admin/layout/index.vue'), + meta: { title: '用户管理', icon: 'user' }, + children: [ + { + path: 'index', + name: 'users', + meta: { title: '账号管理', icon: 'home' }, + component: () => import('./user/index.vue'), + }, + { + path: 'center', + name: 'center', + meta: { title: '个人中心', icon: 'home' }, + component: () => import('./user/center.vue'), + }, + ], + }, +] + +export default router diff --git a/modules/User/views/user/center.vue b/modules/User/views/user/center.vue new file mode 100644 index 0000000..d74d1cb --- /dev/null +++ b/modules/User/views/user/center.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/modules/User/views/user/components/loginLog.vue b/modules/User/views/user/components/loginLog.vue new file mode 100644 index 0000000..fc7f35b --- /dev/null +++ b/modules/User/views/user/components/loginLog.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/modules/User/views/user/components/operateLog.vue b/modules/User/views/user/components/operateLog.vue new file mode 100644 index 0000000..2bb905d --- /dev/null +++ b/modules/User/views/user/components/operateLog.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/modules/User/views/user/components/profile.vue b/modules/User/views/user/components/profile.vue new file mode 100644 index 0000000..b535d45 --- /dev/null +++ b/modules/User/views/user/components/profile.vue @@ -0,0 +1,88 @@ + + + diff --git a/modules/User/views/user/create.vue b/modules/User/views/user/create.vue new file mode 100644 index 0000000..574d3a9 --- /dev/null +++ b/modules/User/views/user/create.vue @@ -0,0 +1,82 @@ + + + diff --git a/modules/User/views/user/index.vue b/modules/User/views/user/index.vue new file mode 100644 index 0000000..29b456c --- /dev/null +++ b/modules/User/views/user/index.vue @@ -0,0 +1,104 @@ + + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..a7d5d21 --- /dev/null +++ b/package.json @@ -0,0 +1,52 @@ +{ + "name": "catchadmin", + "private": false, + "version": "0.0.1", + "scripts": { + "dev": "vite", + "build": "vue-tsc --noEmit && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@heroicons/vue": "^2.0.13", + "@vueuse/core": "^9.5.0", + "autoprefixer": "^10.4.13", + "element-plus": "^2.2.25", + "nprogress": "^0.2.0", + "pinia": "^2.0.27", + "postcss": "^8.4.18", + "tailwindcss": "^3.2.2", + "terser": "^5.15.1", + "vue": "^3.2.44", + "vue-i18n": "9", + "vue-router": "4.1.6", + "vuedraggable": "^4.1.0" + }, + "devDependencies": { + "@iconify-json/logos": "^1.1.18", + "@rollup/plugin-alias": "^4.0.2", + "@types/mockjs": "^1.0.7", + "@types/node": "^18.11.9", + "@types/nprogress": "^0.2.0", + "@typescript-eslint/eslint-plugin": "^5.42.1", + "@typescript-eslint/parser": "^5.42.1", + "@vitejs/plugin-vue": "^3.2.0", + "@vitejs/plugin-vue-jsx": "^2.1.1", + "axios": "^1.1.3", + "eslint": "^8.27.0", + "eslint-config-standard": "^17.0.0", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.5.1", + "eslint-plugin-promise": "^6.1.1", + "eslint-plugin-vue": "^9.7.0", + "prettier": "2.8.0", + "sass": "^1.56.1", + "typescript": "^4.8.4", + "unplugin-auto-import": "^0.11.4", + "unplugin-icons": "^0.14.13", + "unplugin-vue-components": "^0.22.9", + "vite": "^3.2.3", + "vite-plugin-html": "^3.2.0", + "vue-tsc": "^1.0.9" + } +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..2ac86a1 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,31 @@ + + + + + ./tests/Unit + + + ./tests/Feature + + + + + ./app + + + + + + + + + + + + + + diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..85f717c --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {} + } +} diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..3aec5e2 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,21 @@ + + + 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] + diff --git a/public/admin.html b/public/admin.html new file mode 100644 index 0000000..e84ea0a --- /dev/null +++ b/public/admin.html @@ -0,0 +1,22 @@ + + + + + + + Vite App + + + +
+ + + diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..5f75a16d65d76ba9d297fcc0513bf40e0e429f89 GIT binary patch literal 16958 zcmds9e`pkU79Y2gSR0ewnavv0V1lGJ&CYCAQ+w7sOb{tjj-wRskaCDf|0v-oM=2r_ zl1PvsMS>iq6cH(DiJp{lks?hoI`{eP?xf%S&dmNu zHc82r=bQP?`+nbh-|zdr_r7l?Rg?|*ueVphzimp#0YynDiqZi*L(`qrqx~kCiEa1ny-qigYwC|gVKeI0FoO3%}Z$zw^eh9j9 zZ&PQ&1HgNZs>f&JPp{SztSXLR`!_cB!`I#29{IY%&^-#hKMTgkA7>wd5b- zNCM+BhE4!}5_m9{Fo*m%;2pprE0(HXGNUDSw4zDIxeUOIaUOn%IMBzOcd0*H>%w_3 zyQjn$>WwdB<`u{%^*)>>)#dOlSKj|_O+~Qw?*`~j*(^Nst%uXJCo-d^%8G%Qzato@ z9Nopapk3U=-Z-9lqO&YM=^B(jEr=z>d%Ke~!Kj_6Vz3cy> z{hxpV%<-GRBLMo|y8!C%(Ot8n2G5yQ_YY=N--K~53%op@#P{KscFOwO@l+k``K;U? zb`RRW28@3nZ3mcNi@zV$cE!?Cal{(-xk?%Ged!~P&qS-6cftR{4~MS()fu(Rx5l8S zY^O!Ji(RAc(*}C3sW`VdKN?NN`ps_9YhXJ(bA1k=U*KFEF4m`#XNX0NcRa(3a*5X` zrs@jU=k}Iv_zKEnyjm_VGoEIjVSwlZVBf|YPRsK}hsow+vE*jV17qcR}`E6@WKB@efukc~IsNcArt7kIX*)`>@9q zwIi2NFsFZ$>mji1@7BYn&V7=>Gvg^zYo;|A1|6=l&{ptfkPo z`Lhewq6s`1e?*kpl#&H`DSrZoM<7x_MYOX1Hx)A6TkU`Mn)N8-W|+ zPo^o*9F*l*3V$<})DhF^PfzFd@Oa4hlsvwPc!fU52eb3`sT{jy`f~2AM#MPP*o!gW z2Nau!8>l<@^3Y;DU^4~wOIqmt0`%uuBrNC2&I5eX8}iuj)!Wf;7C;&YvRADR0vq3m z{ojtcIEAsl1t9(u*eCNJzTPV9ha4@+TD(k*eSZkL?Za4;()b1IAEZC#PSl@1Yri~x zHSVVp9q==Tj`928CoTaq4;(esRDnM(#tZWIT3RwK(>SWZeSZvq_Z0>6TcAIl`SO0q zM7#0q-^F|^%LmA^1>JMc|3`T$QpsM!d5&@F9C8$09uq$2W$3}ZFWZ-_|ES#8){^vp z69A8PlJyLkZmsXJ#`IG-Lnqs0SrcBk$E<2NmnzP z5p;IDglC^hIt0xHa}l5aMBaM@?PBgmJ35)!)>U9DYcU7Y4ajR^+4uo{j1gG|r!kxN z++>zPgo790v%?ae2%$ekp0}S;{N?s(0K)K5F2@}_zFNhCa@<1_I{GF4|9{P^_7Z$ z9Oo+R-M@!4#*l*b_vY>+*i3=XSV5H+hn|V1_)qP#6Hr*js@aa z^f7OtzP_D#$#Q?J6~e#lKpu+R)^}EXfHn5jWb*rdKj`lRDBE?MBR9jZaSj+OM0vxm zaaJ;X@4p}iV*%?JrE=*h(42-}=(tl?`8&Q_TXgbatza1N>-j^JJ*K~7Zt>T~?X5gt z7sRVkJN67~>}>xG5T>)(&V4={RI8oVvc8ahHOLo;WAK``!r0jdNN4aJ!DP$sg1t%` z`Mz0?SX;XIj)C8pk(P3z<{B#NXV}w$IVfuHj8$H~_7Hm^TR)!K(am`9T9Gkj*~xz& z>!$M0j-}}fE&|G#1CC3&p8=eYZvj^T^v48V!*^t#k*3s`Um%`01??}?oN41z08f`& zXj`;n*zXQJyCXdJ=)&-I?6JlE;ymD6Aleh{pwwOn8@tkWuIb0fxdP>}5(a(VhX8Th z^G?gJH6bg}#q-=niAJOky1syU3(Rpcu{3KqwAV`JG>Av3Hu|y=n&A{l{=z5*Iqr^|T9;Ll~z2HKASmB`0={X25ZKp7~9qZzD~@T|!5R;fK)4)0qS z=XK0&9CJ?=z8`DKpm_-(`%2i0@q({Iz5!!0ACl**luSS4tbG#Sw1nn2tX(0_ZiYQF z*Wj6}+<5EprvLjKHu?(kaMezP=ab}xob)dtdR)##cZJHy@6*xmZGgwOT>JoY_pYO+ zRHr2wHoc4OW*jyVOCHFd(dnDM1WW)X0XG4(6`o^;p)>N;WP-K4Qa{-HCeWWmKXJc% zI$+=X>wxl@kS&4!Vyy7`mxHfijh~%TD}Q%2^W;+zvo&1GRW6( z-wnvRhTVgfX9U_@IXcTUchL`Pko29q!}u+y9dR7CWE=xe+Kw!XcRvm~-`ORMui2Qb zZ=NkOo;Pk7&iQ9eKZ@F6*?hy~32J9e54KK!aupC{@1AV4u#GhH|4M#%$HQ8}tP z`O_A8)_w#2p*iUMT!~EiZ_uO2ary3j98k(ee**1c{2fvyY_hc38n4#Hypepoxm*rr zuJS3M4>pmSi6!wZ9N)cnMVx3lGS!??zSDKZ-o96vs!vB|v`+q=K}|+W)x&mo!mk{K zF5KVDm1$q4WZ~Sr?j$PrMdD;(Z?$2Z>t*mOC6D-=^7H*6=a%obM}b+3>W7^G8|QG% zS`KYH0|@eI;&}2kbw6ot?hcK0qK>7m^K)YCN(cPOHY*Ij(89-@f(=~ z#=eVoRBy^Wv8U2yT{6Adkiy>w7_39dXN<7*sdhVkBI+Z?A?EE%R%DqSQDfI3#{~fM zzG~|jG#>T-7SMn30&eB@6U_X^-BYI*T$eFs*luEprF z|4{QtC2-E-`QajfIXTxWXdTOG<5_$hIYB@0ZWHg}tMTnuF&(P$jP>D+wkv`&e?9WH zPRP!Dl{pRHJpl;;M;vV20 zcdiL6%$~uy!I3<6Tz721of~|R#q%KJeO7{@em6TbusehYL;Zv982T#Q@FlOg$GClmT>h=NFV7N;@~N^lSjOeo=acfUo^B)H*ciy2JN| zTE|+!=-yC+S?kW%T0^zw2G?wG#%kv+7E7=|USNsPW^ bi?Kk9Uk_9x#t`g4RY*t{Bp4)i$H?jbC*YzR literal 0 HcmV?d00001 diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..1d69f3a --- /dev/null +++ b/public/index.php @@ -0,0 +1,55 @@ +make(Kernel::class); + +$response = $kernel->handle( + $request = Request::capture() +)->send(); + +$kernel->terminate($request, $response); diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..eb05362 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/resources/admin/App.vue b/resources/admin/App.vue new file mode 100644 index 0000000..98240ae --- /dev/null +++ b/resources/admin/App.vue @@ -0,0 +1,3 @@ + diff --git a/resources/admin/app.ts b/resources/admin/app.ts new file mode 100644 index 0000000..eeca805 --- /dev/null +++ b/resources/admin/app.ts @@ -0,0 +1,7 @@ +import '/admin/styles/index.scss' + +import CatchAdmin from './support/catchAdmin' + +const admin = new CatchAdmin() + +admin.bootstrap() diff --git a/resources/admin/assets/404.png b/resources/admin/assets/404.png new file mode 100644 index 0000000000000000000000000000000000000000..138814ad863072469d920b67dad80a4e58c6576e GIT binary patch literal 68972 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 E55LdCQ~&?~ literal 0 HcmV?d00001 diff --git a/resources/admin/assets/logo.png b/resources/admin/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c24a070ec35dd995c4fe56455e981d9a8c2e1695 GIT binary patch literal 9353 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 +
+ +
+
抱歉,您访问的页面不存在
+
+ 回到首页 +
+
+
+ + + diff --git a/resources/admin/components/HelloWorld.vue b/resources/admin/components/HelloWorld.vue new file mode 100644 index 0000000..ac2a1ab --- /dev/null +++ b/resources/admin/components/HelloWorld.vue @@ -0,0 +1,97 @@ + + + + diff --git a/resources/admin/components/admin/Select/index.vue b/resources/admin/components/admin/Select/index.vue new file mode 100644 index 0000000..3317a28 --- /dev/null +++ b/resources/admin/components/admin/Select/index.vue @@ -0,0 +1,49 @@ + + + diff --git a/resources/admin/components/admin/buttons/add.vue b/resources/admin/components/admin/buttons/add.vue new file mode 100644 index 0000000..cbb79ea --- /dev/null +++ b/resources/admin/components/admin/buttons/add.vue @@ -0,0 +1,16 @@ + + + diff --git a/resources/admin/components/admin/buttons/destroy.vue b/resources/admin/components/admin/buttons/destroy.vue new file mode 100644 index 0000000..0bdaff3 --- /dev/null +++ b/resources/admin/components/admin/buttons/destroy.vue @@ -0,0 +1,16 @@ + + + diff --git a/resources/admin/components/admin/buttons/show.vue b/resources/admin/components/admin/buttons/show.vue new file mode 100644 index 0000000..59ae4c2 --- /dev/null +++ b/resources/admin/components/admin/buttons/show.vue @@ -0,0 +1,16 @@ + + + diff --git a/resources/admin/components/admin/buttons/update.vue b/resources/admin/components/admin/buttons/update.vue new file mode 100644 index 0000000..0fb8838 --- /dev/null +++ b/resources/admin/components/admin/buttons/update.vue @@ -0,0 +1,18 @@ + + + + + diff --git a/resources/admin/components/admin/dialog/index.vue b/resources/admin/components/admin/dialog/index.vue new file mode 100644 index 0000000..c7c2277 --- /dev/null +++ b/resources/admin/components/admin/dialog/index.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/resources/admin/components/admin/status/index.vue b/resources/admin/components/admin/status/index.vue new file mode 100644 index 0000000..5f191cd --- /dev/null +++ b/resources/admin/components/admin/status/index.vue @@ -0,0 +1,24 @@ + + + diff --git a/resources/admin/components/breadcrumbs/index.vue b/resources/admin/components/breadcrumbs/index.vue new file mode 100644 index 0000000..5c06a1d --- /dev/null +++ b/resources/admin/components/breadcrumbs/index.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/resources/admin/components/icon/index.vue b/resources/admin/components/icon/index.vue new file mode 100644 index 0000000..7448577 --- /dev/null +++ b/resources/admin/components/icon/index.vue @@ -0,0 +1,23 @@ + + + diff --git a/resources/admin/composables/curd/useCreate.ts b/resources/admin/composables/curd/useCreate.ts new file mode 100644 index 0000000..ea44131 --- /dev/null +++ b/resources/admin/composables/curd/useCreate.ts @@ -0,0 +1,69 @@ +import http from '/admin/support/http' +import { ref, unref } from 'vue' +import { Code } from '/admin/enum/app' +import Message from '/admin/support/message' +import { FormInstance } from 'element-plus' +import { AxiosResponse } from 'axios' +import { isFunction } from'/admin/support/helper' + +// get table list +export function useCreate(path: string, id: string | number | null = null, _formData: object = {}) { + const formData = ref(_formData) + + const loading = ref() + const isClose = ref(false) + + // 创建前 hook + const beforeCreate = ref() + // 更新前 hook + const beforeUpdate = ref() + + // store + function store(path: string, id: string | number | null = null) { + loading.value = true + let promise: Promise | null = null + if (id) { + if (isFunction(beforeUpdate.value)) { + beforeUpdate.value() + } + + promise = http.put(path + '/' + id, unref(formData)) + } else { + console.log(isFunction(beforeCreate.value), beforeCreate.value) + if (isFunction(beforeCreate.value)) { + beforeCreate.value() + } + + promise = http.post(path, unref(formData)) + } + + promise + .then(r => { + if (r.data.code === Code.SUCCESS) { + isClose.value = true + Message.success(r.data.message) + } else { + Message.error(r.data.message) + } + }) + .finally(() => { + loading.value = false + }) + } + + const form = ref() + const submitForm = (formEl: FormInstance | undefined) => { + if (!formEl) return + formEl + .validate(valid => { + if (valid) { + store(path, id) + } else { + loading.value = false + } + }) + .then(() => {}) + } + + return { formData, loading, form, submitForm, isClose, beforeCreate, beforeUpdate } +} diff --git a/resources/admin/composables/curd/useDestroy.ts b/resources/admin/composables/curd/useDestroy.ts new file mode 100644 index 0000000..f8c2668 --- /dev/null +++ b/resources/admin/composables/curd/useDestroy.ts @@ -0,0 +1,36 @@ +import http from '/admin/support/http' +import { Code } from '/admin/enum/app' +import Message from '/admin/support/message' +import { ref } from 'vue' +import { isFunction } from'/admin/support/helper' + +export function useDestroy(confirm: string = '确认删除吗') { + const isDeleted = ref(false) + + const beforeDestroy = ref() + + // fetch list + function destroy(path: string, id: string | number) { + Message.confirm(confirm + '?', function () { + + // before destroy + if (isFunction(beforeDestroy.value)) { + beforeDestroy.value() + } + + http + .delete(path + '/' + id) + .then(r => { + if (r.data.code === Code.SUCCESS) { + Message.success(r.data.message) + isDeleted.value = true + } else { + Message.error(r.data.message) + } + }) + .finally(() => {}) + }) + } + + return { destroy, isDeleted } +} diff --git a/resources/admin/composables/curd/useEnabled.ts b/resources/admin/composables/curd/useEnabled.ts new file mode 100644 index 0000000..a3dfd47 --- /dev/null +++ b/resources/admin/composables/curd/useEnabled.ts @@ -0,0 +1,27 @@ +import http from '/admin/support/http' +import { Code } from '/admin/assets/enum/app' +import Message from '/admin/support/message' +import { ref } from 'vue' + +export function useEnabled() { + const success = ref(false) + const loading = ref(false) + function enabled(path: string, id: string | number, data: object = {}) { + loading.value = true + http + .put(path + '/enable/' + id, data) + .then(r => { + if (r.data.code === Code.SUCCESS) { + success.value = true + Message.success(r.data.message) + } else { + Message.error(r.data.message) + } + }) + .finally(() => { + loading.value = false + }) + } + + return { enabled, success, loading } +} diff --git a/resources/admin/composables/curd/useGetList.ts b/resources/admin/composables/curd/useGetList.ts new file mode 100644 index 0000000..cf667b7 --- /dev/null +++ b/resources/admin/composables/curd/useGetList.ts @@ -0,0 +1,75 @@ +import http from '/admin/support/http' +import { ref, unref } from 'vue' +import { Code } from '/admin/enum/app' +import Message from '/admin/support/message' + +const initLimit = 10 +const initPage = 1; + +// get table list +export function useGetList(path: string) { + const data = ref() + const page = ref(initPage) + const limit = ref(initLimit) + const query = ref({ + page: page.value, + limit: limit.value, + }) + const loading = ref(true) + // fetch list + function getList() { + // when table's data page >= 100, it will loading + if (page.value >= 100) { + loading.value = true + } + http + .get(path, unref(query)) + .then(r => { + closeLoading() + if (r.data.code === Code.SUCCESS) { + data.value = r.data + } else { + Message.error(r.data.message) + } + }) + .finally(() => { + closeLoading() + }) + } + + // close loading + function closeLoading() { + loading.value = false + } + // search + function search() { + getList() + } + + // reset + function reset() { + query.value = Object.assign({ page: page.value, limit: limit.value }) + + getList() + } + + // change page + function changePage(p: number) { + page.value = p + // @ts-ignore + query.value.page = p + search() + } + + // change limit + function changeLimit(l: number) { + limit.value = l + // @ts-ignore + query.value.page = 1 + // @ts-ignore + query.value.limit = l + search() + } + + return { data, query, search, reset, changePage, changeLimit, loading } +} diff --git a/resources/admin/composables/curd/useShow.ts b/resources/admin/composables/curd/useShow.ts new file mode 100644 index 0000000..4797d25 --- /dev/null +++ b/resources/admin/composables/curd/useShow.ts @@ -0,0 +1,14 @@ +import http from '/admin/support/http' + +export function useShow(path: string, id: string | number) { + return new Promise((resolve, reject) => { + http + .get(path + '/' + id) + .then(response => { + resolve(response.data) + }) + .catch(e => { + reject(e) + }) + }) +} diff --git a/resources/admin/enum/app.ts b/resources/admin/enum/app.ts new file mode 100644 index 0000000..e03418f --- /dev/null +++ b/resources/admin/enum/app.ts @@ -0,0 +1,43 @@ +/** + * 服务端返回码 + */ +export const enum Code { + SUCCESS = 10000, // 成功 + LOST_LOGIN = 10001, // 登录失效 + VALIDATE_FAILED = 10002, // 验证错误 + PERMISSION_FORBIDDEN = 10003, // 权限禁止 + LOGIN_FAILED = 10004, // 登录失败 + FAILED = 10005, // 操作失败 + LOGIN_EXPIRED = 10006, // 登录失效 + LOGIN_BLACKLIST = 10007, // 黑名单 + USER_FORBIDDEN = 10008, // 账户被禁 + WECHAT_RESPONSE_ERROR = 40000, +} + +/** + * status + */ +export const enum Status { + ENABLE = 1, + DISABLE = 2, +} + +/** + * 白名单页面 + * + * 不需要权限认证 + */ +export const enum WhiteListPage { + LOGIN_PATH = '/login', + + NOT_FOUND_PATH = '/404', +} + +/** + * menu 类型 + */ +export const enum MenuType { + PAGE_TYPE = 1, + + Button_Type, +} diff --git a/resources/admin/env.d.ts b/resources/admin/env.d.ts new file mode 100644 index 0000000..aafef95 --- /dev/null +++ b/resources/admin/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/resources/admin/i18n/index.ts b/resources/admin/i18n/index.ts new file mode 100644 index 0000000..7d3077e --- /dev/null +++ b/resources/admin/i18n/index.ts @@ -0,0 +1,22 @@ +import Cache from '/admin/support/cache' +import { createI18n } from 'vue-i18n' +import en from './languages/en' +import zh from './languages/zh' +import type { App } from 'vue' + +const messages = { + en, + zh, +} + +const i18n = createI18n({ + locale: Cache.get('language') || 'zh', + messages, + globalInjection: true, +}) + +export function bootstrapI18n(app: App): void { + app.use(i18n) +} + +export default i18n diff --git a/resources/admin/i18n/languages/en.ts b/resources/admin/i18n/languages/en.ts new file mode 100644 index 0000000..b8955b1 --- /dev/null +++ b/resources/admin/i18n/languages/en.ts @@ -0,0 +1,150 @@ +const en = { + system: { + name: 'CatchAdmin Dashboard', + chinese: 'Chinese', + english: 'English', + confirm: 'Confirm', + cancel: 'Cancel', + warning: 'Warning', + next: 'Next', + prev: 'Prev', + yes: 'Y', + no: 'N', + add: 'Add', + finish: 'Finish', + back: 'Back', + update: 'Update', + }, + + login: { + email: 'Email', + password: 'Password', + sign_in: 'Sign In', + welcome: 'Welcome Back👏', + lost_password: 'lost password?', + remember: 'Remember me', + verify: { + email: { + required: 'Please input email first', + invalid: 'Email address is invalid', + }, + + password: { + required: 'Please input password first', + }, + }, + }, + + register: { + sign_up: 'Sign Up', + }, + + generate: { + schema: { + title: 'Create Schema', + name: 'Schema Name', + name_verify: 'please input schema name', + engine: { + name: 'Search Engine', + verify: 'please select schema engine', + placeholder: 'select schema engine', + }, + default_field: { + name: 'Default Field', + created_at: 'Create time', + updated_at: 'Update Time', + creator: 'Creator', + delete_at: 'SoftDelete', + }, + comment: { + name: 'Schema Comment', + verify: 'please input schema comment', + }, + + structure: { + title: 'Create Schema Structure', + field_name: { + name: 'Field Name', + verify: 'please input field name', + }, + length: 'Length', + type: { + name: 'Field Type', + placeholder: 'select field type', + verify: 'please select field type', + }, + form_label: 'Form Label', + form_component: 'Component', + list: 'List', + form: 'Form', + unique: 'Unique', + search: 'Search', + search_op: { + name: 'Search Operate', + placeholder: 'select search operate', + }, + nullable: 'Nullable', + default: 'Default', + rules: { + name: 'Verify Rules', + placeholder: 'select verify rules', + }, + operate: 'Operate', + comment: 'Field Comment', + }, + }, + code: { + title: 'Code Gen', + module: { + name: 'module', + placeholder: 'please select module', + verify: 'please select module first', + }, + controller: { + name: 'Controller', + placeholder: 'please input controller name', + verify: 'please input Controller name first', + }, + model: { + name: 'Model', + placeholder: 'please input model name', + verify: 'please input model name first', + }, + paginate: 'Paginate', + }, + }, + + module: { + create: 'Create Module', + update: 'Update Module', + form: { + name: { + title: 'Module Name', + required: 'module name required', + }, + + path: { + title: 'Module Path', + required: 'module Path required', + }, + + desc: { + title: 'Description', + }, + + keywords: { + title: 'Keywords', + }, + + dirs: { + title: 'Default Dirs', + Controller: 'Controller', + Model: 'Model', + Database: 'Database', + Request: 'Request', + }, + }, + }, +} + +export default en diff --git a/resources/admin/i18n/languages/zh.ts b/resources/admin/i18n/languages/zh.ts new file mode 100644 index 0000000..ad1168c --- /dev/null +++ b/resources/admin/i18n/languages/zh.ts @@ -0,0 +1,155 @@ +const zh = { + system: { + name: 'CatchAdmin 管理系统', + chinese: '中文', + english: '英文', + confirm: '确定', + cancel: '取消', + warning: '警告', + next: '下一步', + prev: '上一步', + yes: '是', + no: '否', + add: '新增', + edit: '编辑', + finish: '完成', + back: '返回', + update: '更新', + }, + + login: { + email: '邮箱', + password: '密码', + sign_in: '登录', + welcome: '👏欢迎回来', + lost_password: '忘记密码?', + remember: '记住我', + verify: { + email: { + required: '请先输入邮箱', + invalid: '邮箱地址无效', + }, + + password: { + required: '请先输入密码', + }, + }, + }, + + register: { + sign_up: '注册', + }, + generate: { + schema: { + title: '创建数据表', + name: '表名称', + name_verify: '请输入表名称', + engine: { + name: '表引擎', + verify: '请选择表引擎', + placeholder: '选择表引擎', + }, + default_field: { + name: '默认字段', + created_at: '创建时间', + updated_at: '更新时间', + creator: '创建人', + delete_at: '软删除', + }, + comment: { + name: '表注释', + verify: '请填写表注释/说明', + }, + + structure: { + title: '创建数据结构', + field_name: { + name: '字段名称', + verify: '请填写字段名称', + }, + length: '长度', + type: { + name: '类型', + placeholder: '选择字段类型', + verify: '请先选择字段类型', + }, + form_label: '表单 Label', + form_component: '表单组件', + list: '列表', + form: '表单', + unique: '唯一', + search: '查询', + search_op: { + name: '搜索操作符', + placeholder: '选择搜索操作符', + }, + nullable: 'nullable', + default: '默认值', + rules: { + name: '验证规则', + placeholder: '选择验证规则', + }, + operate: '操作', + comment: '字段注释', + }, + }, + code: { + title: '生成代码', + module: { + name: '模块', + placeholder: '请选择模块', + verify: '请选择模块', + }, + controller: { + name: '控制器', + placeholder: '请输入控制器名称', + verify: '请输入控制器名称', + }, + model: { + name: '模型', + placeholder: '请输入模型名称', + verify: '请输入模型名称', + }, + paginate: '分页', + menu: { + name: '菜单名称', + placeholder: '请输入菜单名称', + verify: '请输入菜单名称', + }, + }, + }, + + module: { + create: '创建模块', + update: '更新模块', + form: { + name: { + title: '模块名称', + required: '请输入模块名称', + }, + + path: { + title: '模块目录', + required: '请输入模块目录', + }, + + desc: { + title: '模块描述', + }, + + keywords: { + title: '模块关键字', + }, + + dirs: { + title: '默认目录', + Controller: 'Controller 目录', + Model: 'Model 目录', + Database: 'Database 目录', + Request: 'Request 目录', + }, + }, + }, +} + +export default zh diff --git a/resources/admin/layout/components/Menu/index.vue b/resources/admin/layout/components/Menu/index.vue new file mode 100644 index 0000000..268d9d7 --- /dev/null +++ b/resources/admin/layout/components/Menu/index.vue @@ -0,0 +1,124 @@ + diff --git a/resources/admin/layout/components/Menu/item.vue b/resources/admin/layout/components/Menu/item.vue new file mode 100644 index 0000000..6628eea --- /dev/null +++ b/resources/admin/layout/components/Menu/item.vue @@ -0,0 +1,58 @@ + + + + + \ No newline at end of file diff --git a/resources/admin/layout/components/Menu/mask.vue b/resources/admin/layout/components/Menu/mask.vue new file mode 100644 index 0000000..39adb6b --- /dev/null +++ b/resources/admin/layout/components/Menu/mask.vue @@ -0,0 +1,9 @@ + + + diff --git a/resources/admin/layout/components/Menu/menus.vue b/resources/admin/layout/components/Menu/menus.vue new file mode 100644 index 0000000..1c69f35 --- /dev/null +++ b/resources/admin/layout/components/Menu/menus.vue @@ -0,0 +1,66 @@ + + + + \ No newline at end of file diff --git a/resources/admin/layout/components/content.vue b/resources/admin/layout/components/content.vue new file mode 100644 index 0000000..5cdb97f --- /dev/null +++ b/resources/admin/layout/components/content.vue @@ -0,0 +1,30 @@ + + diff --git a/resources/admin/layout/components/header/index.vue b/resources/admin/layout/components/header/index.vue new file mode 100644 index 0000000..d74d6c7 --- /dev/null +++ b/resources/admin/layout/components/header/index.vue @@ -0,0 +1,33 @@ + + diff --git a/resources/admin/layout/components/header/lang.vue b/resources/admin/layout/components/header/lang.vue new file mode 100644 index 0000000..f0e7f22 --- /dev/null +++ b/resources/admin/layout/components/header/lang.vue @@ -0,0 +1,38 @@ + + + diff --git a/resources/admin/layout/components/header/logo.vue b/resources/admin/layout/components/header/logo.vue new file mode 100644 index 0000000..22392a8 --- /dev/null +++ b/resources/admin/layout/components/header/logo.vue @@ -0,0 +1,22 @@ + + + + diff --git a/resources/admin/layout/components/header/notification.vue b/resources/admin/layout/components/header/notification.vue new file mode 100644 index 0000000..532516b --- /dev/null +++ b/resources/admin/layout/components/header/notification.vue @@ -0,0 +1,49 @@ + + diff --git a/resources/admin/layout/components/header/profile.vue b/resources/admin/layout/components/header/profile.vue new file mode 100644 index 0000000..afb6ed2 --- /dev/null +++ b/resources/admin/layout/components/header/profile.vue @@ -0,0 +1,34 @@ + + + diff --git a/resources/admin/layout/components/header/search.vue b/resources/admin/layout/components/header/search.vue new file mode 100644 index 0000000..4a0962b --- /dev/null +++ b/resources/admin/layout/components/header/search.vue @@ -0,0 +1,58 @@ + + + diff --git a/resources/admin/layout/components/header/theme.vue b/resources/admin/layout/components/header/theme.vue new file mode 100644 index 0000000..58a2527 --- /dev/null +++ b/resources/admin/layout/components/header/theme.vue @@ -0,0 +1,22 @@ + + + diff --git a/resources/admin/layout/components/sider.vue b/resources/admin/layout/components/sider.vue new file mode 100644 index 0000000..cbc946e --- /dev/null +++ b/resources/admin/layout/components/sider.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/resources/admin/layout/index.vue b/resources/admin/layout/index.vue new file mode 100644 index 0000000..f0d228d --- /dev/null +++ b/resources/admin/layout/index.vue @@ -0,0 +1,8 @@ + diff --git a/resources/admin/router/constantRoutes.ts b/resources/admin/router/constantRoutes.ts new file mode 100644 index 0000000..c3a8a28 --- /dev/null +++ b/resources/admin/router/constantRoutes.ts @@ -0,0 +1,9 @@ +import { RouteRecordRaw } from 'vue-router' +// @ts-ignore +const modules = import.meta.glob('@/module/**/views/router.ts', { eager: true }) +let moduleRoutes: RouteRecordRaw[] = [] + +Object.keys(modules).forEach(routePath => { + moduleRoutes = moduleRoutes.concat(modules[routePath].default) +}) +export default moduleRoutes diff --git a/resources/admin/router/guard/index.ts b/resources/admin/router/guard/index.ts new file mode 100644 index 0000000..d05fa59 --- /dev/null +++ b/resources/admin/router/guard/index.ts @@ -0,0 +1,68 @@ +import { useUserStore } from '/admin/stores/modules/user' +import { getAuthToken, removeAuthToken, setPageTitle } from '/admin/support/Helper' +import progress from '/admin/support/progress' +import { WhiteListPage } from '/admin/enum/app' +import { Router, RouteRecordRaw } from 'vue-router' +import { usePermissionsStore } from '/admin/stores/modules/user/permissions' +import { Menu } from '/admin/types/Menu' + +const guard = (router: Router) => { + // white list + const whiteList: string[] = [WhiteListPage.LOGIN_PATH, WhiteListPage.NOT_FOUND_PATH] + + router.beforeEach(async (to, from, next) => { + // set page title + setPageTitle(to.meta.title as unknown as string) + // page start + progress.start() + // 获取用户的 token + const authToken = getAuthToken() + // 如果 token 存在 + if (authToken) { + // 如果进入 /login 页面,重定向到首页 + if (to.path === WhiteListPage.LOGIN_PATH) { + next({ path: '/' }) + } else { + const userStore = useUserStore() + // 获取用户ID + if (userStore.getId) { + next() + } else { + try { + // 阻塞获取用户信息 + await userStore.getUserInfo() + // 如果后端没有返回 permissions,前台则只使用静态路由 + if (userStore.getPermissions !== undefined) { + // 挂载路由(实际是从后端获取用户的权限) + const permissionStore = usePermissionsStore() + // 动态路由挂载 + const asyncRoutes = permissionStore.getAsyncMenusFrom(userStore.getPermissions) + asyncRoutes.forEach((route: Menu) => { + router.addRoute(route as unknown as RouteRecordRaw) + }) + } + next({ ...to, replace: true }) + } catch (e) { + removeAuthToken() + next({ path: `${WhiteListPage.LOGIN_PATH}?redirect=${to.path}` }) + } + } + } + progress.done() + } else { + // 如果不在白名单 + if (whiteList.indexOf(to.path) !== -1) { + next() + } else { + next({ path: WhiteListPage.LOGIN_PATH }) + } + progress.done() + } + }) + + router.afterEach(() => { + progress.done() + }) +} + +export default guard diff --git a/resources/admin/router/index.ts b/resources/admin/router/index.ts new file mode 100644 index 0000000..a241f2b --- /dev/null +++ b/resources/admin/router/index.ts @@ -0,0 +1,65 @@ +import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router' +import type { App } from 'vue' +// module routers +import moduleRoutes from './constantRoutes' + +export const constantRoutes: RouteRecordRaw[] = [ + { + path: '/dashboard', + component: () => import('/admin/layout/index.vue'), + children: [ + { + path: '', + name: 'Dashboard', + meta: { title: 'Dashboard', icon: 'home', hidden: false }, + component: () => import('/admin/views/dashboard/index.vue'), + }, + ], + }, +] + // @ts-ignore + .concat(moduleRoutes) + +// default routes, it will not change to menus +const defaultRoutes: RouteRecordRaw[] = [ + { + path: '/', + name: '/', + component: () => import('/admin/layout/index.vue'), + redirect: '/dashboard', + children: [ + { + path: '/404', + name: '404', + meta: { title: '404' }, + component: () => import('/admin/components/404/index.vue'), + }, + ], + }, + { + path: '/login', + name: 'login', + component: () => import('/admin/views/login/index.vue'), + }, + // 未定义路有重定向到 404 + { + path: '/:pathMatch(.*)*', + redirect: '/404', + }, +] + +const routes = constantRoutes.concat(defaultRoutes) +const router = createRouter({ + history: createWebHashHistory(), + routes, + // 路由滚动 + scrollBehavior(to, from, savedPosition) { + return savedPosition || { top: 0, behavior: 'smooth' } + }, +}) + +export function bootstrapRouter(app: App) { + app.use(router) +} + +export default router diff --git a/resources/admin/stores/index.ts b/resources/admin/stores/index.ts new file mode 100644 index 0000000..ea06902 --- /dev/null +++ b/resources/admin/stores/index.ts @@ -0,0 +1,10 @@ +import { createPinia } from 'pinia' +import type { App } from 'vue' + +const store = createPinia() + +export function bootstrapStore(app: App) : void { + app.use(store) +} + +export default store diff --git a/resources/admin/stores/modules/app/index.ts b/resources/admin/stores/modules/app/index.ts new file mode 100644 index 0000000..2e99216 --- /dev/null +++ b/resources/admin/stores/modules/app/index.ts @@ -0,0 +1,76 @@ +import { defineStore } from 'pinia' +import Cache from '/admin/support/cache' + +/** + * app + */ +type app = { + size: 'small' | 'medium' | 'large' + + isExpand: boolean + + locale: 'zh' | 'en' + + isMobile: boolean + + isDarkMode: boolean + + activeMenu: string +} + +export const useAppStore = defineStore('app', { + state: (): app => ({ + size: 'small', + isExpand: true, + locale: Cache.get('language'), + isMobile: false, + isDarkMode: false, + activeMenu: '/dashboard', + }), + + getters: { + getSize(): string { + return this.size + }, + + getLocale(): string { + return this.locale + }, + + getIsMobile(): boolean { + return this.isMobile + }, + + getIsDarkMode(): boolean { + return this.isDarkMode + }, + + getActiveMenu(): string { + return this.activeMenu + }, + }, + + actions: { + changeSize(size: 'small' | 'medium' | 'large'): void { + this.size = size + }, + + changeLocale(locale: 'zh' | 'en'): void { + Cache.set('language', locale) + + this.locale = locale + }, + + changeExpaned(): void { + this.isExpand = !this.isExpand + }, + + setDarkMode(isDarkMode: boolean): void { + this.isDarkMode = isDarkMode + }, + + setActiveMenu(activeMenu: string): void { + this.activeMenu = activeMenu.startsWith('/') ? activeMenu : '/' + activeMenu + }, + }, +}) diff --git a/resources/admin/stores/modules/user/index.ts b/resources/admin/stores/modules/user/index.ts new file mode 100644 index 0000000..be39116 --- /dev/null +++ b/resources/admin/stores/modules/user/index.ts @@ -0,0 +1,152 @@ +import { defineStore } from 'pinia' +import { User } from '/admin/types/user' +import http from '/admin/support/http' +import { rememberAuthToken, removeAuthToken } from '/admin/support/helper' +import Message from '/admin/support/message' +import router from '/admin/router' +import { Permission } from '/admin/types/permission' + +export const useUserStore = defineStore('UserStore', { + state: (): User => { + return { + id: 0, + + nickname: '', + + avatar: '', + + email: '', + + remember_token: '', + + status: 0, + + permissions: [] as Permission[], + + roles: [] as string[], + } + }, + + getters: { + getId(): number { + return this.id + }, + getNickname(): string { + return this.nickname + }, + + getAvatar(): string { + return this.avatar + }, + + getRoles(): string[] | undefined { + return this.roles + }, + + getPermissions(): Permission[] | undefined { + return this.permissions + }, + }, + + actions: { + isSuperAdmin(): boolean { + return this.id === 1 + }, + setNickname(nickname: string) { + this.nickname = nickname + }, + + setId(id: number) { + this.id = id + }, + + setRememberToken(token: string) { + this.remember_token = token + }, + + setAvatar(avatar: string) { + this.avatar = avatar + }, + + setRoles(roles: string[]) { + this.roles = roles + }, + + setPermissions(permissions: Permission[]) { + this.permissions = permissions + }, + + setEmail(email: string) { + this.email = email + }, + + setStatus(status: number) { + this.status = status + }, + + /** + * login + * + * @param params + * @returns + */ + login(params: Object) { + return new Promise((resolve, reject) => { + http + .post('/login', params) + .then(response => { + const { token } = response.data.data + rememberAuthToken(token) + this.setRememberToken(token) + resolve() + }) + .catch(e => { + reject(e) + }) + }) + }, + + /** + * logout + */ + logout() { + http + .post('/logout') + .then(() => { + removeAuthToken() + this.$reset() + router.push({ path: '/login' }) + }) + .catch(e => { + Message.error(e.message) + }) + }, + + /** + * user info + */ + getUserInfo() { + return new Promise((resolve, reject) => { + http + .get('/user/info') + .then(response => { + const { id, nickname, email, avatar, permissions, roles, rememberToken, status } = response.data.data + // set user info + this.setId(id) + this.setNickname(nickname) + this.setEmail(email) + this.setRoles(roles) + this.setRememberToken(rememberToken) + this.setStatus(status) + this.setAvatar(avatar) + this.setPermissions(permissions) + + resolve(response.data.data) + }) + .catch(e => { + reject(e) + }) + }) + }, + }, +}) diff --git a/resources/admin/stores/modules/user/permissions.ts b/resources/admin/stores/modules/user/permissions.ts new file mode 100644 index 0000000..c082163 --- /dev/null +++ b/resources/admin/stores/modules/user/permissions.ts @@ -0,0 +1,193 @@ +import { defineStore } from 'pinia' +import { Permission } from '/admin/types/permission' +import { MenuType } from '/admin/enum/app' +import { Menu } from '/admin/types/Menu' +import { constantRoutes } from '/admin/router' +import { RouteRecordRaw } from 'vue-router' + +interface Permissions { + menus: Menu[] + + asyncMenus: Menu[] + + permissions: Permission[] + + menuPathMap: Map +} + +export const usePermissionsStore = defineStore('PermissionsStore', { + state: (): Permissions => { + return { + menus: [], + + asyncMenus: [], + + permissions: [], + + menuPathMap: new Map(), + } + }, + + /** + * get + */ + getters: { + getMenus(): Menu[] { + return this.menus + }, + + getAsyncMenus(): Menu[] { + return this.asyncMenus + }, + + getPermissions(): Permission[] { + return this.permissions + }, + + getMenuPathMap(): Map { + return this.menuPathMap + }, + }, + + /** + * actions + */ + actions: { + /** + * generate async menus + * @param permissions + * @param force + * @returns + */ + getAsyncMenusFrom(permissions: Permission[], force: boolean = false): Menu[] { + // 如果非强制获取并且 menu 有值,直接返回 + if (!force && this.asyncMenus.length > 0) { + return this.asyncMenus + } + + const menus: Permission[] = [] + + permissions.forEach(permission => { + if (permission.type === MenuType.PAGE_TYPE) { + menus.push(permission) + } + + // set map + this.menuPathMap.set(permission.route, permission.title) + }) + + this.setAsyncMenus(this.getAsnycMenus(menus)) + + return this.asyncMenus + }, + + /** + * get menus + * @param permissions + * @param force + * @returns + */ + getMenusFrom(permissions: Permission[], force: boolean = false): Menu[] { + // 如果非强制获取并且 menu 有值,直接返回 + if (!force && this.menus.length > 0) { + return this.menus + } + const asyncMenus = this.getAsyncMenusFrom(permissions, force) + + this.setMenus(asyncMenus) + + return this.menus + }, + + /** + * set menus + * + * @param menus + */ + setMenus(menus: Menu[]) { + this.menus = this.transformRoutesToMenus(constantRoutes).concat(menus) + }, + + setAsyncMenus(menus: Menu[]) { + this.asyncMenus = menus + }, + + /** + * 生成 Menus + * + * @param permissions + * @param parentId + * @param path + * @returns + */ + getAsnycMenus(permissions: Permission[], parentId: number = 0, path: string = ''): Menu[] { + const menus: Menu[] = [] + + permissions.forEach(permission => { + if (permission.parent_id === parentId) { + // menu + const menu: Menu = Object.assign({ + path: this.resoulveRoutePath(permission.route, path), + name: permission.module + '_' + permission.permission_mark, + redirect: permission.redirect, + meta: Object.assign({ title: permission.title, icon: permission.icon, hidden: permission.hidden, is_inner: permission.is_inner }), + }) + + // child menu + const children = this.getAsnycMenus(permissions, permission.id, menu.path) + if (children.length > 0) { + menu.children = children + } + menus.push(menu) + } + }) + + return menus + }, + + /** + * transform routes to menus + * @param routes + * @param path + * @returns + */ + transformRoutesToMenus(routes: Menu[] | Array, path: string = ''): Menu[] { + const menus: Menu[] = [] + + routes.forEach(route => { + if (route.meta?.hidden) { + return false + } + + const menu: Menu = Object.assign({ + path: this.resoulveRoutePath(route.path, path), + name: route.name, + meta: route.meta, + component: route.component, + }) + + if (route.children?.length) { + menu.children = this.transformRoutesToMenus(route.children, menu.path) + } + + menus.push(menu) + }) + return menus + }, + + /** + * resoulve route path + * @param route + * @param path + * @returns + */ + resoulveRoutePath(route: string, path: string): string { + if (path.length) { + return (path + (route.indexOf('/') === -1 ? '/' : '') + route).replace(/\/$/g, '') + } + + // 去除尾部的 / + return route.replace(/\/$/g, '') + }, + }, +}) diff --git a/resources/admin/styles/element.scss b/resources/admin/styles/element.scss new file mode 100644 index 0000000..bf66828 --- /dev/null +++ b/resources/admin/styles/element.scss @@ -0,0 +1,61 @@ +@forward 'element-plus/theme-chalk/src/common/var' with ( + // 基础色调 + $colors: + ( + 'primary': ( + 'base': #4f46e5, + ), + 'success': ( + 'base': #059669, + ), + 'warning': ( + 'base': #fbbf24, + ), + 'danger': ( + 'base': #f43f5e, + ), + 'error': ( + 'base': #f43f5e, + ), + 'info': ( + 'base': #909399, + ) + ), + $input: ('border-radius': 8px) +); + +@use 'element-plus/theme-chalk/src/index' as *; + +.el-table { + border-radius: var(--el-table-border-radius); + + .el-table__row { + @apply h-14; + } + + .el-table__header { + @apply h-14 bg-black; + } +} + +.el-tabs { + border-radius: var(--el-table-border-radius); +} + +.el-card { + border-radius: var(--el-card-border-radius) !important; +} + +.el-pagination { + button { + border-radius: var(--el-page-border-radius) !important; + } + .el-pager { + .number { + border-radius: var(--el-page-border-radius) !important; + } + .more { + border-radius: var(--el-page-border-radius) !important; + } + } +} diff --git a/resources/admin/styles/index.scss b/resources/admin/styles/index.scss new file mode 100644 index 0000000..3628656 --- /dev/null +++ b/resources/admin/styles/index.scss @@ -0,0 +1,12 @@ +// tailwindcss +@import 'tailwind.css'; + +// element style 必须在 tailwindcss 之后,不然样式会被 tailwindcss 覆盖 +// issue 在这里 https://github.com/tailwindlabs/tailwindcss/discussions/5969 +@import 'element'; + +// 后台管理定义的 css 变量 +@import 'var'; + +// theme +@import 'theme/index'; diff --git a/resources/admin/styles/tailwind.css b/resources/admin/styles/tailwind.css new file mode 100644 index 0000000..6d5361c --- /dev/null +++ b/resources/admin/styles/tailwind.css @@ -0,0 +1,22 @@ +@tailwind base; + +@layer base { + .layout-sider { + @apply z-0 absolute top-16 left-0 sm:static transition-width duration-300 ease-linear + } + + .layout-sider-open { + @apply layout-sider w-64 + } + + .layout-sider-hidden { + @apply layout-sider w-0 lg:w-20 + } + + .layout-sider-mask { + @apply block lg:hidden z-40 w-full h-full absolute + } +} + +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/resources/admin/styles/theme/dark.scss b/resources/admin/styles/theme/dark.scss new file mode 100644 index 0000000..313aee0 --- /dev/null +++ b/resources/admin/styles/theme/dark.scss @@ -0,0 +1,36 @@ +@import 'element-plus/theme-chalk/dark/css-vars.css'; + +html.dark { + background-color: #161d31; + // 侧边栏背景色 + --sider-bg-color: #283046; + // header logo 文字颜色 + --header-logo-text-color: #ffffff; + // 侧边栏菜单的文字颜色 + --sider-menu-text-color: #ffffff; + // sub menu bg color + --sider-sub-menu-bg-color: #161d31; + // 侧边栏子菜单 hover 的颜色 + --sider-sub-menu-hover-bg-color: #343d55; + // 激活文字颜色 + --sider-ment-active-text-color: var(--el-color-primary); + // 激活时背景色 + --side-active-menu-bg-color: rgba(255, 255, 255, 0.08); + + /* 自定义深色背景颜色 */ + --el-bg-color: var(--sider-sub-menu-hover-bg-color); + + --el-fill-color-blank: var(--sider-bg-color); + + --el-bg-color-overlay: var(--sider-bg-color); + + --header-bg-color: var(--sider-bg-color); + + // border color + --el-border-color-lighter: #3b4253; + + --el-fill-color-light: #161d31; + + // side sub menu margin + --sider-sub-menu-bg-margin: 0px 0.05rem; +} diff --git a/resources/admin/styles/theme/index.scss b/resources/admin/styles/theme/index.scss new file mode 100644 index 0000000..d66b8af --- /dev/null +++ b/resources/admin/styles/theme/index.scss @@ -0,0 +1,3 @@ +@import "light"; + +@import "dark"; diff --git a/resources/admin/styles/theme/light.scss b/resources/admin/styles/theme/light.scss new file mode 100644 index 0000000..4a3d088 --- /dev/null +++ b/resources/admin/styles/theme/light.scss @@ -0,0 +1,25 @@ +html { + background-color: rgb(241, 245, 249); + // 侧边颜色 + --sider-bg-color: #ffffff; + // header logo 背景颜色 + --header-logo-bg-color: var(--sider-bg-color); + // 侧边栏菜单的文字颜色 + --sider-menu-text-color: #625f6e; + // sub menu bg color + --sider-sub-menu-bg-color: #ffffff; + // 侧边栏子菜单 hover 的颜色 + --sider-sub-menu-hover-bg-color: #f6f6f6; + // 菜单激活时文字的颜色 + --sider-ment-active-text-color: var(--el-color-primary); + // 激活菜单背景 + --side-active-menu-bg-color: rgba(36, 153, 239, 0.06); + // header logo 文字颜色 + --header-logo-text-color: #625f6e; + // header bg color + --header-bg-color: #ffffff; + // 侧边栏菜单的背景色 + --sider-menu-bg-color: var(--sider-bg-color); + + --el-table-tr-bg-color: black; +} diff --git a/resources/admin/styles/var.scss b/resources/admin/styles/var.scss new file mode 100644 index 0000000..22d4789 --- /dev/null +++ b/resources/admin/styles/var.scss @@ -0,0 +1,12 @@ +:root { + --el-menu-base-level-padding: 20px; + // 后台自定义 + // el-table + --el-table-border-radius: 8px; + // el-tabs + --el-tabs-border-radius: 8px; + // el-card + --el-card-border-radius: 8px; + // -el-page + --el-page-border-radius: 8px; +} diff --git a/resources/admin/support/cache.ts b/resources/admin/support/cache.ts new file mode 100644 index 0000000..04bf3ad --- /dev/null +++ b/resources/admin/support/cache.ts @@ -0,0 +1,32 @@ +export default class Cache { + private static readonly prefix:string = 'catchadmin_' + /** + * set + * + * @param key + * @param value + */ + static set (key:string, value: any) : void { + window.localStorage.setItem(Cache.prefix + key, value) + } + + /** + * get + * + * @param key + * @returns + */ + static get (key: string) : any { + return window.localStorage.getItem(Cache.prefix + key) + } + + /** + * delete + * + * @param key + * @returns + */ + static del (key: string) : void { + window.localStorage.removeItem(Cache.prefix + key) + } +} diff --git a/resources/admin/support/catchAdmin.ts b/resources/admin/support/catchAdmin.ts new file mode 100644 index 0000000..33fb635 --- /dev/null +++ b/resources/admin/support/catchAdmin.ts @@ -0,0 +1,88 @@ +import { createApp } from 'vue' +import type { App as app } from 'vue' +import App from '/admin/App.vue' +import router, { bootstrapRouter } from '/admin/router' +import ElementPlus from 'element-plus' +import zh from 'element-plus/es/locale/lang/zh-cn' +import { bootstrapStore } from '/admin/stores' +import Cache from './cache' +import { bootstrapI18n } from '/admin/i18n' +import guard from '/admin/router/guard' + +/** + * catchadmin + */ +export default class CatchAdmin { + protected app: app + + protected element: string + + /** + * construct + * + * @param ele + */ + constructor(ele: string = '#app') { + this.app = createApp(App) + this.element = ele + } + + /** + * admin boot + */ + bootstrap(): void { + this.useElementPlus().usePinia().useI18n().useRouter().mount() + } + + /** + * 挂载节点 + * + * @returns + */ + protected mount(): void { + this.app.mount(this.element) + } + + /** + * 加载路由 + * + * @returns + */ + protected useRouter(): CatchAdmin { + guard(router) + + bootstrapRouter(this.app) + + return this + } + + /** + * ui + * + * @returns + */ + protected useElementPlus(): CatchAdmin { + this.app.use(ElementPlus, { + locale: Cache.get('language') === 'zh' && zh, + }) + return this + } + + /** + * use pinia + */ + protected usePinia(): CatchAdmin { + bootstrapStore(this.app) + + return this + } + + /** + * use i18n + */ + protected useI18n(): CatchAdmin { + bootstrapI18n(this.app) + + return this + } +} diff --git a/resources/admin/support/helper.ts b/resources/admin/support/helper.ts new file mode 100644 index 0000000..f979afd --- /dev/null +++ b/resources/admin/support/helper.ts @@ -0,0 +1,89 @@ +/** + * Helper 助教函数集合 + */ + +import Cache from '/admin/support/cache' +import i18n from '/admin/i18n' + +const AUTH_TOKEN = 'auth_token' + +/** + * env get + * + * @param key + */ +export function env(key: string): any { + const env = import.meta.env + + return env[key] +} + +/** + * remember token + * + * @param token + */ +export function rememberAuthToken(token: string): void { + Cache.set(AUTH_TOKEN, token) +} + +/** + * remove auth token + */ +export function removeAuthToken(): void { + Cache.del(AUTH_TOKEN) +} + +/** + * get auth token + * + */ +export function getAuthToken(): string | null { + return Cache.get(AUTH_TOKEN) +} + +/** + * 是否是小屏幕 + * @return + */ +export function isMiniScreen(): boolean { + return window.document.body.clientWidth < 500 +} + +/** + * translate + * + * @param translate + * @returns + */ +export function t(translate: string) { + return i18n.global.t(translate) +} + +/** + * is undefined + * + * @param value + * @returns + */ +export function isUndefined(value: any): boolean { + return value === undefined +} + +/** + * set page title + * + * @param title + */ +export function setPageTitle(title: string) { + document.title = title +} + +/** + * is function? + * + * @param value + */ +export function isFunction(value: any) { + return typeof value === 'function' +} diff --git a/resources/admin/support/http.ts b/resources/admin/support/http.ts new file mode 100644 index 0000000..c950aee --- /dev/null +++ b/resources/admin/support/http.ts @@ -0,0 +1,213 @@ +import { Code } from '/admin/enum/app' +import axios, { AxiosInstance, AxiosRequestConfig } from 'axios' +import { env, getAuthToken, removeAuthToken } from './helper' +import Message from './message' +import router from '/admin/router' +import ResponseData from '/admin/types/responseData' + +/** + * http util + */ +class Http { + /** + * axios config + * @protected + */ + protected config: AxiosRequestConfig = {} + + /** + * base url + * @protected + */ + protected baseURL: string = '' + + /** + * http request timeout + * + * @protected + */ + protected timeout: number = 0 + + /** + * http request headers + * + * @protected + */ + protected headers: { [k: string]: string } = {} + + /** + * axios instance + * + * @protected + */ + protected request: AxiosInstance + + /** + * instance + */ + constructor() { + this.request = axios.create(this.getConfig()) + } + + /** + * get request + * + * @param path + * @param params + */ + public get(path: string, params: object = {}) { + return this.request.get(this.baseURL + path, { + params, + }) + } + + /** + * post request + * + * @param path + * @param data + */ + public post(path: string, data: object = {}) { + return this.request.post(this.baseURL + path, data) + } + + /** + * put request + * + * @param path + * @param data + */ + public put(path: string, data: object = {}) { + return this.request.put(this.baseURL + path, data) + } + + /** + * delete request + * + * @param path + */ + public delete(path: string) { + return this.request.delete(this.baseURL + path) + } + + /** + * set timeout + * + * @param timeout + * @returns + */ + public setTimeout(timeout: number): Http { + this.timeout = timeout + + return this + } + + /** + * set baseurl + * + * @param url + * @returns + */ + public setBaseUrl(url: string): Http { + this.baseURL = url + + return this + } + + /** + * set headers + * + * @param key + * @param value + * @returns + */ + public setHeader(key: string, value: string): Http { + this.headers.key = value + + return this + } + + /** + * get axios 配置 + * + * @returns + */ + protected getConfig(): AxiosRequestConfig { + // set base url + this.config.baseURL = this.baseURL ? this.baseURL : env('VITE_BASE_URL') + + // set timeout + this.config.timeout = this.timeout ? this.timeout : 5000 + + // set ajax request + this.headers['X-Requested-With'] = 'XMLHttpRequest' + this.config.headers = this.headers + + return this.config + } + + /** + * 添加请求拦截器 + * + */ + public interceptorsOfRequest(): void { + this.request.interceptors.request.use(function (config: AxiosRequestConfig) { + const token = getAuthToken() + if (token) { + if (!config.headers) { + config.headers = {} + } + + config.headers.authorization = 'Bearer ' + token + } + + return config + }) + } + + /** + * 添加响应拦截器 + * + */ + public interceptorsOfResponse(): void { + this.request.interceptors.response.use( + response => { + const r: ResponseData = response.data + const code = r.code + const message = r.message + if (code === 1e4) { + return response + } + + if (code === 10004) { + Message.error(message || 'Error') + } else if (code === Code.LOST_LOGIN || code === Code.LOGIN_EXPIRED) { + // to re-login + Message.confirm(message + ',需要重新登陆', function () { + removeAuthToken() + router.push('/login') + }) + } else if (code === Code.LOGIN_BLACKLIST || code === Code.USER_FORBIDDEN) { + Message.error(message || 'Error') + removeAuthToken() + // to login page + router.push('/login') + } else { + Message.error(message || 'Error') + } + + return Promise.reject(new Error(message || 'Error')) + }, + error => { + Message.error(error.message) + return Promise.reject(error) + }, + ) + } +} + +const http = new Http() +http.interceptorsOfRequest() + +http.interceptorsOfResponse() +export default http diff --git a/resources/admin/support/message.ts b/resources/admin/support/message.ts new file mode 100644 index 0000000..f43b92d --- /dev/null +++ b/resources/admin/support/message.ts @@ -0,0 +1,59 @@ +import { ElMessage, ElMessageBox } from 'element-plus' +import { t } from './helper' + +export default class Message { + /** + * success + * + * @param message + */ + static success (message: string) : void { + this.message(message, 'success') + } + + /** + * error + * + * @param message + */ + static error (message: string) : void { + this.message(message, 'error') + } + + /** + * warning + * + * @param message + */ + static warning (message: string) : void { + this.message(message, 'warning') + } + + /** + * confirm + * + * @param message + * @param callback + */ + static confirm (message: string, callback: any) : void { + ElMessageBox.confirm(message, t('system.warning'), { + confirmButtonText: t('system.confirm'), + cancelButtonText: t('system.cancel'), + type: 'warning' + }).then(callback) + } + + /** + * message + * + * @param message + * @param type + */ + protected static message (message: string, type: any) { + ElMessage({ + message, + + type + }) + } +} diff --git a/resources/admin/support/progress.ts b/resources/admin/support/progress.ts new file mode 100644 index 0000000..081dd10 --- /dev/null +++ b/resources/admin/support/progress.ts @@ -0,0 +1,14 @@ +import NProgress from 'nprogress' +import 'nprogress/nprogress.css' + +// 全局进度条的配置 +NProgress.configure({ + easing: 'ease', // 动画方式 + speed: 500, // 递增进度条的速度 + showSpinner: true, // 是否显示加载ico + trickleSpeed: 200, // 自动递增间隔 + minimum: 0.3, // 更改启动时使用的最小百分比 + parent: 'body' // 指定进度条的父容器 +}) + +export default NProgress diff --git a/resources/admin/types/Permission.ts b/resources/admin/types/Permission.ts new file mode 100644 index 0000000..f0b7c97 --- /dev/null +++ b/resources/admin/types/Permission.ts @@ -0,0 +1,27 @@ +export interface Permission { + id: number + + parent_id: number + + title: string + + type: number + + icon: string + + component: string + + module: string + + permission_mark: string + + route: string + + redirect: string + + keepAlive: boolean + + hidden: boolean + + is_inner: boolean +} diff --git a/resources/admin/types/Role.ts b/resources/admin/types/Role.ts new file mode 100644 index 0000000..336ce12 --- /dev/null +++ b/resources/admin/types/Role.ts @@ -0,0 +1 @@ +export {} diff --git a/resources/admin/types/User.ts b/resources/admin/types/User.ts new file mode 100644 index 0000000..998cebc --- /dev/null +++ b/resources/admin/types/User.ts @@ -0,0 +1,22 @@ + +// login user type + +import { Permission } from './permission' + +export interface User { + id: number, + + nickname: string, + + avatar: string, + + email: string, + + status: number, + + remember_token: string, + + roles?: string[], + + permissions?: Permission[] +} diff --git a/resources/admin/types/menu.ts b/resources/admin/types/menu.ts new file mode 100644 index 0000000..c469940 --- /dev/null +++ b/resources/admin/types/menu.ts @@ -0,0 +1,33 @@ +import { Component } from 'vue' +import { RouteRecordRaw } from 'vue-router' + +export interface Meta { + title: string + + icon: string + + roles?: string[] + + cache?: boolean + + hidden: boolean + + keepalive?: boolean + + is_inner?: boolean +} + +// @ts-ignore +export interface Menu extends Omit { + path: string + + name: string + + meta?: Meta + + redirect?: string + + component?: Component + + children?: Menu[] +} diff --git a/resources/admin/types/responseData.ts b/resources/admin/types/responseData.ts new file mode 100644 index 0000000..5df41ac --- /dev/null +++ b/resources/admin/types/responseData.ts @@ -0,0 +1,7 @@ +export default interface ResponseData{ + code: number; + + message: string; + + data: any +} diff --git a/resources/admin/types/router.ts b/resources/admin/types/router.ts new file mode 100644 index 0000000..f9a7c76 --- /dev/null +++ b/resources/admin/types/router.ts @@ -0,0 +1,15 @@ + +export interface RouterMeta { + icon: string, + title: string, + roles: string[] +} + +export interface RouterRecord { + name: string; + meta: RouterMeta; + component?: string; + children?: RouterRecord[]; + fullPath?: string; + redirect: string +} diff --git a/resources/admin/views/dashboard/dependencies.vue b/resources/admin/views/dashboard/dependencies.vue new file mode 100644 index 0000000..6adeec1 --- /dev/null +++ b/resources/admin/views/dashboard/dependencies.vue @@ -0,0 +1,36 @@ + + diff --git a/resources/admin/views/dashboard/index.vue b/resources/admin/views/dashboard/index.vue new file mode 100644 index 0000000..eb3b403 --- /dev/null +++ b/resources/admin/views/dashboard/index.vue @@ -0,0 +1,84 @@ + + diff --git a/resources/admin/views/dashboard/introduce.vue b/resources/admin/views/dashboard/introduce.vue new file mode 100644 index 0000000..05fc5f1 --- /dev/null +++ b/resources/admin/views/dashboard/introduce.vue @@ -0,0 +1,121 @@ + + diff --git a/resources/admin/views/dashboard/project.vue b/resources/admin/views/dashboard/project.vue new file mode 100644 index 0000000..d31e99c --- /dev/null +++ b/resources/admin/views/dashboard/project.vue @@ -0,0 +1,39 @@ + diff --git a/resources/admin/views/login/index.vue b/resources/admin/views/login/index.vue new file mode 100644 index 0000000..a802fdf --- /dev/null +++ b/resources/admin/views/login/index.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/resources/admin/views/login/login.ts b/resources/admin/views/login/login.ts new file mode 100644 index 0000000..42f6b20 --- /dev/null +++ b/resources/admin/views/login/login.ts @@ -0,0 +1,60 @@ +import { reactive, ref } from 'vue' +import type { FormInstance } from 'element-plus' +import { useUserStore } from '/admin/stores/modules/user' +import router from '/admin/router' +import { t } from '/admin/support/helper' + +export const useLogin = () => { + const params = reactive({ + email: '', + password: '', + remember: false, + }) + + const loading = ref(false) + + const rules = reactive({ + email: [ + { required: true, message: t('login.verify.email.required'), trigger: 'blur' }, + { type: 'email', message: t('login.verify.email.invalid'), trigger: 'blur' }, + ], + password: [{ required: true, message: t('login.verify.password.required'), trigger: 'blur' }], + }) + + const form = ref() + + const submit = (loginForm: FormInstance | undefined) => { + if (!loginForm) return + + loginForm.validate(valid => { + if (valid) { + loading.value = true + const store = useUserStore() + + store + .login(params) + .then(() => { + loading.value = false + router.push({ path: '/' }) + }) + .catch(e => { + loading.value = false + }) + } else { + return false + } + }) + } + + return { + params, + + rules, + + loading, + + submit, + + form, + } +} diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php new file mode 100644 index 0000000..e69de29 diff --git a/routes/api.php b/routes/api.php new file mode 100644 index 0000000..aed6626 --- /dev/null +++ b/routes/api.php @@ -0,0 +1,28 @@ + 1, + 'nickname' => 'catchadmin', + 'remember_token' => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhZG1pbl9pZCI6MSwiYXVkIjoiIiwiZXhwIjoxNjg0ODA2MzIxLCJpYXQiOjE2NTg4ODYzMjEsImlzcyI6IiIsImp0aSI6ImI4ZjZlMzM4ZjM1MDg2OWJiZjIwNjE4OTA4OTk0ODMzIiwibmJmIjoxNjU4ODg2MzIxLCJzdWIiOiIifQ.ZJinC0JvY6OhJjr1GJgMaYk2qie8U_2W55L_I2AIBHk', + + ]; +}); + diff --git a/routes/channels.php b/routes/channels.php new file mode 100644 index 0000000..5d451e1 --- /dev/null +++ b/routes/channels.php @@ -0,0 +1,18 @@ +id === (int) $id; +}); diff --git a/routes/console.php b/routes/console.php new file mode 100644 index 0000000..e05f4c9 --- /dev/null +++ b/routes/console.php @@ -0,0 +1,19 @@ +comment(Inspiring::quote()); +})->purpose('Display an inspiring quote'); diff --git a/routes/web.php b/routes/web.php new file mode 100644 index 0000000..9800a1e --- /dev/null +++ b/routes/web.php @@ -0,0 +1,14 @@ +make(Kernel::class)->bootstrap(); + + return $app; + } +} diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php new file mode 100644 index 0000000..61cd84c --- /dev/null +++ b/tests/ExampleTest.php @@ -0,0 +1,5 @@ +toBeTrue(); +}); diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php new file mode 100644 index 0000000..1eafba6 --- /dev/null +++ b/tests/Feature/ExampleTest.php @@ -0,0 +1,21 @@ +get('/'); + + $response->assertStatus(200); + } +} diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..5949c61 --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,45 @@ +in('Feature'); + +/* +|-------------------------------------------------------------------------- +| Expectations +|-------------------------------------------------------------------------- +| +| When you're writing tests, you often need to check that values meet certain conditions. The +| "expect()" function gives you access to a set of "expectations" methods that you can use +| to assert different things. Of course, you may extend the Expectation API at any time. +| +*/ + +expect()->extend('toBeOne', function () { + return $this->toBe(1); +}); + +/* +|-------------------------------------------------------------------------- +| Functions +|-------------------------------------------------------------------------- +| +| While Pest is very powerful out-of-the-box, you may have some testing code specific to your +| project that you don't want to repeat in every file. Here you can also expose helpers as +| global functions to help you to reduce the number of lines of code in your test files. +| +*/ + +function something() +{ + // .. +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..2932d4a --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,10 @@ +assertTrue(true); + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..8bad2a4 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,43 @@ +{ + "compilerOptions": { + "baseUrl": "./", + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "moduleResolution": "node", + "strict": true, + "jsx": "preserve", + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "allowJs": true, + "lib": [ + "esnext", + "dom" + ], + "skipLibCheck": true, + // "types": ["element-plus/golbal"], + "paths": { + "@/*": [ + "resources/js/*" + ], + "/admin/*": [ + "resources/admin/*" + ] + } + }, + "include": [ + "resources/admin/js/**/*.ts", + "resources/admin/js/**/*.d.ts", + "resources/admin/js/**/*.tsx", + "resources/admin/js/**/*.vue", + "resources/admin/js/**/*.js", + "resources/admin/js/env.d.ts" + ], + "references": [ + { + "path": "./tsconfig.node.json" + } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..e993792 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "composite": true, + "module": "esnext", + "moduleResolution": "node" + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..1419670 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,103 @@ +import { defineConfig, loadEnv } from 'vite' +import vue from '@vitejs/plugin-vue' +import alias from '@rollup/plugin-alias' +import vueJsx from '@vitejs/plugin-vue-jsx' +import { resolve } from 'path' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import Icons from 'unplugin-icons/vite' +const rootPath = resolve(__dirname) +import { createHtmlPlugin } from 'vite-plugin-html' + +// https://vitejs.dev/config/ +export default defineConfig(({ command, mode }) => { + const env = loadEnv(mode, process.cwd(), '') + return { + plugins: [ + vue(), + vueJsx(), + createHtmlPlugin({ + minify: true, + template: 'public/admin.html', + }), + alias({ + entries: [ + { + find: '/admin', + replacement: resolve(rootPath, 'resources/admin'), + }, + { + find: '@/module', + replacement: resolve(rootPath, 'modules'), + }, + ], + }), + AutoImport({ + imports: ['vue', 'vue-router', 'pinia', '@vueuse/core'], + // resolvers: [ ElementPlusResolver({importStyle: 'sass'}) ] + }), + Components({ + dirs: ['resources/admin/components/', 'resources/admin/layout/'], + + extensions: ['vue'], + + deep: true, + + dts: true, + + include: [/\.vue$/, /\.vue\?vue/], + + exclude: [/[\\/]node_modules[\\/]/, /[\\/]\.git[\\/]/, /[\\/]\.nuxt[\\/]/], + // resolvers: [ ElementPlusResolver({ importStyle: 'sass'}) ] + }), + Icons({ + compiler: 'vue3', + autoInstall: true, + }), + ], + publicDir: false, + define: { + BASE_URL: env.BASE_URL, + }, + preprocessorOptions: { + scss: { + // additionalData: `@use "@/assets/styles/element.scss" as *;`, + }, + }, + server: { + host: '127.0.0.1', + port: 8000, + open: true, // 自动打开浏览器 + cors: true, // 允许跨域 + strictPort: false, // 端口占用直接退出 + hmr: true, + fs: { + allow: ['./'], + }, + }, + build: { + chunkSizeWarningLimit: 2000, + minify: 'terser', + terserOptions: { + compress: { + drop_console: false, + pure_funcs: ['console.log', 'console.info'], + drop_debugger: true, + }, + }, + // emptyOutDir: false, + outDir: 'public/admin', + assetsDir: 'assets', + rollupOptions: { + input: './public/admin.html', + output: { + chunkFileNames: 'assets/js/[name]-[hash].js', + + entryFileNames: 'assets/js/[name]-[hash].js', + + assetFileNames: 'assets/[ext]/[name]-[hash].[ext]', + }, + }, + }, + } +})