commit 0024080c28f2727e410241e2873dfb3c359efd90 Author: JaguarJack Date: Mon Dec 5 23:01:12 2022 +0800 first commit 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 0000000..5f75a16 Binary files /dev/null and b/public/favicon.ico differ 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 0000000..138814a Binary files /dev/null and b/resources/admin/assets/404.png differ diff --git a/resources/admin/assets/enum/app.ts b/resources/admin/assets/enum/app.ts new file mode 100644 index 0000000..56aae11 --- /dev/null +++ b/resources/admin/assets/enum/app.ts @@ -0,0 +1,35 @@ +/** + * 服务端返回码 + */ +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, +} + +/** + * 白名单页面 + * + * 不需要权限认证 + */ +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/assets/login-left.png b/resources/admin/assets/login-left.png new file mode 100644 index 0000000..d4a5772 Binary files /dev/null and b/resources/admin/assets/login-left.png differ diff --git a/resources/admin/assets/logo.png b/resources/admin/assets/logo.png new file mode 100644 index 0000000..c24a070 Binary files /dev/null and b/resources/admin/assets/logo.png differ diff --git a/resources/admin/components/404/index.vue b/resources/admin/components/404/index.vue new file mode 100644 index 0000000..7b0ae50 --- /dev/null +++ b/resources/admin/components/404/index.vue @@ -0,0 +1,27 @@ + + + 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]', + }, + }, + }, + } +})