commit c2b5291a746f153e58eb27470c53844c01b47baa Author: Josep Piguillem Date: Sun Jul 26 01:02:41 2026 +0200 Initial commit: OAuth IAM Laravel application Laravel-based OAuth/IAM service with Passport, Vue 3 frontend, and Docker Compose setup for local and production deployment. Co-Authored-By: Claude Sonnet 5 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4d94a73 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,28 @@ +.git +.gitignore +.env +.env.* +!.env.production.example +node_modules +vendor +public/build +public/hot +public/storage +storage/*.key +storage/api-docs +storage/framework/cache/* +storage/framework/sessions/* +storage/framework/testing/* +storage/framework/views/* +storage/logs/* +storage/pail +docs/*.pdf +bootstrap/cache/*.php +tests +.phpunit.cache +.phpunit.result.cache +.idea +.vscode +.DS_Store +Dockerfile +docker-compose*.yml diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6df8428 --- /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 + +[{compose,docker-compose}.{yml,yaml}] +indent_size = 4 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c0660ea --- /dev/null +++ b/.env.example @@ -0,0 +1,65 @@ +APP_NAME=Laravel +APP_ENV=local +APP_KEY= +APP_DEBUG=true +APP_URL=http://localhost + +APP_LOCALE=en +APP_FALLBACK_LOCALE=en +APP_FAKER_LOCALE=en_US + +APP_MAINTENANCE_DRIVER=file +# APP_MAINTENANCE_STORE=database + +# PHP_CLI_SERVER_WORKERS=4 + +BCRYPT_ROUNDS=12 + +LOG_CHANNEL=stack +LOG_STACK=single +LOG_DEPRECATIONS_CHANNEL=null +LOG_LEVEL=debug + +DB_CONNECTION=sqlite +# DB_HOST=127.0.0.1 +# DB_PORT=3306 +# DB_DATABASE=laravel +# DB_USERNAME=root +# DB_PASSWORD= + +SESSION_DRIVER=database +SESSION_LIFETIME=120 +SESSION_ENCRYPT=false +SESSION_PATH=/ +SESSION_DOMAIN=null + +BROADCAST_CONNECTION=log +FILESYSTEM_DISK=local +QUEUE_CONNECTION=database + +CACHE_STORE=database +# CACHE_PREFIX= + +MEMCACHED_HOST=127.0.0.1 + +REDIS_CLIENT=phpredis +REDIS_HOST=127.0.0.1 +REDIS_PASSWORD=null +REDIS_PORT=6379 + +MAIL_MAILER=log +MAIL_SCHEME=null +MAIL_HOST=127.0.0.1 +MAIL_PORT=2525 +MAIL_USERNAME=null +MAIL_PASSWORD=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 + +VITE_APP_NAME="${APP_NAME}" diff --git a/.env.production.example b/.env.production.example new file mode 100644 index 0000000..4e652f0 --- /dev/null +++ b/.env.production.example @@ -0,0 +1,46 @@ +APP_NAME="IAM" +APP_ENV=production +APP_KEY= +APP_DEBUG=false +APP_URL=https://tu-dominio.com + +APP_LOCALE=es +APP_FALLBACK_LOCALE=en +APP_FAKER_LOCALE=es_ES + +LOG_CHANNEL=stack +LOG_STACK=single +LOG_LEVEL=warning + +# --- Base de datos (PostgreSQL, servicio "db" del docker-compose) --- +DB_CONNECTION=pgsql +DB_HOST=db +DB_PORT=5432 +DB_DATABASE=oauth_iam +DB_USERNAME=oauth_iam +DB_PASSWORD= + +# --- Sesión / cache / colas (Redis, servicio "redis") --- +SESSION_DRIVER=redis +SESSION_LIFETIME=120 +SESSION_ENCRYPT=false +SESSION_SECURE_COOKIE=true +SESSION_DOMAIN=.tu-dominio.com + +CACHE_STORE=redis +QUEUE_CONNECTION=redis + +REDIS_CLIENT=phpredis +REDIS_HOST=redis +REDIS_PORT=6379 +REDIS_PASSWORD=null + +FILESYSTEM_DISK=local + +MAIL_MAILER=log +MAIL_FROM_ADDRESS="no-reply@tu-dominio.com" +MAIL_FROM_NAME="${APP_NAME}" + +L5_SWAGGER_GENERATE_ALWAYS=false + +VITE_APP_NAME="${APP_NAME}" diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fcb21d3 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +* text=auto eol=lf + +*.blade.php diff=html +*.css diff=css +*.html diff=html +*.md diff=markdown +*.php diff=php + +/.github export-ignore +CHANGELOG.md export-ignore +.styleci.yml export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..df1790d --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +*.log +.DS_Store +.env +.env.backup +.env.production +.phpactor.json +.phpunit.result.cache +/.claude/ +/.codex +/.cursor/ +/.idea +/.nova +/.phpunit.cache +/.vscode +/.zed +/auth.json +/node_modules +/public/build +/public/fonts-manifest.dev.json +/public/hot +/public/storage +/storage/*.key +/storage/pail +/vendor +_ide_helper.php +Homestead.json +Homestead.yaml +Thumbs.db diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..495a6af --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +ignore-scripts=true +audit=true diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..df5d1c3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,76 @@ +# syntax=docker/dockerfile:1 + +# +# ---- 1. Frontend build (Vite: Vue + TS + SCSS) ---- +# +FROM node:20-alpine AS frontend +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci +COPY resources/ resources/ +COPY vite.config.ts tsconfig.json tsconfig.node.json ./ +RUN npm run build + +# +# ---- 2. PHP dependencies (no dev, no scripts — no app code/DB available yet) ---- +# +FROM composer:2 AS vendor +WORKDIR /app +COPY composer.json composer.lock ./ +RUN composer install \ + --no-dev \ + --no-scripts \ + --no-interaction \ + --no-progress \ + --prefer-dist \ + --optimize-autoloader + +# +# ---- 3. Application image (PHP-FPM) ---- +# +FROM php:8.4-fpm-alpine AS app + +RUN apk add --no-cache \ + postgresql-libs \ + icu-libs \ + oniguruma \ + libzip \ + su-exec \ + && apk add --no-cache --virtual .build-deps \ + postgresql-dev \ + icu-dev \ + oniguruma-dev \ + libzip-dev \ + linux-headers \ + $PHPIZE_DEPS \ + && docker-php-ext-install -j$(nproc) pdo pdo_pgsql bcmath intl zip pcntl opcache \ + && pecl install redis \ + && docker-php-ext-enable redis \ + && apk del .build-deps + +COPY docker/php/production.ini /usr/local/etc/php/conf.d/zz-production.ini +COPY docker/php/opcache.ini /usr/local/etc/php/conf.d/zz-opcache.ini + +WORKDIR /var/www/html + +COPY --from=vendor /app/vendor vendor/ +COPY . . +COPY --from=frontend /app/public/build public/build + +RUN php artisan package:discover --ansi + +RUN chown -R www-data:www-data storage bootstrap/cache \ + && chmod -R 775 storage bootstrap/cache + +COPY docker/php/entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod +x /usr/local/bin/entrypoint.sh + +ENTRYPOINT ["entrypoint.sh"] +CMD ["php-fpm"] + +# +# ---- 4. Nginx (static assets + reverse proxy to php-fpm) ---- +# +FROM nginx:1.27-alpine AS nginx +COPY --from=frontend /app/public /var/www/html/public +COPY docker/nginx/default.conf /etc/nginx/conf.d/default.conf diff --git a/README.md b/README.md new file mode 100644 index 0000000..5ad1377 --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +

Laravel Logo

+ +

+Build Status +Total Downloads +Latest Stable Version +License +

+ +## About Laravel + +Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as: + +- [Simple, fast routing engine](https://laravel.com/docs/routing). +- [Powerful dependency injection container](https://laravel.com/docs/container). +- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage. +- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent). +- Database agnostic [schema migrations](https://laravel.com/docs/migrations). +- [Robust background job processing](https://laravel.com/docs/queues). +- [Real-time event broadcasting](https://laravel.com/docs/broadcasting). + +Laravel is accessible, powerful, and provides tools required for large, robust applications. + +## Learning Laravel + +Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. + +In addition, [Laracasts](https://laracasts.com) contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library. + +You can also watch bite-sized lessons with real-world projects on [Laravel Learn](https://laravel.com/learn), where you will be guided through building a Laravel application from scratch while learning PHP fundamentals. + +## Agentic Development + +Laravel's predictable structure and conventions make it ideal for AI coding agents like Claude Code, Cursor, and GitHub Copilot. Install [Laravel Boost](https://laravel.com/docs/ai) to supercharge your AI workflow: + +```bash +composer require laravel/boost --dev + +php artisan boost:install +``` + +Boost provides your agent 15+ tools and skills that help agents build Laravel applications while following best practices. + +## Contributing + +Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions). + +## Code of Conduct + +In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct). + +## Security Vulnerabilities + +If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed. + +## License + +The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). diff --git a/app/Http/Controllers/Admin/AnalyticsController.php b/app/Http/Controllers/Admin/AnalyticsController.php new file mode 100644 index 0000000..0cfd964 --- /dev/null +++ b/app/Http/Controllers/Admin/AnalyticsController.php @@ -0,0 +1,17 @@ +integer('days', 30), 7), 90); + + return response()->json(AnalyticsSummary::build($days)); + } +} diff --git a/app/Http/Controllers/Admin/AppController.php b/app/Http/Controllers/Admin/AppController.php new file mode 100644 index 0000000..a8c5672 --- /dev/null +++ b/app/Http/Controllers/Admin/AppController.php @@ -0,0 +1,20 @@ +json( + Client::query()->where('revoked', false)->orderBy('name')->get(['id', 'name']) + ); + } +} diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php new file mode 100644 index 0000000..ed26264 --- /dev/null +++ b/app/Http/Controllers/Admin/UserController.php @@ -0,0 +1,198 @@ +json( + User::query()->whereNotNull('approved_at')->orderBy('name')->get()->map($this->present(...)) + ); + } + + /** + * Usuarios borrados (soft delete) — la "papelera". + */ + public function trashed() + { + return response()->json( + User::onlyTrashed()->orderByDesc('deleted_at')->get()->map($this->present(...)) + ); + } + + /** + * Usuarios que se autorregistraron y todavía no aprobó ningún admin. + */ + public function pending() + { + return response()->json( + User::query()->whereNull('approved_at')->orderBy('created_at')->get()->map($this->present(...)) + ); + } + + public function approve(User $user) + { + $user->update(['approved_at' => now()]); + + return response()->json($this->present($user)); + } + + public function store(Request $request) + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users', 'email')], + 'password' => ['required', 'string', 'min:8'], + 'role' => ['required', Rule::in(Role::pluck('name'))], + ]); + + $user = User::create([ + 'name' => $data['name'], + 'email' => $data['email'], + 'password' => Hash::make($data['password']), + 'is_active' => true, + 'approved_at' => now(), // lo da de alta un admin: ya está aprobado. + ]); + + $user->assignRole($data['role']); + + return response()->json($this->present($user), 201); + } + + public function update(Request $request, User $user) + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users', 'email')->ignore($user->id)], + 'password' => ['nullable', 'string', 'min:8'], + 'is_active' => ['boolean'], + 'role' => ['required', Rule::in(Role::pluck('name'))], + ]); + + if ($user->id === $request->user()->id && $data['role'] !== 'admin' && $user->hasRole('admin')) { + abort(422, __('messages.cannot_remove_own_admin_role')); + } + + $user->update([ + 'name' => $data['name'], + 'email' => $data['email'], + 'is_active' => $data['is_active'] ?? $user->is_active, + ...(isset($data['password']) ? ['password' => Hash::make($data['password'])] : []), + ]); + + $user->syncRoles([$data['role']]); + + return response()->json($this->present($user->fresh())); + } + + /** + * Soft delete: el usuario deja de aparecer en el sistema pero queda + * recuperable desde la papelera. + */ + public function destroy(Request $request, User $user) + { + abort_if($user->id === $request->user()->id, 422, __('messages.cannot_delete_self')); + + if ($user->hasRole('admin') && User::role('admin')->count() <= 1) { + abort(422, __('messages.cannot_delete_last_admin')); + } + + $user->delete(); + + return response()->noContent(); + } + + public function restore(int $id) + { + $user = User::onlyTrashed()->findOrFail($id); + $user->restore(); + + return response()->json($this->present($user)); + } + + /** + * Borrado definitivo — solo tiene sentido sobre alguien que ya está en la papelera. + */ + public function forceDelete(Request $request, int $id) + { + $user = User::onlyTrashed()->findOrFail($id); + + $user->forceDelete(); + + return response()->noContent(); + } + + /** + * Roles que este usuario tiene asignados en cada app (cliente OAuth), agrupados. + * Es un sistema aparte del rol global de spatie: vive en app_user_roles, + * no en las tablas de spatie (evita mezclar "quién administra este panel" + * con "qué rol tiene este usuario dentro de la app de un tercero"). + */ + public function appRoles(User $user) + { + $rows = DB::table('app_user_roles') + ->join('oauth_clients', 'oauth_clients.id', '=', 'app_user_roles.client_id') + ->where('app_user_roles.user_id', $user->id) + ->get(['oauth_clients.id as client_id', 'oauth_clients.name as client_name', 'app_user_roles.role']); + + return response()->json( + $rows->groupBy('client_id')->map(fn ($group) => [ + 'client_id' => $group->first()->client_id, + 'client_name' => $group->first()->client_name, + 'roles' => $group->pluck('role'), + ])->values() + ); + } + + public function assignAppRole(Request $request, User $user) + { + $data = $request->validate([ + 'client_id' => ['required', 'exists:oauth_clients,id'], + 'role' => ['required', Rule::in(Role::pluck('name'))], + ]); + + DB::table('app_user_roles')->insertOrIgnore([ + 'user_id' => $user->id, + 'client_id' => $data['client_id'], + 'role' => $data['role'], + 'created_at' => now(), + 'updated_at' => now(), + ]); + + return response()->json($data, 201); + } + + public function revokeAppRole(User $user, string $clientId, string $role) + { + DB::table('app_user_roles') + ->where('user_id', $user->id) + ->where('client_id', $clientId) + ->where('role', $role) + ->delete(); + + return response()->noContent(); + } + + private function present(User $user): array + { + return [ + 'id' => $user->id, + 'name' => $user->name, + 'email' => $user->email, + 'is_active' => $user->is_active, + 'roles' => $user->getRoleNames(), + 'created_at' => $user->created_at, + 'deleted_at' => $user->deleted_at, + 'approved_at' => $user->approved_at, + ]; + } +} diff --git a/app/Http/Controllers/Api/AnalyticsController.php b/app/Http/Controllers/Api/AnalyticsController.php new file mode 100644 index 0000000..c7e16cf --- /dev/null +++ b/app/Http/Controllers/Api/AnalyticsController.php @@ -0,0 +1,43 @@ + ['users:read']]], + tags: ['Analytics'], + parameters: [ + new OA\Parameter(name: 'days', in: 'query', required: false, description: 'Ventana en días (7-90, default 30)', schema: new OA\Schema(type: 'integer')), + ], + responses: [ + new OA\Response(response: 200, description: 'Resumen de analíticas'), + new OA\Response(response: 403, description: 'Sin permiso users.view'), + ] + )] + public function summary(Request $request) + { + $actor = $request->user('api'); + abort_if($actor && ! $actor->can('users.view'), 403); + + $days = min(max((int) $request->integer('days', 30), 7), 90); + + return response()->json(AnalyticsSummary::build($days)); + } +} diff --git a/app/Http/Controllers/Api/AppController.php b/app/Http/Controllers/Api/AppController.php new file mode 100644 index 0000000..ba454d3 --- /dev/null +++ b/app/Http/Controllers/Api/AppController.php @@ -0,0 +1,41 @@ + ['users:read']]], + tags: ['Users'], + responses: [ + new OA\Response(response: 200, description: 'Listado de apps'), + new OA\Response(response: 403, description: 'Sin permiso users.view'), + ] + )] + public function index(Request $request) + { + $actor = $request->user('api'); + + abort_if($actor && ! $actor->can('users.view'), 403); + + return response()->json( + Client::query()->where('revoked', false)->orderBy('name')->get(['id', 'name']) + ); + } +} diff --git a/app/Http/Controllers/Api/AuthorizedAppController.php b/app/Http/Controllers/Api/AuthorizedAppController.php new file mode 100644 index 0000000..9e28abb --- /dev/null +++ b/app/Http/Controllers/Api/AuthorizedAppController.php @@ -0,0 +1,71 @@ + ['account:read']]], + tags: ['Account'], + responses: [new OA\Response(response: 200, description: 'Listado de tokens/apps autorizadas')] + )] + public function index(Request $request) + { + $user = $this->requireUser($request); + + $tokens = $user->tokens() + ->where('revoked', false) + ->with('client') + ->get() + ->reject(fn ($token) => $token->client->revoked || $token->client->hasGrantType('personal_access')) + ->values(); + + return response()->json($tokens); + } + + #[OA\Delete( + path: '/authorized-apps/{tokenId}', + summary: 'Revocar el acceso de una app de terceros', + security: [['oauth2' => ['account:write']]], + tags: ['Account'], + parameters: [new OA\Parameter(name: 'tokenId', in: 'path', required: true, schema: new OA\Schema(type: 'string'))], + responses: [ + new OA\Response(response: 204, description: 'Acceso revocado'), + new OA\Response(response: 404, description: 'No existe o no es tuyo'), + ] + )] + public function destroy(Request $request, string $tokenId) + { + $token = $this->requireUser($request)->tokens()->where('id', $tokenId)->first(); + + abort_unless($token, 404); + + $token->revoke(); + $token->refreshToken?->revoke(); + + return response()->noContent(); + } + + private function requireUser(Request $request): User + { + return $request->user('api') ?? abort(422, __('messages.self_service_requires_user')); + } +} diff --git a/app/Http/Controllers/Api/ClientController.php b/app/Http/Controllers/Api/ClientController.php new file mode 100644 index 0000000..0f1d171 --- /dev/null +++ b/app/Http/Controllers/Api/ClientController.php @@ -0,0 +1,170 @@ + ['account:read']]], + tags: ['Account'], + responses: [ + new OA\Response(response: 200, description: 'Listado de clientes', content: new OA\JsonContent(type: 'array', items: new OA\Items(ref: '#/components/schemas/Client'))), + new OA\Response(response: 422, description: 'El token no tiene un usuario asociado (ej. client_credentials)'), + ] + )] + public function index(Request $request) + { + return response()->json( + $this->requireUser($request)->oauthApps()->where('revoked', false)->orderByDesc('created_at')->get() + ); + } + + #[OA\Post( + path: '/clients', + summary: 'Registrar un nuevo cliente OAuth (tu app)', + security: [['oauth2' => ['account:write']]], + tags: ['Account'], + requestBody: new OA\RequestBody( + required: true, + content: new OA\JsonContent( + required: ['name', 'grant_type'], + properties: [ + new OA\Property(property: 'name', type: 'string'), + new OA\Property(property: 'grant_type', type: 'string', enum: ['authorization_code', 'client_credentials', 'password']), + new OA\Property(property: 'redirect_uris', type: 'array', items: new OA\Items(type: 'string')), + new OA\Property(property: 'confidential', type: 'boolean', default: true), + ] + ) + ), + responses: [ + new OA\Response(response: 201, description: 'Cliente creado (incluye plain_secret una sola vez)'), + new OA\Response(response: 422, description: 'Datos inválidos o token sin usuario asociado'), + ] + )] + public function store(Request $request) + { + $user = $this->requireUser($request); + + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'grant_type' => ['required', Rule::in(['authorization_code', 'client_credentials', 'password'])], + 'redirect_uris' => ['required_if:grant_type,authorization_code', 'array'], + 'redirect_uris.*' => ['url'], + 'confidential' => ['boolean'], + ]); + + $confidential = $request->boolean('confidential', true); + + $grantTypes = match ($data['grant_type']) { + 'authorization_code' => ['authorization_code', 'refresh_token'], + 'password' => ['password', 'refresh_token'], + 'client_credentials' => ['client_credentials'], + }; + + // client_credentials always needs a secret: there is no user present to prove identity. + if ($data['grant_type'] === 'client_credentials') { + $confidential = true; + } + + $client = $user->oauthApps()->create([ + 'name' => $data['name'], + 'secret' => $confidential ? Str::random(40) : null, + 'provider' => null, + 'redirect_uris' => $data['redirect_uris'] ?? [], + 'grant_types' => $grantTypes, + 'revoked' => false, + ]); + + return response()->json([ + 'client' => $client, + 'plain_secret' => $client->plainSecret, + ], 201); + } + + #[OA\Put( + path: '/clients/{client}', + summary: 'Editar uno de tus clientes OAuth', + security: [['oauth2' => ['account:write']]], + tags: ['Account'], + parameters: [new OA\Parameter(name: 'client', in: 'path', required: true, schema: new OA\Schema(type: 'string'))], + responses: [ + new OA\Response(response: 200, description: 'Cliente actualizado', content: new OA\JsonContent(ref: '#/components/schemas/Client')), + new OA\Response(response: 404, description: 'No es tuyo o no existe'), + ] + )] + public function update(Request $request, Client $client) + { + abort_unless($client->owner_id === $this->requireUser($request)->id, 404); + + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'redirect_uris' => ['array'], + 'redirect_uris.*' => ['url'], + ]); + + $client->update([ + 'name' => $data['name'], + 'redirect_uris' => $data['redirect_uris'] ?? $client->redirect_uris, + ]); + + return response()->json($client); + } + + #[OA\Delete( + path: '/clients/{client}', + summary: 'Revocar uno de tus clientes OAuth (y sus tokens)', + security: [['oauth2' => ['account:write']]], + tags: ['Account'], + parameters: [new OA\Parameter(name: 'client', in: 'path', required: true, schema: new OA\Schema(type: 'string'))], + responses: [ + new OA\Response(response: 204, description: 'Revocado'), + new OA\Response(response: 404, description: 'No es tuyo o no existe'), + ] + )] + public function destroy(Request $request, Client $client) + { + abort_unless($client->owner_id === $this->requireUser($request)->id, 404); + + $client->update(['revoked' => true]); + $client->tokens()->update(['revoked' => true]); + + return response()->noContent(); + } + + private function requireUser(Request $request): User + { + return $request->user('api') ?? abort(422, __('messages.self_service_requires_user')); + } +} diff --git a/app/Http/Controllers/Api/PersonalAccessTokenController.php b/app/Http/Controllers/Api/PersonalAccessTokenController.php new file mode 100644 index 0000000..9af9ed8 --- /dev/null +++ b/app/Http/Controllers/Api/PersonalAccessTokenController.php @@ -0,0 +1,106 @@ + ['account:read']]], + tags: ['Account'], + responses: [new OA\Response(response: 200, description: 'Listado de tokens')] + )] + public function index(Request $request) + { + return response()->json( + $this->requireUser($request)->tokens() + ->where('revoked', false) + ->whereHas('client', fn ($q) => $q->whereJsonContains('grant_types', 'personal_access')) + ->orderByDesc('created_at') + ->get(['id', 'name', 'scopes', 'created_at', 'expires_at']) + ); + } + + #[OA\Post( + path: '/personal-access-tokens', + summary: 'Crear un Personal Access Token (tipo API key)', + security: [['oauth2' => ['account:write']]], + tags: ['Account'], + requestBody: new OA\RequestBody( + required: true, + content: new OA\JsonContent( + required: ['name'], + properties: [ + new OA\Property(property: 'name', type: 'string'), + new OA\Property(property: 'scopes', type: 'array', items: new OA\Items(type: 'string')), + ] + ) + ), + responses: [new OA\Response(response: 201, description: 'Token creado (access_token solo se muestra esta vez)')] + )] + public function store(Request $request) + { + $user = $this->requireUser($request); + + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'scopes' => ['array'], + 'scopes.*' => [Rule::in(Passport::scopeIds())], + ]); + + $result = $user->createToken($data['name'], $data['scopes'] ?? []); + + return response()->json([ + 'access_token' => $result->accessToken, + 'token' => $result->token, + ], 201); + } + + #[OA\Delete( + path: '/personal-access-tokens/{tokenId}', + summary: 'Revocar un Personal Access Token', + security: [['oauth2' => ['account:write']]], + tags: ['Account'], + parameters: [new OA\Parameter(name: 'tokenId', in: 'path', required: true, schema: new OA\Schema(type: 'string'))], + responses: [ + new OA\Response(response: 204, description: 'Revocado'), + new OA\Response(response: 404, description: 'No existe o no es tuyo'), + ] + )] + public function destroy(Request $request, string $tokenId) + { + /** @var Token|null $token */ + $token = $this->requireUser($request)->tokens()->where('id', $tokenId)->first(); + + abort_unless($token, 404); + + $token->revoke(); + + return response()->noContent(); + } + + private function requireUser(Request $request): User + { + return $request->user('api') ?? abort(422, __('messages.self_service_requires_user')); + } +} diff --git a/app/Http/Controllers/Api/UserController.php b/app/Http/Controllers/Api/UserController.php new file mode 100644 index 0000000..fcfb95f --- /dev/null +++ b/app/Http/Controllers/Api/UserController.php @@ -0,0 +1,490 @@ + []]], + tags: ['Users'], + responses: [ + new OA\Response(response: 200, description: 'Usuario autenticado', content: new OA\JsonContent(ref: '#/components/schemas/User')), + new OA\Response(response: 401, description: 'Token inválido o sin usuario asociado (ej. client_credentials)'), + ] + )] + public function me(Request $request) + { + return response()->json($this->present($request->user())); + } + + #[OA\Get( + path: '/users', + summary: 'Listar usuarios', + description: 'Incluye usuarios pendientes de aprobación del panel admin (ver campo approved_at) — esa aprobación solo gobierna el acceso al panel de sesión de esta app, no la consulta de usuarios vía API.', + security: [['oauth2' => ['users:read']]], + tags: ['Users'], + responses: [ + new OA\Response(response: 200, description: 'Listado de usuarios', content: new OA\JsonContent(type: 'array', items: new OA\Items(ref: '#/components/schemas/User'))), + new OA\Response(response: 403, description: 'Sin permiso users.view'), + ] + )] + public function index(Request $request) + { + $this->authorize($request, 'users.view'); + + // A diferencia del panel admin (Admin\UserController), acá NO se + // esconden los usuarios pendientes de aprobación: "aprobado" es un + // concepto del panel de esta app (¿puede entrar a /admin?), no algo + // que le importe a otra app que solo está consultando/consumiendo + // usuarios por la API. + return response()->json( + User::query()->with('roles:id,name')->orderBy('name')->get()->map($this->present(...)) + ); + } + + #[OA\Post( + path: '/users', + summary: 'Crear un usuario', + security: [['oauth2' => ['users:write']]], + tags: ['Users'], + requestBody: new OA\RequestBody( + required: true, + content: new OA\JsonContent( + required: ['name', 'email', 'password'], + properties: [ + new OA\Property(property: 'name', type: 'string'), + new OA\Property(property: 'email', type: 'string', format: 'email'), + new OA\Property(property: 'password', type: 'string', format: 'password'), + new OA\Property(property: 'role', type: 'string', example: 'user'), + ] + ) + ), + responses: [ + new OA\Response(response: 201, description: 'Usuario creado', content: new OA\JsonContent(ref: '#/components/schemas/User')), + new OA\Response(response: 403, description: 'Sin permiso users.create'), + ] + )] + public function store(Request $request) + { + $this->authorize($request, 'users.create'); + + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users', 'email')], + 'password' => ['required', 'string', 'min:8'], + 'role' => ['sometimes', Rule::in(Role::pluck('name'))], + ]); + + $user = User::create([ + 'name' => $data['name'], + 'email' => $data['email'], + 'password' => Hash::make($data['password']), + 'is_active' => true, + 'approved_at' => now(), // lo crea un admin/servicio de confianza: ya está aprobado. + ]); + + $user->assignRole($data['role'] ?? 'user'); + + return response()->json($this->present($user), 201); + } + + #[OA\Get( + path: '/users/{user}', + summary: 'Ver un usuario (el propio siempre permitido; otros requieren users.view)', + security: [['oauth2' => ['users:read']]], + tags: ['Users'], + parameters: [new OA\Parameter(name: 'user', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))], + responses: [ + new OA\Response(response: 200, description: 'Datos del usuario', content: new OA\JsonContent(ref: '#/components/schemas/User')), + new OA\Response(response: 403, description: 'Sin permiso users.view'), + ] + )] + public function show(Request $request, User $user) + { + $this->authorize($request, 'users.view', $user); + + return response()->json($this->present($user)); + } + + #[OA\Put( + path: '/users/{user}', + summary: 'Editar un usuario (el propio siempre permitido; rol/estado y otros usuarios requieren users.update)', + security: [['oauth2' => ['users:write']]], + tags: ['Users'], + parameters: [new OA\Parameter(name: 'user', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))], + requestBody: new OA\RequestBody( + required: true, + content: new OA\JsonContent( + properties: [ + new OA\Property(property: 'name', type: 'string'), + new OA\Property(property: 'email', type: 'string', format: 'email'), + new OA\Property(property: 'password', type: 'string', format: 'password'), + new OA\Property(property: 'is_active', type: 'boolean'), + new OA\Property(property: 'role', type: 'string'), + ] + ) + ), + responses: [ + new OA\Response(response: 200, description: 'Usuario actualizado', content: new OA\JsonContent(ref: '#/components/schemas/User')), + new OA\Response(response: 403, description: 'Sin permiso users.update'), + ] + )] + public function update(Request $request, User $user) + { + $actor = $this->authorize($request, 'users.update', $user); + $isAdminAction = is_null($actor) || $actor->can('users.update'); + + $data = $request->validate([ + 'name' => ['sometimes', 'string', 'max:255'], + 'email' => ['sometimes', 'string', 'email', 'max:255', Rule::unique('users', 'email')->ignore($user->id)], + 'password' => ['sometimes', 'string', 'min:8'], + 'is_active' => ['sometimes', 'boolean'], + 'role' => ['sometimes', Rule::in(Role::pluck('name'))], + ]); + + if (! $isAdminAction) { + $data = array_intersect_key($data, array_flip(['name', 'email', 'password'])); + } + + if (isset($data['role']) && $user->id === $actor?->id && $data['role'] !== 'admin' && $user->hasRole('admin')) { + abort(422, __('messages.cannot_remove_own_admin_role')); + } + + $user->update([ + ...array_diff_key($data, array_flip(['password', 'role'])), + ...(isset($data['password']) ? ['password' => Hash::make($data['password'])] : []), + ]); + + if (isset($data['role'])) { + $user->syncRoles([$data['role']]); + } + + return response()->json($this->present($user->fresh())); + } + + #[OA\Delete( + path: '/users/{user}', + summary: 'Borrar un usuario (requiere users.delete; no se puede autoeliminar)', + security: [['oauth2' => ['users:write']]], + tags: ['Users'], + parameters: [new OA\Parameter(name: 'user', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))], + responses: [ + new OA\Response(response: 204, description: 'Usuario borrado'), + new OA\Response(response: 403, description: 'Sin permiso users.delete'), + new OA\Response(response: 422, description: 'No se puede autoeliminar ni borrar al último admin'), + ] + )] + public function destroy(Request $request, User $user) + { + $actor = $request->user('api'); + + abort_if($actor && $actor->id === $user->id, 422, __('messages.cannot_delete_self')); + abort_if($actor && ! $actor->can('users.delete'), 403); + + if ($user->hasRole('admin') && User::role('admin')->count() <= 1) { + abort(422, __('messages.cannot_delete_last_admin')); + } + + $user->delete(); + + return response()->noContent(); + } + + #[OA\Get( + path: '/users/trashed', + summary: 'Listar usuarios borrados (papelera)', + security: [['oauth2' => ['users:read']]], + tags: ['Users'], + responses: [ + new OA\Response(response: 200, description: 'Listado de usuarios en la papelera', content: new OA\JsonContent(type: 'array', items: new OA\Items(ref: '#/components/schemas/User'))), + new OA\Response(response: 403, description: 'Sin permiso users.view'), + ] + )] + public function trashed(Request $request) + { + $this->authorize($request, 'users.view'); + + return response()->json( + User::onlyTrashed()->orderByDesc('deleted_at')->get()->map($this->present(...)) + ); + } + + #[OA\Post( + path: '/users/{id}/restore', + summary: 'Restaurar un usuario borrado', + security: [['oauth2' => ['users:write']]], + tags: ['Users'], + parameters: [new OA\Parameter(name: 'id', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))], + responses: [ + new OA\Response(response: 200, description: 'Usuario restaurado', content: new OA\JsonContent(ref: '#/components/schemas/User')), + new OA\Response(response: 403, description: 'Sin permiso users.update'), + ] + )] + public function restore(Request $request, int $id) + { + $actor = $request->user('api'); + abort_if($actor && ! $actor->can('users.update'), 403); + + $user = User::onlyTrashed()->findOrFail($id); + $user->restore(); + + return response()->json($this->present($user)); + } + + #[OA\Delete( + path: '/users/{id}/force', + summary: 'Borrar un usuario definitivamente (tiene que estar en la papelera)', + security: [['oauth2' => ['users:write']]], + tags: ['Users'], + parameters: [new OA\Parameter(name: 'id', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))], + responses: [ + new OA\Response(response: 204, description: 'Borrado definitivamente'), + new OA\Response(response: 403, description: 'Sin permiso users.delete'), + ] + )] + public function forceDelete(Request $request, int $id) + { + $actor = $request->user('api'); + abort_if($actor && ! $actor->can('users.delete'), 403); + + $user = User::onlyTrashed()->findOrFail($id); + $user->forceDelete(); + + return response()->noContent(); + } + + #[OA\Get( + path: '/users/pending', + summary: 'Listar usuarios autorregistrados que todavía no aprobó ningún admin', + security: [['oauth2' => ['users:read']]], + tags: ['Users'], + responses: [ + new OA\Response(response: 200, description: 'Listado de usuarios pendientes', content: new OA\JsonContent(type: 'array', items: new OA\Items(ref: '#/components/schemas/User'))), + new OA\Response(response: 403, description: 'Sin permiso users.view'), + ] + )] + public function pending(Request $request) + { + $this->authorize($request, 'users.view'); + + return response()->json( + User::query()->whereNull('approved_at')->orderBy('created_at')->get()->map($this->present(...)) + ); + } + + #[OA\Post( + path: '/users/{user}/approve', + summary: 'Aprobar un registro pendiente', + security: [['oauth2' => ['users:write']]], + tags: ['Users'], + parameters: [new OA\Parameter(name: 'user', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))], + responses: [ + new OA\Response(response: 200, description: 'Usuario aprobado', content: new OA\JsonContent(ref: '#/components/schemas/User')), + new OA\Response(response: 403, description: 'Sin permiso users.update'), + ] + )] + public function approve(Request $request, User $user) + { + $actor = $request->user('api'); + abort_if($actor && ! $actor->can('users.update'), 403); + + $user->update(['approved_at' => now()]); + + return response()->json($this->present($user)); + } + + #[OA\Get( + path: '/users/{user}/app-roles', + summary: 'Roles que el usuario tiene en cada app (el propio siempre permitido; otros requieren users.view)', + security: [['oauth2' => ['users:read']]], + tags: ['Users'], + parameters: [new OA\Parameter(name: 'user', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))], + responses: [new OA\Response(response: 200, description: 'Roles agrupados por app')] + )] + public function appRoles(Request $request, User $user) + { + $this->authorize($request, 'users.view', $user); + + $rows = DB::table('app_user_roles') + ->join('oauth_clients', 'oauth_clients.id', '=', 'app_user_roles.client_id') + ->where('app_user_roles.user_id', $user->id) + ->get(['oauth_clients.id as client_id', 'oauth_clients.name as client_name', 'app_user_roles.role']); + + return response()->json( + $rows->groupBy('client_id')->map(fn ($group) => [ + 'client_id' => $group->first()->client_id, + 'client_name' => $group->first()->client_name, + 'roles' => $group->pluck('role'), + ])->values() + ); + } + + #[OA\Post( + path: '/users/{user}/app-roles', + summary: 'Asignar un rol a un usuario dentro de una app (requiere users.update; sin bypass por self)', + security: [['oauth2' => ['users:write']]], + tags: ['Users'], + parameters: [new OA\Parameter(name: 'user', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))], + requestBody: new OA\RequestBody( + required: true, + content: new OA\JsonContent( + required: ['client_id', 'role'], + properties: [ + new OA\Property(property: 'client_id', type: 'string', format: 'uuid'), + new OA\Property(property: 'role', type: 'string', example: 'admin'), + ] + ) + ), + responses: [ + new OA\Response(response: 201, description: 'Rol asignado'), + new OA\Response(response: 403, description: 'Sin permiso users.update'), + ] + )] + public function assignAppRole(Request $request, User $user) + { + $actor = $request->user('api'); + abort_if($actor && ! $actor->can('users.update'), 403); + + $data = $request->validate([ + 'client_id' => ['required', 'exists:oauth_clients,id'], + 'role' => ['required', Rule::in(Role::pluck('name'))], + ]); + + DB::table('app_user_roles')->insertOrIgnore([ + 'user_id' => $user->id, + 'client_id' => $data['client_id'], + 'role' => $data['role'], + 'created_at' => now(), + 'updated_at' => now(), + ]); + + return response()->json($data, 201); + } + + #[OA\Delete( + path: '/users/{user}/app-roles/{clientId}/{role}', + summary: 'Quitarle a un usuario un rol dentro de una app (requiere users.update)', + security: [['oauth2' => ['users:write']]], + tags: ['Users'], + parameters: [ + new OA\Parameter(name: 'user', in: 'path', required: true, schema: new OA\Schema(type: 'integer')), + new OA\Parameter(name: 'clientId', in: 'path', required: true, schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'role', in: 'path', required: true, schema: new OA\Schema(type: 'string')), + ], + responses: [ + new OA\Response(response: 204, description: 'Rol quitado'), + new OA\Response(response: 403, description: 'Sin permiso users.update'), + ] + )] + public function revokeAppRole(Request $request, User $user, string $clientId, string $role) + { + $actor = $request->user('api'); + abort_if($actor && ! $actor->can('users.update'), 403); + + DB::table('app_user_roles') + ->where('user_id', $user->id) + ->where('client_id', $clientId) + ->where('role', $role) + ->delete(); + + return response()->noContent(); + } + + /** + * Un token client_credentials (sin usuario) actúa como servicio de confianza: + * ya validado por scope, no necesita permiso de usuario. Si hay usuario, puede + * operar siempre sobre sí mismo; sobre otros usuarios necesita el permiso dado. + */ + private function authorize(Request $request, string $permission, ?User $target = null): ?User + { + $actor = $request->user('api'); + + if (! $actor) { + return null; + } + + $isSelf = $target && $target->id === $actor->id; + + if (! $isSelf && ! $actor->can($permission)) { + abort(403); + } + + return $actor; + } + + /** + * Devuelve el usuario más "app_roles": los roles que tiene asignados + * específicamente para la app que está llamando (el cliente OAuth del + * token actual), guardados en app_user_roles — un sistema aparte del rol + * global de spatie (ver Admin\UserController::appRoles). + */ + private function present(User $user): array + { + $callingClientId = Auth::guard('api')->client()?->id; + + $appRoles = $callingClientId + ? DB::table('app_user_roles') + ->where('user_id', $user->id) + ->where('client_id', $callingClientId) + ->pluck('role') + : collect(); + + return [ + 'id' => $user->id, + 'name' => $user->name, + 'email' => $user->email, + 'is_active' => $user->is_active, + 'roles' => $user->getRoleNames(), + 'app_roles' => $appRoles, + 'created_at' => $user->created_at, + 'approved_at' => $user->approved_at, + ]; + } +} diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php new file mode 100644 index 0000000..d221bdb --- /dev/null +++ b/app/Http/Controllers/Auth/AuthController.php @@ -0,0 +1,141 @@ +json([ + 'empty' => User::query()->count() === 0, + ]); + } + + public function register(Request $request) + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users', 'email')], + 'password' => ['required', 'string', 'min:8', 'confirmed'], + ]); + + // Instalación nueva (sin usuarios todavía): el primero en registrarse + // queda como admin, aprobado y logueado de una — no hay nadie más que + // pueda aprobarlo. De ahí en más, todo registro vuelve a requerir + // aprobación de un admin como siempre. + $isFirstUser = User::query()->count() === 0; + + $user = User::create([ + 'name' => $data['name'], + 'email' => $data['email'], + 'password' => Hash::make($data['password']), + 'is_active' => true, + 'approved_at' => $isFirstUser ? now() : null, + ]); + + if ($isFirstUser) { + (new RolesAndPermissionsSeeder)->run(); + $user->assignRole('admin'); + + Auth::login($user); + $request->session()->regenerate(); + $this->recordSessionLogin($request, $user); + + return response()->json([ + 'bootstrap' => true, + 'user' => $this->present($user), + 'intended' => $request->session()->pull('url.intended'), + ]); + } + + $user->assignRole('user'); + + return response()->json([ + 'pending' => true, + 'message' => __('messages.registration_pending'), + ], 201); + } + + public function login(Request $request) + { + $credentials = $request->validate([ + 'email' => ['required', 'string', 'email'], + 'password' => ['required', 'string'], + ]); + + if (! Auth::attempt($credentials, $request->boolean('remember'))) { + throw ValidationException::withMessages([ + 'email' => __('messages.invalid_credentials'), + ]); + } + + if (! Auth::user()->is_active) { + Auth::logout(); + + throw ValidationException::withMessages([ + 'email' => __('messages.account_deactivated'), + ]); + } + + if (Auth::user()->isPending()) { + Auth::logout(); + + throw ValidationException::withMessages([ + 'email' => __('messages.account_pending_approval'), + ]); + } + + $request->session()->regenerate(); + $this->recordSessionLogin($request, Auth::user()); + + return response()->json([ + 'user' => $this->present(Auth::user()), + 'intended' => $request->session()->pull('url.intended'), + ]); + } + + public function logout(Request $request) + { + Auth::logout(); + $request->session()->invalidate(); + $request->session()->regenerateToken(); + + return response()->noContent(); + } + + public function me(Request $request) + { + return response()->json(['user' => $this->present($request->user())]); + } + + private function recordSessionLogin(Request $request, User $user): void + { + LoginEvent::create([ + 'user_id' => $user->id, + 'client_id' => null, + 'type' => 'session', + 'ip_address' => $request->ip(), + 'user_agent' => $request->userAgent(), + 'created_at' => now(), + ]); + } + + private function present(User $user): array + { + return [ + ...$user->toArray(), + 'roles' => $user->getRoleNames(), + 'permissions' => $user->getAllPermissions()->pluck('name'), + ]; + } +} diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php new file mode 100644 index 0000000..09c3e74 --- /dev/null +++ b/app/Http/Controllers/Controller.php @@ -0,0 +1,55 @@ + 'Leer usuarios', + 'users:write' => 'Crear, editar y borrar usuarios', + 'account:read' => 'Leer tus clientes OAuth, tokens y apps autorizadas', + 'account:write' => 'Gestionar tus clientes OAuth, tokens y apps autorizadas', + ] + ), + new OA\Flow( + flow: 'clientCredentials', + tokenUrl: '/oauth/token', + scopes: [ + 'users:read' => 'Leer usuarios', + 'users:write' => 'Crear, editar y borrar usuarios', + 'account:read' => 'Leer tus clientes OAuth, tokens y apps autorizadas', + 'account:write' => 'Gestionar tus clientes OAuth, tokens y apps autorizadas', + ] + ), + new OA\Flow( + flow: 'password', + tokenUrl: '/oauth/token', + scopes: [ + 'users:read' => 'Leer usuarios', + 'users:write' => 'Crear, editar y borrar usuarios', + 'account:read' => 'Leer tus clientes OAuth, tokens y apps autorizadas', + 'account:write' => 'Gestionar tus clientes OAuth, tokens y apps autorizadas', + ] + ), + ] +)] +abstract class Controller +{ + // +} diff --git a/app/Http/Controllers/OAuth/AuthorizedAppController.php b/app/Http/Controllers/OAuth/AuthorizedAppController.php new file mode 100644 index 0000000..4aad1a3 --- /dev/null +++ b/app/Http/Controllers/OAuth/AuthorizedAppController.php @@ -0,0 +1,36 @@ +user()->tokens() + ->where('revoked', false) + ->with('client') + ->get() + ->reject(fn ($token) => $token->client->revoked || $token->client->hasGrantType('personal_access')) + ->values(); + + return response()->json($tokens); + } + + public function destroy(Request $request, string $tokenId) + { + $token = $request->user()->tokens()->where('id', $tokenId)->first(); + + abort_unless($token, 404); + + $token->revoke(); + $token->refreshToken?->revoke(); + + return response()->noContent(); + } +} diff --git a/app/Http/Controllers/OAuth/ClientController.php b/app/Http/Controllers/OAuth/ClientController.php new file mode 100644 index 0000000..3b29318 --- /dev/null +++ b/app/Http/Controllers/OAuth/ClientController.php @@ -0,0 +1,97 @@ +json( + $request->user()->oauthApps()->where('revoked', false)->orderByDesc('created_at')->get() + ); + } + + /** + * Create a new OAuth client (authorization_code+PKCE, client_credentials or password). + */ + public function store(Request $request) + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'grant_type' => ['required', Rule::in(['authorization_code', 'client_credentials', 'password'])], + 'redirect_uris' => ['required_if:grant_type,authorization_code', 'array'], + 'redirect_uris.*' => ['url'], + 'confidential' => ['boolean'], + ]); + + $confidential = $request->boolean('confidential', true); + + $grantTypes = match ($data['grant_type']) { + 'authorization_code' => ['authorization_code', 'refresh_token'], + 'password' => ['password', 'refresh_token'], + 'client_credentials' => ['client_credentials'], + }; + + // client_credentials always needs a secret: there is no user present to prove identity. + if ($data['grant_type'] === 'client_credentials') { + $confidential = true; + } + + $client = $request->user()->oauthApps()->create([ + 'name' => $data['name'], + 'secret' => $confidential ? Str::random(40) : null, + 'provider' => null, + 'redirect_uris' => $data['redirect_uris'] ?? [], + 'grant_types' => $grantTypes, + 'revoked' => false, + ]); + + return response()->json([ + 'client' => $client, + 'plain_secret' => $client->plainSecret, + ], 201); + } + + /** + * Update an existing client's name/redirect URIs. + */ + public function update(Request $request, Client $client) + { + abort_unless($client->owner_id === $request->user()->id, 404); + + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'redirect_uris' => ['array'], + 'redirect_uris.*' => ['url'], + ]); + + $client->update([ + 'name' => $data['name'], + 'redirect_uris' => $data['redirect_uris'] ?? $client->redirect_uris, + ]); + + return response()->json($client); + } + + /** + * Revoke (soft-delete) a client and its tokens. + */ + public function destroy(Request $request, Client $client) + { + abort_unless($client->owner_id === $request->user()->id, 404); + + $client->update(['revoked' => true]); + $client->tokens()->update(['revoked' => true]); + + return response()->noContent(); + } +} diff --git a/app/Http/Controllers/OAuth/PersonalAccessTokenController.php b/app/Http/Controllers/OAuth/PersonalAccessTokenController.php new file mode 100644 index 0000000..4f1fadf --- /dev/null +++ b/app/Http/Controllers/OAuth/PersonalAccessTokenController.php @@ -0,0 +1,51 @@ +json( + $request->user()->tokens() + ->where('revoked', false) + ->whereHas('client', fn ($q) => $q->whereJsonContains('grant_types', 'personal_access')) + ->orderByDesc('created_at') + ->get(['id', 'name', 'scopes', 'created_at', 'expires_at']) + ); + } + + public function store(Request $request) + { + $data = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'scopes' => ['array'], + 'scopes.*' => [Rule::in(Passport::scopeIds())], + ]); + + $result = $request->user()->createToken($data['name'], $data['scopes'] ?? []); + + return response()->json([ + 'access_token' => $result->accessToken, + 'token' => $result->token, + ], 201); + } + + public function destroy(Request $request, string $tokenId) + { + /** @var Token|null $token */ + $token = $request->user()->tokens()->where('id', $tokenId)->first(); + + abort_unless($token, 404); + + $token->revoke(); + + return response()->noContent(); + } +} diff --git a/app/Http/Middleware/SetLocale.php b/app/Http/Middleware/SetLocale.php new file mode 100644 index 0000000..24b329d --- /dev/null +++ b/app/Http/Middleware/SetLocale.php @@ -0,0 +1,22 @@ +session()->get('locale', config('app.locale')); + + App::setLocale(in_array($locale, self::SUPPORTED, true) ? $locale : config('app.locale')); + + return $next($request); + } +} diff --git a/app/Listeners/RecordOAuthLoginEvent.php b/app/Listeners/RecordOAuthLoginEvent.php new file mode 100644 index 0000000..1b74de8 --- /dev/null +++ b/app/Listeners/RecordOAuthLoginEvent.php @@ -0,0 +1,31 @@ + $event->userId, + 'client_id' => $event->clientId, + 'type' => 'oauth', + 'ip_address' => $this->request->ip(), + 'user_agent' => $this->request->userAgent(), + 'created_at' => now(), + ]); + } +} diff --git a/app/Models/LoginEvent.php b/app/Models/LoginEvent.php new file mode 100644 index 0000000..99319fa --- /dev/null +++ b/app/Models/LoginEvent.php @@ -0,0 +1,24 @@ +belongsTo(User::class); + } + + public function client(): BelongsTo + { + return $this->belongsTo(Client::class); + } +} diff --git a/app/Models/User.php b/app/Models/User.php new file mode 100644 index 0000000..fac7787 --- /dev/null +++ b/app/Models/User.php @@ -0,0 +1,43 @@ + */ + use HasApiTokens, HasFactory, HasRoles, Notifiable, SoftDeletes; + + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return [ + 'email_verified_at' => 'datetime', + 'password' => 'hashed', + 'is_active' => 'boolean', + 'deleted_at' => 'datetime', + 'approved_at' => 'datetime', + ]; + } + + public function isPending(): bool + { + return $this->approved_at === null; + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php new file mode 100644 index 0000000..93655d9 --- /dev/null +++ b/app/Providers/AppServiceProvider.php @@ -0,0 +1,50 @@ +addDays(15)); + Passport::refreshTokensExpireIn(now()->addDays(30)); + Passport::personalAccessTokensExpireIn(now()->addMonths(6)); + + Passport::tokensCan([ + 'users:read' => 'Leer usuarios', + 'users:write' => 'Crear, editar y borrar usuarios', + 'account:read' => 'Leer tus clientes OAuth, tokens y apps autorizadas', + 'account:write' => 'Gestionar tus clientes OAuth, tokens y apps autorizadas', + ]); + + // Sin scope por defecto: los datos de usuarios son sensibles, el cliente + // tiene que pedir explícitamente el acceso que necesita. + + // La pantalla de consentimiento la renderiza la SPA Vue (ver Authorize.vue). + // Passport inyecta client/user/scopes/authToken; se los pasamos al shell Blade + // como JSON para que el JS los lea (ver resources/views/app.blade.php). + Passport::authorizationView('app'); + + Event::listen(AccessTokenCreated::class, RecordOAuthLoginEvent::class); + } +} diff --git a/app/Support/AnalyticsSummary.php b/app/Support/AnalyticsSummary.php new file mode 100644 index 0000000..395ec72 --- /dev/null +++ b/app/Support/AnalyticsSummary.php @@ -0,0 +1,103 @@ +subDays($days - 1)->startOfDay(); + + return [ + 'kpis' => [ + 'total_users' => User::count(), + 'active_sessions' => Token::query()->where('revoked', false)->where('expires_at', '>', now())->count(), + 'logins_today' => LoginEvent::whereDate('created_at', today())->count(), + 'logins_7d' => LoginEvent::where('created_at', '>=', now()->subDays(7))->count(), + ], + 'logins_by_day' => self::loginsByDay($since, $days), + 'logins_by_app' => self::loginsByApp($since), + 'top_users' => self::topUsers($since), + 'recent' => self::recent(), + ]; + } + + private static function loginsByDay(Carbon $since, int $days): array + { + $rows = LoginEvent::query() + ->selectRaw("date(created_at) as day, type, count(*) as count") + ->where('created_at', '>=', $since) + ->groupBy('day', 'type') + ->get() + ->groupBy('day'); + + $out = []; + for ($i = 0; $i < $days; $i++) { + $date = $since->copy()->addDays($i)->toDateString(); + $dayRows = $rows->get($date, collect()); + + $out[] = [ + 'date' => $date, + 'session' => (int) $dayRows->firstWhere('type', 'session')?->count, + 'oauth' => (int) $dayRows->firstWhere('type', 'oauth')?->count, + ]; + } + + return $out; + } + + private static function loginsByApp(Carbon $since): array + { + return LoginEvent::query() + ->join('oauth_clients', 'oauth_clients.id', '=', 'login_events.client_id') + ->where('login_events.type', 'oauth') + ->where('login_events.created_at', '>=', $since) + ->groupBy('oauth_clients.id', 'oauth_clients.name') + ->orderByDesc('count') + ->limit(8) + ->get(['oauth_clients.name as client_name', DB::raw('count(*) as count')]) + ->toArray(); + } + + private static function topUsers(Carbon $since): array + { + return LoginEvent::query() + ->join('users', 'users.id', '=', 'login_events.user_id') + ->where('login_events.created_at', '>=', $since) + ->groupBy('users.id', 'users.name', 'users.email') + ->orderByDesc('count') + ->limit(8) + ->get(['users.name', 'users.email', DB::raw('count(*) as count')]) + ->toArray(); + } + + private static function recent(int $limit = 15): array + { + return LoginEvent::with(['user:id,name,email', 'client:id,name']) + ->latest('created_at') + ->limit($limit) + ->get() + ->map(fn (LoginEvent $event) => [ + 'user_name' => $event->user?->name, + 'user_email' => $event->user?->email, + // null para logins de sesión (el frontend lo traduce como "Panel admin"/"Admin + // panel" según el idioma) y para apps borradas (el frontend traduce ese caso + // también) — así el backend no filtra texto sin traducir a la UI. + 'client_name' => $event->type === 'session' ? null : $event->client?->name, + 'type' => $event->type, + 'ip_address' => $event->ip_address, + 'created_at' => $event->created_at, + ]) + ->toArray(); + } +} diff --git a/artisan b/artisan new file mode 100755 index 0000000..c35e31d --- /dev/null +++ b/artisan @@ -0,0 +1,18 @@ +#!/usr/bin/env php +handleCommand(new ArgvInput); + +exit($status); diff --git a/bootstrap/app.php b/bootstrap/app.php new file mode 100644 index 0000000..b068688 --- /dev/null +++ b/bootstrap/app.php @@ -0,0 +1,23 @@ +withRouting( + web: __DIR__.'/../routes/web.php', + api: __DIR__.'/../routes/api.php', + commands: __DIR__.'/../routes/console.php', + health: '/up', + ) + ->withMiddleware(function (Middleware $middleware): void { + $middleware->web(append: [SetLocale::class]); + }) + ->withExceptions(function (Exceptions $exceptions): void { + $exceptions->shouldRenderJsonWhen( + fn (Request $request) => $request->is('api/*') || $request->is('web-api/*') || $request->expectsJson(), + ); + })->create(); diff --git a/bootstrap/cache/.gitignore b/bootstrap/cache/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/bootstrap/cache/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/bootstrap/providers.php b/bootstrap/providers.php new file mode 100644 index 0000000..fc94ae6 --- /dev/null +++ b/bootstrap/providers.php @@ -0,0 +1,7 @@ +=5.0.0" + }, + "require-dev": { + "doctrine/dbal": "^4.0.0", + "nesbot/carbon": "^2.71.0 || ^3.0.0", + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Carbon\\Doctrine\\": "src/Carbon/Doctrine/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "KyleKatarn", + "email": "kylekatarnls@gmail.com" + } + ], + "description": "Types to use Carbon in Doctrine", + "keywords": [ + "carbon", + "date", + "datetime", + "doctrine", + "time" + ], + "support": { + "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/kylekatarnls", + "type": "github" + }, + { + "url": "https://opencollective.com/Carbon", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/nesbot/carbon", + "type": "tidelift" + } + ], + "time": "2024-02-09T16:56:22+00:00" + }, + { + "name": "darkaonline/l5-swagger", + "version": "11.1.0", + "source": { + "type": "git", + "url": "https://github.com/DarkaOnLine/L5-Swagger.git", + "reference": "110b59478c9417c13794cef62a82b019433d642a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DarkaOnLine/L5-Swagger/zipball/110b59478c9417c13794cef62a82b019433d642a", + "reference": "110b59478c9417c13794cef62a82b019433d642a", + "shasum": "" + }, + "require": { + "ext-json": "*", + "laravel/framework": "^13.0 || ^12.1 || ^11.44", + "php": "^8.2", + "swagger-api/swagger-ui": ">=5.18.3", + "symfony/yaml": "^5.0 || ^6.0 || ^7.0 || ^8.0", + "zircote/swagger-php": "^6.0" + }, + "require-dev": { + "mockery/mockery": "1.*", + "orchestra/testbench": "^11.0 || ^10.0 || ^9.0 || ^8.0 || 7.* || ^6.15 || 5.*", + "php-coveralls/php-coveralls": "^2.0", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "L5Swagger": "L5Swagger\\L5SwaggerFacade" + }, + "providers": [ + "L5Swagger\\L5SwaggerServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "L5Swagger\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Darius Matulionis", + "email": "darius@matulionis.lt" + } + ], + "description": "OpenApi or Swagger integration to Laravel", + "keywords": [ + "api", + "documentation", + "laravel", + "openapi", + "specification", + "swagger", + "ui" + ], + "support": { + "issues": "https://github.com/DarkaOnLine/L5-Swagger/issues", + "source": "https://github.com/DarkaOnLine/L5-Swagger/tree/11.1.0" + }, + "funding": [ + { + "url": "https://github.com/DarkaOnLine", + "type": "github" + } + ], + "time": "2026-06-12T10:04:41+00:00" + }, + { + "name": "defuse/php-encryption", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/defuse/php-encryption.git", + "reference": "f53396c2d34225064647a05ca76c1da9d99e5828" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/defuse/php-encryption/zipball/f53396c2d34225064647a05ca76c1da9d99e5828", + "reference": "f53396c2d34225064647a05ca76c1da9d99e5828", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "paragonie/random_compat": ">= 2", + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "^5|^6|^7|^8|^9|^10", + "yoast/phpunit-polyfills": "^2.0.0" + }, + "bin": [ + "bin/generate-defuse-key" + ], + "type": "library", + "autoload": { + "psr-4": { + "Defuse\\Crypto\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Hornby", + "email": "taylor@defuse.ca", + "homepage": "https://defuse.ca/" + }, + { + "name": "Scott Arciszewski", + "email": "info@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "Secure PHP Encryption Library", + "keywords": [ + "aes", + "authenticated encryption", + "cipher", + "crypto", + "cryptography", + "encrypt", + "encryption", + "openssl", + "security", + "symmetric key cryptography" + ], + "support": { + "issues": "https://github.com/defuse/php-encryption/issues", + "source": "https://github.com/defuse/php-encryption/tree/v2.4.0" + }, + "time": "2023-06-19T06:10:36+00:00" + }, + { + "name": "dflydev/dot-access-data", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "reference": "a23a2bf4f31d3518f3ecb38660c95715dfead60f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.3" + }, + "time": "2024-07-08T12:26:09+00:00" + }, + { + "name": "doctrine/inflector", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/inflector.git", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "reference": "6d6c96277ea252fc1304627204c3d5e6e15faa3b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^12.0 || ^13.0", + "phpstan/phpstan": "^1.12 || ^2.0", + "phpstan/phpstan-phpunit": "^1.4 || ^2.0", + "phpstan/phpstan-strict-rules": "^1.6 || ^2.0", + "phpunit/phpunit": "^8.5 || ^12.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Inflector\\": "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" + } + ], + "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.1.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%2Finflector", + "type": "tidelift" + } + ], + "time": "2025-08-10T19:31:58+00:00" + }, + { + "name": "doctrine/lexer", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^5.21" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "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": "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/3.0.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%2Flexer", + "type": "tidelift" + } + ], + "time": "2024-02-05T11:56:58+00:00" + }, + { + "name": "dragonmantank/cron-expression", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/dragonmantank/cron-expression.git", + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/d61a8a9604ec1f8c3d150d09db6ce98b32675013", + "reference": "d61a8a9604ec1f8c3d150d09db6ce98b32675013", + "shasum": "" + }, + "require": { + "php": "^8.2|^8.3|^8.4|^8.5" + }, + "replace": { + "mtdowling/cron-expression": "^1.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.32|^2.1.31", + "phpunit/phpunit": "^8.5.48|^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "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.6.0" + }, + "funding": [ + { + "url": "https://github.com/dragonmantank", + "type": "github" + } + ], + "time": "2025-10-31T18:51:33+00:00" + }, + { + "name": "egulias/email-validator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "reference": "d42c8731f0624ad6bdc8d3e5e9a4524f68801cfa", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" + }, + "require-dev": { + "phpunit/phpunit": "^10.2", + "vimeo/psalm": "^5.12" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.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/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/egulias", + "type": "github" + } + ], + "time": "2025-03-06T22:45:56+00:00" + }, + { + "name": "firebase/php-jwt", + "version": "v7.1.0", + "source": { + "type": "git", + "url": "https://github.com/googleapis/php-jwt.git", + "reference": "b374a5d1a4f1f67fadc2165cdb284645945e2fc0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/googleapis/php-jwt/zipball/b374a5d1a4f1f67fadc2165cdb284645945e2fc0", + "reference": "b374a5d1a4f1f67fadc2165cdb284645945e2fc0", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.4", + "phpfastcache/phpfastcache": "^9.2", + "phpseclib/phpseclib": "~3.0", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^2.0||^3.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present", + "phpseclib/phpseclib": "Support PS256 (RSASSA-PSS) signatures" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/googleapis/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/googleapis/php-jwt/issues", + "source": "https://github.com/googleapis/php-jwt/tree/v7.1.0" + }, + "time": "2026-06-11T17:54:14+00:00" + }, + { + "name": "fruitcake/php-cors", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/fruitcake/php-cors.git", + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fruitcake/php-cors/zipball/38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", + "reference": "38aaa6c3fd4c157ffe2a4d10aa8b9b16ba8de379", + "shasum": "" + }, + "require": { + "php": "^8.1", + "symfony/http-foundation": "^5.4|^6.4|^7.3|^8" + }, + "require-dev": { + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-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.4.0" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2025-12-03T09:33:47+00:00" + }, + { + "name": "graham-campbell/result-type", + "version": "v1.1.4", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.5" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7" + }, + "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.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:43:20+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.15.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "61443dfb33c62f308ee8add20f45b4d6e4bf8d2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/61443dfb33c62f308ee8add20f45b4d6e4bf8d2f", + "reference": "61443dfb33c62f308ee8add20f45b4d6e4bf8d2f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^2.5.1", + "guzzlehttp/psr7": "^2.13", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.25" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.3", + "guzzlehttp/test-server": "^0.7", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.52 || ^9.6.34", + "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 + } + }, + "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.15.1" + }, + "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": "2026-07-18T11:23:11+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.5.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "9ad1e4fc607446a055b95870c7f668e93b5cff29" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/9ad1e4fc607446a055b95870c7f668e93b5cff29", + "reference": "9ad1e4fc607446a055b95870c7f668e93b5cff29", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.52 || ^9.6.34" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "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/2.5.1" + }, + "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": "2026-07-08T15:48:39+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.13.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "dad89620b7a6edb60c15858442eb2e408b45d8f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/dad89620b7a6edb60c15858442eb2e408b45d8f4", + "reference": "dad89620b7a6edb60c15858442eb2e408b45d8f4", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", + "symfony/polyfill-php80": "^1.25" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "1.1.0", + "jshttp/mime-db": "1.54.0.1", + "phpunit/phpunit": "^8.5.52 || ^9.6.34" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "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.13.0" + }, + "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": "2026-07-16T22:23:49+00:00" + }, + { + "name": "guzzlehttp/uri-template", + "version": "v1.0.10", + "source": { + "type": "git", + "url": "https://github.com/guzzle/uri-template.git", + "reference": "f6c24c21f42b990e9a58912b332d0874df6ba839" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/f6c24c21f42b990e9a58912b332d0874df6ba839", + "reference": "f6c24c21f42b990e9a58912b332d0874df6ba839", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-php80": "^1.25" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.52 || ^9.6.34", + "uri-template/tests": "1.0.0" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\UriTemplate\\": "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" + } + ], + "description": "A polyfill class for uri_template of PHP", + "keywords": [ + "guzzlehttp", + "uri-template" + ], + "support": { + "issues": "https://github.com/guzzle/uri-template/issues", + "source": "https://github.com/guzzle/uri-template/tree/v1.0.10" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/uri-template", + "type": "tidelift" + } + ], + "time": "2026-07-17T13:53:03+00:00" + }, + { + "name": "laravel/framework", + "version": "v13.22.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "d354afa45334cdac523fbc015f203a78d7e2f5c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/d354afa45334cdac523fbc015f203a78d7e2f5c6", + "reference": "d354afa45334cdac523fbc015f203a78d7e2f5c6", + "shasum": "" + }, + "require": { + "brick/math": "^0.14.2 || ^0.15 || ^0.16 || ^0.17 || ^0.18", + "composer-runtime-api": "^2.2", + "doctrine/inflector": "^2.0.5", + "dragonmantank/cron-expression": "^3.4", + "egulias/email-validator": "^4.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-session": "*", + "ext-tokenizer": "*", + "fruitcake/php-cors": "^1.3", + "guzzlehttp/guzzle": "^7.8.2", + "guzzlehttp/promises": "^2.0.3", + "guzzlehttp/uri-template": "^1.0", + "laravel/prompts": "^0.3.0", + "laravel/serializable-closure": "^2.0.10", + "league/commonmark": "^2.8.1", + "league/flysystem": "^3.25.1", + "league/flysystem-local": "^3.25.1", + "league/uri": "^7.5.1", + "monolog/monolog": "^3.0", + "nesbot/carbon": "^3.8.4", + "nunomaduro/termwind": "^2.0", + "php": "^8.3", + "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.7", + "symfony/console": "^7.4.0 || ^8.0.0", + "symfony/error-handler": "^7.4.0 || ^8.0.0", + "symfony/finder": "^7.4.0 || ^8.0.0", + "symfony/http-foundation": "^7.4.0 || ^8.0.0", + "symfony/http-kernel": "^7.4.0 || ^8.0.0", + "symfony/mailer": "^7.4.0 || ^8.0.0", + "symfony/mime": "^7.4.0 || ^8.0.0", + "symfony/polyfill-php84": "^1.36", + "symfony/polyfill-php85": "^1.36", + "symfony/polyfill-php86": "^1.36", + "symfony/process": "^7.4.5 || ^8.0.5", + "symfony/routing": "^7.4.0 || ^8.0.0", + "symfony/uid": "^7.4.0 || ^8.0.0", + "symfony/var-dumper": "^7.4.0 || ^8.0.0", + "tijsverkoyen/css-to-inline-styles": "^2.2.5", + "vlucas/phpdotenv": "^5.6.1", + "voku/portable-ascii": "^2.0.2" + }, + "conflict": { + "tightenco/collect": "<5.5.33" + }, + "provide": { + "psr/container-implementation": "1.1 || 2.0", + "psr/log-implementation": "1.0 || 2.0 || 3.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/concurrency": "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/image": "self.version", + "illuminate/json-schema": "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/process": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/reflection": "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", + "spatie/once": "*" + }, + "require-dev": { + "ably/ably-php": "^1.0", + "aws/aws-sdk-php": "^3.322.9", + "ext-gmp": "*", + "fakerphp/faker": "^1.24", + "guzzlehttp/psr7": "^2.9", + "intervention/image": "^4.0", + "laravel/pint": "^1.18", + "league/flysystem-aws-s3-v3": "^3.25.1", + "league/flysystem-ftp": "^3.25.1", + "league/flysystem-path-prefixing": "^3.25.1", + "league/flysystem-read-only": "^3.25.1", + "league/flysystem-sftp-v3": "^3.25.1", + "mockery/mockery": "^1.6.10", + "opis/json-schema": "^2.4.1", + "orchestra/testbench-core": "^11.0.0", + "pda/pheanstalk": "^7.0.0 || ^8.0.0", + "php-http/discovery": "^1.15", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^11.5.50 || ^12.5.8 || ^13.0.3", + "predis/predis": "^2.3 || ^3.0", + "rector/rector": "^2.3", + "resend/resend-php": "^1.0", + "symfony/cache": "^7.4.0 || ^8.0.0", + "symfony/http-client": "^7.4.0 || ^8.0.0", + "symfony/psr-http-message-bridge": "^7.4.0 || ^8.0.0", + "symfony/translation": "^7.4.0 || ^8.0.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.322.9).", + "brianium/paratest": "Required to run tests in parallel (^7.0 || ^8.0).", + "ext-apcu": "Required to use the APC cache driver.", + "ext-fileinfo": "Required to use the Filesystem class.", + "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 and console signal trapping.", + "ext-pdo": "Required to use all database features.", + "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 || ^6.0).", + "fakerphp/faker": "Required to generate fake data using the fake() helper (^1.23).", + "filp/whoops": "Required for friendly error pages in development (^2.14.3).", + "intervention/image": "Required to use the image processing features (^4.0).", + "laravel/tinker": "Required to use the tinker console command (^2.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", + "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).", + "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.25.1).", + "league/flysystem-read-only": "Required to use read-only disks (^3.25.1)", + "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).", + "mockery/mockery": "Required to use mocking (^1.6).", + "pda/pheanstalk": "Required to use the beanstalk queue driver (^7.0 || ^8.0).", + "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", + "phpunit/phpunit": "Required to use assertions and run tests (^11.5.50 || ^12.5.8 || ^13.0.3).", + "predis/predis": "Required to use the predis connector (^2.3 || ^3.0).", + "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).", + "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0 || ^1.0).", + "spatie/fork": "Required to use the 'fork' concurrency driver (^1.2).", + "symfony/cache": "Required to PSR-6 cache bridge (^7.4 || ^8.0).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.4 || ^8.0).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.4 || ^8.0).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.4 || ^8.0).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.4 || ^8.0).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.4 || ^8.0)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "13.0.x-dev" + } + }, + "autoload": { + "files": [ + "src/Illuminate/Collections/functions.php", + "src/Illuminate/Collections/helpers.php", + "src/Illuminate/Events/functions.php", + "src/Illuminate/Filesystem/functions.php", + "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Log/functions.php", + "src/Illuminate/Reflection/helpers.php", + "src/Illuminate/Support/functions.php", + "src/Illuminate/Support/helpers.php" + ], + "psr-4": { + "Illuminate\\": "src/Illuminate/", + "Illuminate\\Support\\": [ + "src/Illuminate/Macroable/", + "src/Illuminate/Collections/", + "src/Illuminate/Conditionable/", + "src/Illuminate/Reflection/" + ] + } + }, + "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": "2026-07-24T20:38:48+00:00" + }, + { + "name": "laravel/passport", + "version": "v13.7.5", + "source": { + "type": "git", + "url": "https://github.com/laravel/passport.git", + "reference": "90053dc4ba681c076855779250109bb624f961f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/passport/zipball/90053dc4ba681c076855779250109bb624f961f6", + "reference": "90053dc4ba681c076855779250109bb624f961f6", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-openssl": "*", + "firebase/php-jwt": "^6.4|^7.0", + "illuminate/auth": "^11.35|^12.0|^13.0", + "illuminate/console": "^11.35|^12.0|^13.0", + "illuminate/container": "^11.35|^12.0|^13.0", + "illuminate/contracts": "^11.35|^12.0|^13.0", + "illuminate/cookie": "^11.35|^12.0|^13.0", + "illuminate/database": "^11.35|^12.0|^13.0", + "illuminate/encryption": "^11.35|^12.0|^13.0", + "illuminate/http": "^11.35|^12.0|^13.0", + "illuminate/support": "^11.35|^12.0|^13.0", + "league/oauth2-server": "^9.2", + "php": "^8.2", + "php-http/discovery": "^1.20", + "phpseclib/phpseclib": "^3.0", + "psr/http-factory-implementation": "*", + "symfony/console": "^7.1|^8.0", + "symfony/psr-http-message-bridge": "^7.1|^8.0" + }, + "require-dev": { + "orchestra/testbench": "^9.15|^10.8|^11.0", + "phpstan/phpstan": "^2.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Passport\\PassportServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Passport\\": "src/", + "Laravel\\Passport\\Database\\Factories\\": "database/factories/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Passport provides OAuth2 server support to Laravel.", + "keywords": [ + "laravel", + "oauth", + "passport" + ], + "support": { + "issues": "https://github.com/laravel/passport/issues", + "source": "https://github.com/laravel/passport" + }, + "time": "2026-04-16T14:00:29+00:00" + }, + { + "name": "laravel/prompts", + "version": "v0.3.21", + "source": { + "type": "git", + "url": "https://github.com/laravel/prompts.git", + "reference": "7753c65c281c2550c7c183f14e18062073b7d821" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/prompts/zipball/7753c65c281c2550c7c183f14e18062073b7d821", + "reference": "7753c65c281c2550c7c183f14e18062073b7d821", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.2", + "ext-mbstring": "*", + "php": "^8.1", + "symfony/console": "^6.2|^7.0|^8.0" + }, + "conflict": { + "illuminate/console": ">=10.17.0 <10.25.0", + "laravel/framework": ">=10.17.0 <10.25.0" + }, + "require-dev": { + "illuminate/collections": "^10.0|^11.0|^12.0|^13.0", + "mockery/mockery": "^1.5", + "pestphp/pest": "^2.3|^3.4|^4.0", + "phpstan/phpstan": "^1.12.28", + "phpstan/phpstan-mockery": "^1.1.3" + }, + "suggest": { + "ext-pcntl": "Required for the spinner to be animated." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "0.3.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Laravel\\Prompts\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Add beautiful and user-friendly forms to your command-line applications.", + "support": { + "issues": "https://github.com/laravel/prompts/issues", + "source": "https://github.com/laravel/prompts/tree/v0.3.21" + }, + "time": "2026-06-26T00:11:25+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v2.0.15", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "dccd8bcb851bb03fcc005df650b708b57cc52661" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/dccd8bcb851bb03fcc005df650b708b57cc52661", + "reference": "dccd8bcb851bb03fcc005df650b708b57cc52661", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "illuminate/support": "^10.0|^11.0|^12.0|^13.0", + "nesbot/carbon": "^2.67|^3.0", + "pestphp/pest": "^2.36|^3.0|^4.0", + "phpstan/phpstan": "^2.0", + "symfony/var-dumper": "^6.2.0|^7.0.0|^8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.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": "2026-07-21T16:49:22+00:00" + }, + { + "name": "laravel/tinker", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/tinker.git", + "reference": "4faba77764bd33411735936acdf30446d058c78b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/tinker/zipball/4faba77764bd33411735936acdf30446d058c78b", + "reference": "4faba77764bd33411735936acdf30446d058c78b", + "shasum": "" + }, + "require": { + "illuminate/console": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", + "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", + "illuminate/support": "^8.0|^9.0|^10.0|^11.0|^12.0|^13.0", + "php": "^8.1", + "psy/psysh": "^0.12.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0|^8.0" + }, + "require-dev": { + "mockery/mockery": "~1.3.3|^1.4.2", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5|^11.5" + }, + "suggest": { + "illuminate/database": "The Illuminate Database package (^8.0|^9.0|^10.0|^11.0|^12.0|^13.0)." + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Tinker\\TinkerServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "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/v3.0.2" + }, + "time": "2026-03-17T14:54:13+00:00" + }, + { + "name": "lcobucci/jwt", + "version": "5.6.0", + "source": { + "type": "git", + "url": "https://github.com/lcobucci/jwt.git", + "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/bb3e9f21e4196e8afc41def81ef649c164bca25e", + "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-sodium": "*", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/clock": "^1.0" + }, + "require-dev": { + "infection/infection": "^0.29", + "lcobucci/clock": "^3.2", + "lcobucci/coding-standard": "^11.0", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.10.7", + "phpstan/phpstan-deprecation-rules": "^1.1.3", + "phpstan/phpstan-phpunit": "^1.3.10", + "phpstan/phpstan-strict-rules": "^1.5.0", + "phpunit/phpunit": "^11.1" + }, + "suggest": { + "lcobucci/clock": ">= 3.2" + }, + "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/5.6.0" + }, + "funding": [ + { + "url": "https://github.com/lcobucci", + "type": "github" + }, + { + "url": "https://www.patreon.com/lcobucci", + "type": "patreon" + } + ], + "time": "2025-10-17T11:30:53+00:00" + }, + { + "name": "league/commonmark", + "version": "2.8.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "1902f60f984235023acbe03db6ad614a37b3c3e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/1902f60f984235023acbe03db6ad614a37b3c3e7", + "reference": "1902f60f984235023acbe03db6ad614a37b3c3e7", + "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.31.1", + "commonmark/commonmark.js": "0.31.1", + "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": "^2.0.0", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0 || ^12.0.0 || ^13.0.0", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0 || ^8.0", + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.9-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": "2026-07-12T15:29:16+00:00" + }, + { + "name": "league/config", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.8.2", + "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": "2022-12-11T20:36:23+00:00" + }, + { + "name": "league/event", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/event.git", + "reference": "ec38ff7ea10cad7d99a79ac937fbcffb9334c210" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/event/zipball/ec38ff7ea10cad7d99a79ac937fbcffb9334c210", + "reference": "ec38ff7ea10cad7d99a79ac937fbcffb9334c210", + "shasum": "" + }, + "require": { + "php": ">=7.2.0", + "psr/event-dispatcher": "^1.0" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "phpstan/phpstan": "^0.12.45", + "phpunit/phpunit": "^8.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Event\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Event package", + "keywords": [ + "emitter", + "event", + "listener" + ], + "support": { + "issues": "https://github.com/thephpleague/event/issues", + "source": "https://github.com/thephpleague/event/tree/3.0.3" + }, + "time": "2024-09-04T16:06:53+00:00" + }, + { + "name": "league/flysystem", + "version": "3.35.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "b277b5dc3d56650b68904117124e79c851e12376" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/b277b5dc3d56650b68904117124e79c851e12376", + "reference": "b277b5dc3d56650b68904117124e79c851e12376", + "shasum": "" + }, + "require": { + "league/flysystem-local": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "async-aws/core": "<1.19.0", + "async-aws/s3": "<1.14.0", + "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 || ^2.0", + "async-aws/simple-s3": "^1.1 || ^2.0", + "aws/aws-sdk-php": "^3.295.10", + "composer/semver": "^3.0", + "ext-fileinfo": "*", + "ext-ftp": "*", + "ext-mongodb": "^1.3|^2", + "ext-zip": "*", + "friendsofphp/php-cs-fixer": "^3.5", + "google/cloud-storage": "^1.23", + "guzzlehttp/psr7": "^2.6", + "microsoft/azure-storage-blob": "^1.1", + "mongodb/mongodb": "^1.2|^2", + "phpseclib/phpseclib": "^3.0.36", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9.5.11|^10.0", + "sabre/dav": "^4.6.0" + }, + "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.35.2" + }, + "time": "2026-07-06T14:42:07+00:00" + }, + { + "name": "league/flysystem-local", + "version": "3.31.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-local.git", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "reference": "2f669db18a4c20c755c2bb7d3a7b0b2340488079", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "league/flysystem": "^3.0.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Local\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Local filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "file", + "files", + "filesystem", + "local" + ], + "support": { + "source": "https://github.com/thephpleague/flysystem-local/tree/3.31.0" + }, + "time": "2026-01-23T15:30:45+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.17.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/f5f47eff7c48ed1003069a2ca67f316fb4021c76", + "reference": "f5f47eff7c48ed1003069a2ca67f316fb4021c76", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0 || ^11.0 || ^12.0" + }, + "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.17.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2026-07-09T11:49:27+00:00" + }, + { + "name": "league/oauth2-server", + "version": "9.4.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/oauth2-server.git", + "reference": "9d2f6fc0a0b5aa1bb02506971d3a4ecff2c6526c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/9d2f6fc0a0b5aa1bb02506971d3a4ecff2c6526c", + "reference": "9d2f6fc0a0b5aa1bb02506971d3a4ecff2c6526c", + "shasum": "" + }, + "require": { + "defuse/php-encryption": "^2.4", + "ext-json": "*", + "ext-openssl": "*", + "lcobucci/jwt": "^5.6", + "league/event": "^3.0", + "league/uri": "^7.8", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "psr/clock": "^1.0", + "psr/http-message": "^2.0", + "psr/http-server-middleware": "^1.0" + }, + "replace": { + "league/oauth2server": "*", + "lncd/oauth2": "*" + }, + "require-dev": { + "laminas/laminas-diactoros": "^3.8", + "paragonie/random_compat": "^9.99.100", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.38", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0.12", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^11.5.50", + "roave/security-advisories": "dev-master", + "slevomat/coding-standard": "^8.27.1", + "squizlabs/php_codesniffer": "^4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\OAuth2\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alex Bilbie", + "email": "hello@alexbilbie.com", + "homepage": "http://www.alexbilbie.com", + "role": "Developer" + }, + { + "name": "Andy Millington", + "email": "andrew@noexceptions.io", + "homepage": "https://www.noexceptions.io", + "role": "Developer" + } + ], + "description": "A lightweight and powerful OAuth 2.0 authorization and resource server library with support for all the core specification grants. This library will allow you to secure your API with OAuth and allow your applications users to approve apps that want to access their data from your API.", + "homepage": "https://oauth2.thephpleague.com/", + "keywords": [ + "Authentication", + "api", + "auth", + "authorisation", + "authorization", + "oauth", + "oauth 2", + "oauth 2.0", + "oauth2", + "protect", + "resource", + "secure", + "server" + ], + "support": { + "issues": "https://github.com/thephpleague/oauth2-server/issues", + "source": "https://github.com/thephpleague/oauth2-server/tree/9.4.1" + }, + "funding": [ + { + "url": "https://github.com/sephster", + "type": "github" + } + ], + "time": "2026-06-25T15:24:07+00:00" + }, + { + "name": "league/uri", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri.git", + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/08cf38e3924d4f56238125547b5720496fac8fd4", + "reference": "08cf38e3924d4f56238125547b5720496fac8fd4", + "shasum": "" + }, + "require": { + "league/uri-interfaces": "^7.8.1", + "php": "^8.1", + "psr/http-factory": "^1" + }, + "conflict": { + "league/uri-schemes": "^1.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-dom": "to convert the URI into an HTML anchor tag", + "ext-fileinfo": "to create Data URI from file contennts", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "ext-uri": "to use the PHP native URI class", + "jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain", + "league/uri-components": "to provide additional tools to manipulate URI objects components", + "league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI manipulation library", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "URN", + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc2141", + "rfc3986", + "rfc3987", + "rfc6570", + "rfc8141", + "uri", + "uri-template", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2026-03-15T20:22:25+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/85d5c77c5d6d3af6c54db4a78246364908f3c928", + "reference": "85d5c77c5d6d3af6c54db4a78246364908f3c928", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^8.1", + "psr/http-message": "^1.1 || ^2.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "php-64bit": "to improve IPV4 host parsing", + "rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "Common tools for parsing and resolving RFC3987/RFC3986 URI", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2026-03-08T20:05:35+00:00" + }, + { + "name": "monolog/monolog", + "version": "3.10.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/b321dd6749f0bf7189444158a3ce785cc16d69b0", + "reference": "b321dd6749f0bf7189444158a3ce785cc16d69b0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8 || ^2.0", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", + "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": "3.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/3.10.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2026-01-02T08:56:05+00:00" + }, + { + "name": "nesbot/carbon", + "version": "3.13.1", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "2937ad3d1d2c506fd2bc97d571438a95641f44e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/2937ad3d1d2c506fd2bc97d571438a95641f44e2", + "reference": "2937ad3d1d2c506fd2bc97d571438a95641f44e2", + "shasum": "" + }, + "require": { + "carbonphp/carbon-doctrine-types": "<100.0", + "ext-json": "*", + "php": "^8.1", + "psr/clock": "^1.0", + "symfony/clock": "^6.3.12 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation": "^4.4.18 || ^5.2.1 || ^6.0 || ^7.0 || ^8.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "require-dev": { + "doctrine/dbal": "^3.6.3 || ^4.0", + "doctrine/orm": "^2.15.2 || ^3.0", + "friendsofphp/php-cs-fixer": "^v3.87.1", + "kylekatarnls/multi-tester": "^2.5.3", + "phpmd/phpmd": "^2.15.0", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.22", + "phpunit/phpunit": "^10.5.53", + "squizlabs/php_codesniffer": "^3.13.4 || ^4.0.0" + }, + "bin": [ + "bin/carbon" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Carbon\\Laravel\\ServiceProvider" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" + } + }, + "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://carbonphp.github.io/carbon/", + "keywords": [ + "date", + "datetime", + "time" + ], + "support": { + "docs": "https://carbonphp.github.io/carbon/guide/getting-started/introduction.html", + "issues": "https://github.com/CarbonPHP/carbon/issues", + "source": "https://github.com/CarbonPHP/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": "2026-07-09T18:23:49+00:00" + }, + { + "name": "nette/schema", + "version": "v1.3.5", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002", + "reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002", + "shasum": "" + }, + "require": { + "nette/utils": "^4.0", + "php": "8.1 - 8.5" + }, + "require-dev": { + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.6", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1.39@stable", + "tracy/tracy": "^2.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "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.3.5" + }, + "time": "2026-02-23T03:47:12+00:00" + }, + { + "name": "nette/utils", + "version": "v4.1.5", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "b043439dbdf954e6c28b5ea7e34b0100f83165e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/b043439dbdf954e6c28b5ea7e34b0100f83165e0", + "reference": "b043439dbdf954e6c28b5ea7e34b0100f83165e0", + "shasum": "" + }, + "require": { + "php": "8.2 - 8.5" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "^1.2", + "nette/phpstan-rules": "^1.0", + "nette/tester": "^2.5", + "phpstan/extension-installer": "^1.4@stable", + "phpstan/phpstan": "^2.1@stable", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::chr(), ord() 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()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Nette\\": "src" + }, + "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/v4.1.5" + }, + "time": "2026-07-17T23:02:45+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.8.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-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/v5.8.0" + }, + "time": "2026-07-04T14:30:18+00:00" + }, + { + "name": "nunomaduro/termwind", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/termwind.git", + "reference": "712a31b768f5daea284c2169a7d227031001b9a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/712a31b768f5daea284c2169a7d227031001b9a8", + "reference": "712a31b768f5daea284c2169a7d227031001b9a8", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^8.2", + "symfony/console": "^7.4.4 || ^8.0.4" + }, + "require-dev": { + "illuminate/console": "^11.47.0", + "laravel/pint": "^1.27.1", + "mockery/mockery": "^1.6.12", + "pestphp/pest": "^2.36.0 || ^3.8.4 || ^4.3.2", + "phpstan/phpstan": "^1.12.32", + "phpstan/phpstan-strict-rules": "^1.6.2", + "symfony/var-dumper": "^7.3.5 || ^8.0.4", + "thecodingmachine/phpstan-strict-rules": "^1.0.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Termwind\\Laravel\\TermwindServiceProvider" + ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "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": "It's 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/v2.4.0" + }, + "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": "2026-02-16T23:10:27+00:00" + }, + { + "name": "paragonie/constant_time_encoding", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", + "reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77", + "shasum": "" + }, + "require": { + "php": "^8" + }, + "require-dev": { + "infection/infection": "^0", + "nikic/php-fuzzer": "^0", + "phpunit/phpunit": "^9|^10|^11", + "vimeo/psalm": "^4|^5|^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "time": "2025-09-24T15:06:41+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, + { + "name": "php-http/discovery", + "version": "1.20.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/discovery.git", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d", + "reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0|^2.0", + "php": "^7.1 || ^8.0" + }, + "conflict": { + "nyholm/psr7": "<1.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "*", + "psr/http-factory-implementation": "*", + "psr/http-message-implementation": "*" + }, + "require-dev": { + "composer/composer": "^1.0.2|^2.0", + "graham-campbell/phpspec-skip-example-extension": "^5.0", + "php-http/httplug": "^1.0 || ^2.0", + "php-http/message-factory": "^1.0", + "phpspec/phpspec": "^5.1 || ^6.1 || ^7.3", + "sebastian/comparator": "^3.0.5 || ^4.0.8", + "symfony/phpunit-bridge": "^6.4.4 || ^7.0.1" + }, + "type": "composer-plugin", + "extra": { + "class": "Http\\Discovery\\Composer\\Plugin", + "plugin-optional": true + }, + "autoload": { + "psr-4": { + "Http\\Discovery\\": "src/" + }, + "exclude-from-classmap": [ + "src/Composer/Plugin.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations", + "homepage": "http://php-http.org", + "keywords": [ + "adapter", + "client", + "discovery", + "factory", + "http", + "message", + "psr17", + "psr7" + ], + "support": { + "issues": "https://github.com/php-http/discovery/issues", + "source": "https://github.com/php-http/discovery/tree/1.20.0" + }, + "time": "2024-10-02T11:20:13+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.5", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "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.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:41:33+00:00" + }, + { + "name": "phpseclib/phpseclib", + "version": "3.0.55", + "source": { + "type": "git", + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "db9744e6d47e742b1f974e965ad49bdd041105af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/db9744e6d47e742b1f974e965ad49bdd041105af", + "reference": "db9744e6d47e742b1f974e965ad49bdd041105af", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "^1|^2|^3", + "paragonie/random_compat": "^1.4|^2.0|^9.99.99", + "php": ">=5.6.1" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "suggest": { + "ext-dom": "Install the DOM extension to load XML formatted public keys.", + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." + }, + "type": "library", + "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], + "psr-4": { + "phpseclib3\\": "phpseclib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", + "keywords": [ + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" + ], + "support": { + "issues": "https://github.com/phpseclib/phpseclib/issues", + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.55" + }, + "funding": [ + { + "url": "https://github.com/terrafrost", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpseclib", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", + "type": "tidelift" + } + ], + "time": "2026-06-14T23:24:10+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "fb19eedd2bb67ff8cf7a5502ad329e701d6398a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fb19eedd2bb67ff8cf7a5502ad329e701d6398a3", + "reference": "fb19eedd2bb67ff8cf7a5502ad329e701d6398a3", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^5.3.0", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.3.3" + }, + "time": "2026-07-08T07:01:06+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.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.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": "https://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" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.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": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: 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" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://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/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/http-server-handler", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-handler.git", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4", + "reference": "84c4fb66179be4caaf8e97bd239203245302e7d4", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side request handler", + "keywords": [ + "handler", + "http", + "http-interop", + "psr", + "psr-15", + "psr-7", + "request", + "response", + "server" + ], + "support": { + "source": "https://github.com/php-fig/http-server-handler/tree/1.0.2" + }, + "time": "2023-04-10T20:06:20+00:00" + }, + { + "name": "psr/http-server-middleware", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-server-middleware.git", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829", + "shasum": "" + }, + "require": { + "php": ">=7.0", + "psr/http-message": "^1.0 || ^2.0", + "psr/http-server-handler": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP server-side middleware", + "keywords": [ + "http", + "http-interop", + "middleware", + "psr", + "psr-15", + "psr-7", + "request", + "response" + ], + "support": { + "issues": "https://github.com/php-fig/http-server-middleware/issues", + "source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2" + }, + "time": "2023-04-11T06:14:47+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "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.2" + }, + "time": "2024-09-11T13:17:53+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.12.24", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1", + "reference": "ca0fdcf8a7617afa3adfdf1b5fef573dffb69ca1", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "nikic/php-parser": "^5.0 || ^4.0", + "php": "^8.0 || ^7.4", + "symfony/console": "^8.0 || ^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^8.0 || ^7.0 || ^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", + "composer/class-map-generator": "^1.6" + }, + "suggest": { + "composer/class-map-generator": "Improved tab completion performance with better class discovery.", + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well." + }, + "bin": [ + "bin/psysh" + ], + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": false, + "forward-command": false + }, + "branch-alias": { + "dev-main": "0.12.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" + } + ], + "description": "An interactive shell for modern PHP.", + "homepage": "https://psysh.org", + "keywords": [ + "REPL", + "console", + "interactive", + "shell" + ], + "support": { + "issues": "https://github.com/bobthecow/psysh/issues", + "source": "https://github.com/bobthecow/psysh/tree/v0.12.24" + }, + "time": "2026-06-29T15:41:09+00:00" + }, + { + "name": "radebatz/type-info-extras", + "version": "1.0.7", + "source": { + "type": "git", + "url": "https://github.com/DerManoMann/type-info-extras.git", + "reference": "95a524a74a61648b44e355cb33d38db4b17ef5ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DerManoMann/type-info-extras/zipball/95a524a74a61648b44e355cb33d38db4b17ef5ce", + "reference": "95a524a74a61648b44e355cb33d38db4b17ef5ce", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "phpstan/phpdoc-parser": "^2.0", + "symfony/type-info": "^7.3.8 || ^7.4.1 || ^8.0 || ^8.1-@dev" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.70", + "phpstan/phpstan": "^2.1", + "phpunit/phpunit": "^11.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Radebatz\\TypeInfoExtras\\": "src" + }, + "exclude-from-classmap": [ + "/tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Martin Rademacher", + "email": "mano@radebatz.org" + } + ], + "description": "Extras for symfony/type-info", + "homepage": "http://radebatz.net/mano/", + "keywords": [ + "component", + "symfony", + "type-info", + "types" + ], + "support": { + "issues": "https://github.com/DerManoMann/type-info-extras/issues", + "source": "https://github.com/DerManoMann/type-info-extras/tree/1.0.7" + }, + "time": "2026-03-06T22:40:29+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": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "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/2.1.1" + }, + "time": "2025-03-22T05:38:12+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.9.3", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/1df15849d00943a67d677dc9cfd80795f038c9f8", + "reference": "1df15849d00943a67d677dc9cfd80795f038c9f8", + "shasum": "" + }, + "require": { + "brick/math": ">=0.8.16 <=0.18", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.25", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" + }, + "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.9.3" + }, + "time": "2026-06-18T03:57:49+00:00" + }, + { + "name": "spatie/laravel-package-tools", + "version": "1.93.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-package-tools.git", + "reference": "d5552849801f2642aea710557463234b59ef65eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/d5552849801f2642aea710557463234b59ef65eb", + "reference": "d5552849801f2642aea710557463234b59ef65eb", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^10.0|^11.0|^12.0|^13.0", + "php": "^8.1" + }, + "require-dev": { + "mockery/mockery": "^1.5", + "orchestra/testbench": "^8.0|^9.2|^10.0|^11.0", + "pestphp/pest": "^2.1|^3.1|^4.0", + "phpunit/php-code-coverage": "^10.0|^11.0|^12.0", + "phpunit/phpunit": "^10.5|^11.5|^12.5", + "spatie/pest-plugin-test-time": "^2.2|^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\LaravelPackageTools\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" + } + ], + "description": "Tools for creating Laravel packages", + "homepage": "https://github.com/spatie/laravel-package-tools", + "keywords": [ + "laravel-package-tools", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-package-tools/issues", + "source": "https://github.com/spatie/laravel-package-tools/tree/1.93.1" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-05-19T14:06:37+00:00" + }, + { + "name": "spatie/laravel-permission", + "version": "8.3.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-permission.git", + "reference": "60e8ed5b2fbf043c2264433fc2680c76b8b66aa6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/60e8ed5b2fbf043c2264433fc2680c76b8b66aa6", + "reference": "60e8ed5b2fbf043c2264433fc2680c76b8b66aa6", + "shasum": "" + }, + "require": { + "illuminate/auth": "^12.0|^13.0", + "illuminate/container": "^12.0|^13.0", + "illuminate/contracts": "^12.0|^13.0", + "illuminate/database": "^12.0|^13.0", + "php": "^8.3", + "spatie/laravel-package-tools": "^1.0" + }, + "require-dev": { + "larastan/larastan": "^3.9", + "laravel/octane": "^2.0", + "laravel/passport": "^13.0", + "laravel/pint": "^1.0", + "orchestra/testbench": "^10.0|^11.0", + "pestphp/pest": "^3.0|^4.0", + "pestphp/pest-plugin-laravel": "^3.0|^4.1", + "phpstan/phpstan": "^2.1" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Permission\\PermissionServiceProvider" + ] + }, + "branch-alias": { + "dev-main": "8.x-dev", + "dev-master": "8.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Permission\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Permission handling for Laravel 12 and up", + "homepage": "https://github.com/spatie/laravel-permission", + "keywords": [ + "acl", + "laravel", + "permission", + "permissions", + "rbac", + "roles", + "security", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-permission/issues", + "source": "https://github.com/spatie/laravel-permission/tree/8.3.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2026-07-03T15:36:01+00:00" + }, + { + "name": "swagger-api/swagger-ui", + "version": "v5.32.11", + "source": { + "type": "git", + "url": "https://github.com/swagger-api/swagger-ui.git", + "reference": "414a60c9a40408d37821297682b8a190a840a79b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swagger-api/swagger-ui/zipball/414a60c9a40408d37821297682b8a190a840a79b", + "reference": "414a60c9a40408d37821297682b8a190a840a79b", + "shasum": "" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Anna Bodnia", + "email": "anna.bodnia@gmail.com" + }, + { + "name": "Buu Nguyen", + "email": "buunguyen@gmail.com" + }, + { + "name": "Josh Ponelat", + "email": "jponelat@gmail.com" + }, + { + "name": "Kyle Shockey", + "email": "kyleshockey1@gmail.com" + }, + { + "name": "Robert Barnwell", + "email": "robert@robertismy.name" + }, + { + "name": "Sahar Jafari", + "email": "shr.jafari@gmail.com" + } + ], + "description": " Swagger UI is a collection of HTML, Javascript, and CSS assets that dynamically generate beautiful documentation from a Swagger-compliant API.", + "homepage": "http://swagger.io", + "keywords": [ + "api", + "documentation", + "openapi", + "specification", + "swagger", + "ui" + ], + "support": { + "issues": "https://github.com/swagger-api/swagger-ui/issues", + "source": "https://github.com/swagger-api/swagger-ui/tree/v5.32.11" + }, + "time": "2026-07-22T07:31:41+00:00" + }, + { + "name": "symfony/clock", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "701ef4de9705d6c32292ebee5e8044094a09fbf6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/701ef4de9705d6c32292ebee5e8044094a09fbf6", + "reference": "701ef4de9705d6c32292ebee5e8044094a09fbf6", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "psr/clock": "^1.0" + }, + "provide": { + "psr/clock-implementation": "1.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/now.php" + ], + "psr-4": { + "Symfony\\Component\\Clock\\": "" + }, + "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": "Decouples applications from the system clock", + "homepage": "https://symfony.com", + "keywords": [ + "clock", + "psr20", + "time" + ], + "support": { + "source": "https://github.com/symfony/clock/tree/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/console", + "version": "v8.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "b711a8ab808b6c074c6b8caef70d0fd8d6b6d07d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/b711a8ab808b6c074c6b8caef70d0fd8d6b6d07d", + "reference": "b711a8ab808b6c074c6b8caef70d0fd8d6b6d07d", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "^1.0", + "symfony/polyfill-php85": "^1.32", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.4.6|^8.0.6" + }, + "conflict": { + "symfony/dependency-injection": "<8.1", + "symfony/event-dispatcher": "<8.1" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^8.1", + "symfony/event-dispatcher": "^8.1", + "symfony/filesystem": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/lock": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^7.4|^8.0" + }, + "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/v8.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-16T12:55:20+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "dc0e2be45c9b5588c82414f02ac574b4b986abcd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/dc0e2be45c9b5588c82414f02ac574b4b986abcd", + "reference": "dc0e2be45c9b5588c82414f02ac574b4b986abcd", + "shasum": "" + }, + "require": { + "php": ">=8.4.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/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/f3202fa1b5097b0af062dc978b32ecf63404e31d", + "reference": "f3202fa1b5097b0af062dc978b32ecf63404e31d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "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.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "d8aeb1abd3fef84795567850d3a567bdb5945ee5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/d8aeb1abd3fef84795567850d3a567bdb5945ee5", + "reference": "d8aeb1abd3fef84795567850d3a567bdb5945ee5", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "psr/log": "^1|^2|^3", + "symfony/polyfill-php85": "^1.32", + "symfony/var-dumper": "^7.4|^8.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/webpack-encore-bundle": "^1.0|^2.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/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v8.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "abd6c11dc468725d1627302ad10f6cd486e9e3d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/abd6c11dc468725d1627302ad10f6cd486e9e3d0", + "reference": "abd6c11dc468725d1627302ad10f6cd486e9e3d0", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/security-http": "<7.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/error-handler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^7.4|^8.0" + }, + "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/v8.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-09T12:28:30+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "reference": "c7de7a00ffb67842132da02ea92988a39ccd9f4e", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "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.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/finder", + "version": "v8.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "e2989e762c70f9490fa3a00a0ac0fae5aa97a531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/e2989e762c70f9490fa3a00a0ac0fae5aa97a531", + "reference": "e2989e762c70f9490fa3a00a0ac0fae5aa97a531", + "shasum": "" + }, + "require": { + "php": ">=8.4.1" + }, + "require-dev": { + "symfony/filesystem": "^7.4|^8.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/v8.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-27T09:05:56+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v8.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "6a168c8fcee806b57ac020244da14293d1f9a883" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6a168c8fcee806b57ac020244da14293d1f9a883", + "reference": "6a168c8fcee806b57ac020244da14293d1f9a883", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "^1.1" + }, + "conflict": { + "doctrine/dbal": "<4.3" + }, + "require-dev": { + "doctrine/dbal": "^4.3", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0" + }, + "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/v8.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-12T08:43:41+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v8.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "89d8d6e7fbab3d9eda89ccb5ecdf44a74c4ec9d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/89d8d6e7fbab3d9eda89ccb5ecdf44a74c4ec9d2", + "reference": "89d8d6e7fbab3d9eda89ccb5ecdf44a74c4ec9d2", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/dependency-injection": "<8.1", + "symfony/flex": "<2.10", + "symfony/http-client-contracts": "<2.5", + "symfony/translation-contracts": "<2.5", + "symfony/var-dumper": "<8.1", + "symfony/web-profiler-bundle": "<8.1", + "twig/twig": "<3.21" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^7.4|^8.0", + "symfony/clock": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/css-selector": "^7.4|^8.0", + "symfony/dependency-injection": "^8.1", + "symfony/dom-crawler": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/rate-limiter": "^7.4|^8.0", + "symfony/routing": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.0", + "symfony/stopwatch": "^7.4|^8.0", + "symfony/translation": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^7.4|^8.0", + "symfony/validator": "^7.4|^8.0", + "symfony/var-dumper": "^8.1", + "symfony/var-exporter": "^7.4|^8.0", + "twig/twig": "^3.21" + }, + "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/v8.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-27T09:27:36+00:00" + }, + { + "name": "symfony/mailer", + "version": "v8.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailer.git", + "reference": "4fa583a7377f28d54e4de442fba76375b2e20a12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailer/zipball/4fa583a7377f28d54e4de442fba76375b2e20a12", + "reference": "4fa583a7377f28d54e4de442fba76375b2e20a12", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.1.10|^3|^4", + "php": ">=8.4.1", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/mime": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/http-client-contracts": "<2.5" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/messenger": "^7.4|^8.0", + "symfony/twig-bridge": "^7.4|^8.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/v8.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-16T12:55:20+00:00" + }, + { + "name": "symfony/mime", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "b164ae7e3f7915aacfe9ee155f2f358502440664" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/b164ae7e3f7915aacfe9ee155f2f358502440664", + "reference": "b164ae7e3f7915aacfe9ee155f2f358502440664", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "egulias/email-validator": "~3.0.0", + "phpdocumentor/reflection-docblock": "<5.2|>=7", + "phpdocumentor/type-resolver": "<1.5.1" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3.1|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^5.2|^6.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/property-access": "^7.4|^8.0", + "symfony/property-info": "^7.4|^8.0", + "symfony/serializer": "^7.4|^8.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/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2", + "reference": "141046a8f9477948ff284fa65be2095baafb94f2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "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.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "e9247d281d694a5120554d9afaf54e070e88a603" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e9247d281d694a5120554d9afaf54e070e88a603", + "reference": "e9247d281d694a5120554d9afaf54e070e88a603", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "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.38.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-26T05:58:03+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "dc21118016c039a66235cf93d96b435ffb282412" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/dc21118016c039a66235cf93d96b435ffb282412", + "reference": "dc21118016c039a66235cf93d96b435ffb282412", + "shasum": "" + }, + "require": { + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "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.38.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-25T15:22:23+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.38.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "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.38.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-25T13:48:31+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.38.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "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.38.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-27T06:59:30+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "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.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "reference": "f4e1dfaee5b74aba5964fe1fd4dfc7ba5e3085fa", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "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.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.38.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-26T12:51:13+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", + "reference": "ba2ba04f3352cfa2dcbbcb90aee13ed967f505b1", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "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.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.38.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-26T02:25:22+00:00" + }, + { + "name": "symfony/polyfill-php86", + "version": "v1.38.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php86.git", + "reference": "fcec68d64f46dc84e1f6ffcf2c6dda40ff3143ad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php86/zipball/fcec68d64f46dc84e1f6ffcf2c6dda40ff3143ad", + "reference": "fcec68d64f46dc84e1f6ffcf2c6dda40ff3143ad", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php86\\": "" + }, + "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.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php86/tree/v1.38.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-25T11:52:35+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.37.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/26dfec253c4cf3e51b541b52ddf7e42cb0908e94", + "reference": "26dfec253c4cf3e51b541b52ddf7e42cb0908e94", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "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.37.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-04-10T16:19:22+00:00" + }, + { + "name": "symfony/process", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", + "reference": "c4a9e58f235a6bf7f97ffbfedae2687353ac79e5", + "shasum": "" + }, + "require": { + "php": ">=8.4.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/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/psr-http-message-bridge", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "67fd34de15ded1763aa1e330fe345f080a94022c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/67fd34de15ded1763aa1e330fe345f080a94022c", + "reference": "67fd34de15ded1763aa1e330fe345f080a94022c", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "psr/http-message": "^1.0|^2.0", + "symfony/http-foundation": "^7.4|^8.0" + }, + "conflict": { + "php-http/discovery": "<1.15" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "php-http/discovery": "^1.15", + "psr/log": "^1.1.4|^2|^3", + "symfony/browser-kit": "^7.4|^8.0", + "symfony/config": "^7.4|^8.0", + "symfony/event-dispatcher": "^7.4|^8.0", + "symfony/framework-bundle": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/runtime": "^7.4|^8.0" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\PsrHttpMessage\\": "" + }, + "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": "PSR HTTP message bridge", + "homepage": "https://symfony.com", + "keywords": [ + "http", + "http-message", + "psr-17", + "psr-7" + ], + "support": { + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/routing", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "fe0bfec72c8a806109fb9c3a5f2b898fe0c76eb3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/fe0bfec72c8a806109fb9c3a5f2b898fe0c76eb3", + "reference": "fe0bfec72c8a806109fb9c3a5f2b898fe0c76eb3", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/expression-language": "^7.4|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0" + }, + "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/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/c0a284bab1ed8aa0417e3d69250ab437739563a0", + "reference": "c0a284bab1ed8aa0417e3d69250ab437739563a0", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "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.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-16T09:55:08+00:00" + }, + { + "name": "symfony/string", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/afd5944f4005862d961efb85c8bbd5c523c4e3c9", + "reference": "afd5944f4005862d961efb85c8bbd5c523c4e3c9", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-intl-grapheme": "^1.33", + "symfony/polyfill-intl-normalizer": "^1.0", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.4|^8.0", + "symfony/http-client": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^7.4|^8.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/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/translation", + "version": "v8.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "342b4218630dc2cf284cedcb2080c80b13404014" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/342b4218630dc2cf284cedcb2080c80b13404014", + "reference": "342b4218630dc2cf284cedcb2080c80b13404014", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/polyfill-mbstring": "^1.0", + "symfony/translation-contracts": "^3.6.1" + }, + "conflict": { + "nikic/php-parser": "<5.0", + "symfony/http-client-contracts": "<2.5", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^7.4|^8.0", + "symfony/console": "^7.4|^8.0", + "symfony/dependency-injection": "^7.4|^8.0", + "symfony/finder": "^7.4|^8.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/intl": "^7.4|^8.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^7.4|^8.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^7.4|^8.0" + }, + "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/v8.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-06T11:11:44+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.7.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/ccb206b98faccc511ebae8e5fad50f2dc0b30621", + "reference": "ccb206b98faccc511ebae8e5fad50f2dc0b30621", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.7-dev" + } + }, + "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.7.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-05T06:23:12+00:00" + }, + { + "name": "symfony/type-info", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/type-info.git", + "reference": "9f24df8a79781b9b9f030fea7dfd2f3bd1e7e7e7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/type-info/zipball/9f24df8a79781b9b9f030fea7dfd2f3bd1e7e7e7", + "reference": "9f24df8a79781b9b9f030fea7dfd2f3bd1e7e7e7", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "psr/container": "^1.1|^2.0" + }, + "conflict": { + "phpstan/phpdoc-parser": "<1.30" + }, + "require-dev": { + "phpstan/phpdoc-parser": "^1.30|^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\TypeInfo\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mathias Arlaud", + "email": "mathias.arlaud@gmail.com" + }, + { + "name": "Baptiste LEDUC", + "email": "baptiste.leduc@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Extracts PHP types information.", + "homepage": "https://symfony.com", + "keywords": [ + "PHPStan", + "phpdoc", + "symfony", + "type" + ], + "support": { + "source": "https://github.com/symfony/type-info/tree/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/uid", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "7393f157a55f7e70a4de0334435c55a5a8fe749a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/7393f157a55f7e70a4de0334435c55a5a8fe749a", + "reference": "7393f157a55f7e70a4de0334435c55a5a8fe749a", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^7.4|^8.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/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v8.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "40096a2515a979f3125c5c928603995b8664c62a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/40096a2515a979f3125c5c928603995b8664c62a", + "reference": "40096a2515a979f3125c5c928603995b8664c62a", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/polyfill-mbstring": "^1.0" + }, + "conflict": { + "symfony/console": "<7.4", + "symfony/error-handler": "<7.4" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0", + "symfony/http-kernel": "^7.4|^8.0", + "symfony/process": "^7.4|^8.0", + "symfony/uid": "^7.4|^8.0", + "twig/twig": "^3.12" + }, + "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/v8.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-09T10:54:51+00:00" + }, + { + "name": "symfony/yaml", + "version": "v8.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "8e4cdd4311683516be06944f4b85244063cdb886" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/8e4cdd4311683516be06944f4b85244063cdb886", + "reference": "8e4cdd4311683516be06944f4b85244063cdb886", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<7.4" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0", + "yaml/yaml-test-suite": "*" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "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": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v8.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-06-09T11:06:24+00:00" + }, + { + "name": "tijsverkoyen/css-to-inline-styles", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/f0292ccf0ec75843d65027214426b6b163b48b41", + "reference": "f0292ccf0ec75843d65027214426b6b163b48b41", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "php": "^7.4 || ^8.0", + "symfony/css-selector": "^5.4 || ^6.0 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^8.5.21 || ^9.5.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "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/v2.4.0" + }, + "time": "2025-12-02T11:56:42+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.6.4", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "416df702837983f8d5ff48c9c3fee4f5f57b980b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/416df702837983f8d5ff48c9c3fee4f5f57b980b", + "reference": "416df702837983f8d5ff48c9c3fee4f5f57b980b", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.4", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.5", + "symfony/polyfill-ctype": "^1.26", + "symfony/polyfill-mbstring": "^1.26", + "symfony/polyfill-php80": "^1.26" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "5.6-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.6.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2026-07-06T19:11:50+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "8e1051fe39379367aecf014f41744ce7539a856f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/8e1051fe39379367aecf014f41744ce7539a856f", + "reference": "8e1051fe39379367aecf014f41744ce7539a856f", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpunit/phpunit": "~8.5 || ~9.6 || ~10.5 || ~11.5" + }, + "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": "https://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.1.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": "2026-04-26T05:33:54+00:00" + }, + { + "name": "zircote/swagger-php", + "version": "6.4.0", + "source": { + "type": "git", + "url": "https://github.com/zircote/swagger-php.git", + "reference": "d2006242dae81ae8833ad9a0ffb74e3254654cff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/d2006242dae81ae8833ad9a0ffb74e3254654cff", + "reference": "d2006242dae81ae8833ad9a0ffb74e3254654cff", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.19 || ^5.0", + "php": ">=8.2", + "phpstan/phpdoc-parser": "^2.0", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "radebatz/type-info-extras": "^1.0.2", + "symfony/console": "^7.4 || ^8.0", + "symfony/deprecation-contracts": "^2 || ^3", + "symfony/finder": "^5.0 || ^6.0 || ^7.0 || ^8.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0 || ^8.0" + }, + "conflict": { + "symfony/process": ">=6, <6.4.14" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "friendsofphp/php-cs-fixer": "^3.62.0", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^11.5 || >=12.5.22", + "rector/rector": "^2.3.1" + }, + "bin": [ + "bin/openapi" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.x-dev" + } + }, + "autoload": { + "psr-4": { + "OpenApi\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Robert Allen", + "email": "zircote@gmail.com" + }, + { + "name": "Bob Fanger", + "email": "bfanger@gmail.com", + "homepage": "https://bfanger.nl" + }, + { + "name": "Martin Rademacher", + "email": "mano@radebatz.net", + "homepage": "https://radebatz.net" + } + ], + "description": "Generate interactive documentation for your RESTful API using PHP attributes (preferred) or PHPDoc annotations", + "homepage": "https://github.com/zircote/swagger-php", + "keywords": [ + "api", + "json", + "rest", + "service discovery" + ], + "support": { + "issues": "https://github.com/zircote/swagger-php/issues", + "source": "https://github.com/zircote/swagger-php/tree/6.4.0" + }, + "funding": [ + { + "url": "https://github.com/zircote", + "type": "github" + } + ], + "time": "2026-07-12T03:44:54+00:00" + } + ], + "packages-dev": [ + { + "name": "archtechx/enums", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/archtechx/enums.git", + "reference": "81375b71c176f680880a95e7448d84258cfb5c72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/archtechx/enums/zipball/81375b71c176f680880a95e7448d84258cfb5c72", + "reference": "81375b71c176f680880a95e7448d84258cfb5c72", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "larastan/larastan": "^2.4", + "orchestra/testbench": "^8.0 || ^9.0", + "pestphp/pest": "^2.0", + "pestphp/pest-plugin-laravel": "^2.0" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "ArchTech\\Enums\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Samuel Štancl", + "email": "samuel@archte.ch" + } + ], + "description": "Helpers for making PHP enums more lovable.", + "support": { + "issues": "https://github.com/archtechx/enums/issues", + "source": "https://github.com/archtechx/enums/tree/v1.1.2" + }, + "time": "2025-06-06T23:15:09+00:00" + }, + { + "name": "codezero/browser-locale", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/codezero-be/browser-locale.git", + "reference": "dd6c50e5557b06b57960df6db7f52e85ef683e5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/codezero-be/browser-locale/zipball/dd6c50e5557b06b57960df6db7f52e85ef683e5e", + "reference": "dd6c50e5557b06b57960df6db7f52e85ef683e5e", + "shasum": "" + }, + "require": { + "php": "^7.0|^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.3.3", + "phpunit/phpunit": "^6.0|^7.0|^8.0|^9.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "CodeZero\\BrowserLocale\\Laravel\\BrowserLocaleServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "CodeZero\\BrowserLocale\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ivan Vermeyen", + "email": "ivan@codezero.be" + } + ], + "description": "Get the most preferred locales from your visitor's browser.", + "keywords": [ + "browser", + "country", + "detect", + "language", + "locale", + "php", + "website" + ], + "support": { + "issues": "https://github.com/codezero-be/browser-locale/issues", + "source": "https://github.com/codezero-be/browser-locale/tree/3.4.0" + }, + "funding": [ + { + "url": "https://paypal.me/ivanvermeyen", + "type": "custom" + }, + { + "url": "https://ko-fi.com/ivanvermeyen", + "type": "ko_fi" + } + ], + "time": "2024-03-02T12:51:17+00:00" + }, + { + "name": "composer/class-map-generator", + "version": "1.7.3", + "source": { + "type": "git", + "url": "https://github.com/composer/class-map-generator.git", + "reference": "86d8208fc3c649a3a999daf1a63c25201be2990f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/86d8208fc3c649a3a999daf1a63c25201be2990f", + "reference": "86d8208fc3c649a3a999daf1a63c25201be2990f", + "shasum": "" + }, + "require": { + "composer/pcre": "^2.1 || ^3.1", + "php": "^7.2 || ^8.0", + "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7 || ^8" + }, + "require-dev": { + "phpstan/phpstan": "^1.12 || ^2", + "phpstan/phpstan-deprecation-rules": "^1 || ^2", + "phpstan/phpstan-phpunit": "^1 || ^2", + "phpstan/phpstan-strict-rules": "^1.1 || ^2", + "phpunit/phpunit": "^8", + "symfony/filesystem": "^5.4 || ^6 || ^7 || ^8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\ClassMapGenerator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Utilities to scan PHP code and generate class maps.", + "keywords": [ + "classmap" + ], + "support": { + "issues": "https://github.com/composer/class-map-generator/issues", + "source": "https://github.com/composer/class-map-generator/tree/1.7.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2026-05-05T09:17:07+00:00" + }, + { + "name": "composer/pcre", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed", + "reference": "d5a341b3fb61f3001970940afb1d332968a183ed", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<2.2.2" + }, + "require-dev": { + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^9" + }, + "type": "library", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.4.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2026-06-07T11:47:49+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" + }, + { + "name": "dragon-code/contracts", + "version": "2.25.0", + "source": { + "type": "git", + "url": "https://github.com/TheDragonCode/contracts.git", + "reference": "13d1254801026be5ba33cf1309a414953869175f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TheDragonCode/contracts/zipball/13d1254801026be5ba33cf1309a414953869175f", + "reference": "13d1254801026be5ba33cf1309a414953869175f", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-message": "^1.0.1 || ^2.0", + "symfony/http-kernel": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0", + "symfony/polyfill-php80": "^1.23" + }, + "conflict": { + "andrey-helldar/contracts": "*" + }, + "require-dev": { + "illuminate/database": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "phpdocumentor/reflection-docblock": "^5.0 || ^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "DragonCode\\Contracts\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro", + "homepage": "https://dragon-code.pro" + } + ], + "description": "A set of contracts for any project", + "keywords": [ + "contracts", + "interfaces" + ], + "support": { + "source": "https://github.com/TheDragonCode/contracts" + }, + "funding": [ + { + "url": "https://boosty.to/dragon-code", + "type": "boosty" + }, + { + "url": "https://yoomoney.ru/to/410012608840929", + "type": "yoomoney" + } + ], + "time": "2026-03-17T21:50:20+00:00" + }, + { + "name": "dragon-code/pretty-array", + "version": "4.2.0", + "source": { + "type": "git", + "url": "https://github.com/TheDragonCode/pretty-array.git", + "reference": "b94034d92172a5d14a578822d68b2a8f8b5388e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TheDragonCode/pretty-array/zipball/b94034d92172a5d14a578822d68b2a8f8b5388e0", + "reference": "b94034d92172a5d14a578822d68b2a8f8b5388e0", + "shasum": "" + }, + "require": { + "dragon-code/contracts": "^2.20", + "dragon-code/support": "^6.11.2", + "ext-dom": "*", + "ext-mbstring": "*", + "php": "^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^10.0 || ^11.0 || ^12.0" + }, + "suggest": { + "symfony/thanks": "Give thanks (in the form of a GitHub) to your fellow PHP package maintainers" + }, + "type": "library", + "autoload": { + "psr-4": { + "DragonCode\\PrettyArray\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro", + "homepage": "https://dragon-code.pro" + } + ], + "description": "Simple conversion of an array to a pretty view", + "keywords": [ + "andrey helldar", + "array", + "dragon", + "dragon code", + "pretty", + "pretty array" + ], + "support": { + "issues": "https://github.com/TheDragonCode/pretty-array/issues", + "source": "https://github.com/TheDragonCode/pretty-array" + }, + "funding": [ + { + "url": "https://boosty.to/dragon-code", + "type": "boosty" + }, + { + "url": "https://yoomoney.ru/to/410012608840929", + "type": "yoomoney" + } + ], + "time": "2025-02-24T15:35:24+00:00" + }, + { + "name": "dragon-code/support", + "version": "6.17.1", + "source": { + "type": "git", + "url": "https://github.com/TheDragonCode/support.git", + "reference": "82a465953267989883d64b921e9725600a5073b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/TheDragonCode/support/zipball/82a465953267989883d64b921e9725600a5073b5", + "reference": "82a465953267989883d64b921e9725600a5073b5", + "shasum": "" + }, + "require": { + "dragon-code/contracts": "^2.22.0", + "ext-bcmath": "*", + "ext-ctype": "*", + "ext-dom": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": "^8.1", + "psr/http-message": "^1.0.1 || ^2.0", + "voku/portable-ascii": "^1.4.8 || ^2.0.1" + }, + "conflict": { + "andrey-helldar/support": "*" + }, + "require-dev": { + "illuminate/contracts": "^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0", + "phpunit/phpunit": "^9.6 || ^11.0 || ^12.0", + "symfony/var-dumper": "^6.0 || ^7.0" + }, + "suggest": { + "dragon-code/laravel-support": "Various helper files for the Laravel and Lumen frameworks", + "symfony/thanks": "Give thanks (in the form of a GitHub) to your fellow PHP package maintainers" + }, + "type": "library", + "extra": { + "dragon-code": { + "docs-generator": { + "preview": { + "brand": "php", + "vendor": "The Dragon Code" + } + } + } + }, + "autoload": { + "psr-4": { + "DragonCode\\Support\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro", + "homepage": "https://dragon-code.pro" + } + ], + "description": "Support package is a collection of helpers and tools for any project.", + "keywords": [ + "dragon", + "dragon-code", + "framework", + "helper", + "helpers", + "laravel", + "php", + "support", + "symfony", + "yii", + "yii2" + ], + "support": { + "issues": "https://github.com/TheDragonCode/support/issues", + "source": "https://github.com/TheDragonCode/support" + }, + "funding": [ + { + "url": "https://boosty.to/dragon-code", + "type": "boosty" + }, + { + "url": "https://yoomoney.ru/to/410012608840929", + "type": "yoomoney" + } + ], + "time": "2026-04-03T15:06:29+00:00" + }, + { + "name": "fakerphp/faker", + "version": "v1.24.1", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^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": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "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", + "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.24.1" + }, + "time": "2024-11-21T13:46:39+00:00" + }, + { + "name": "filp/whoops", + "version": "2.18.4", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/d2102955e48b9fd9ab24280a7ad12ed552752c4d", + "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^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.18.4" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2025-08-08T12:00:00+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.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.1.1" + }, + "time": "2025-04-30T06:54:44+00:00" + }, + { + "name": "laravel-lang/actions", + "version": "1.13.2", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/actions.git", + "reference": "5e9bbd3b8af3fed4b733e424c56810b0d3e81377" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/actions/zipball/5e9bbd3b8af3fed4b733e424c56810b0d3e81377", + "reference": "5e9bbd3b8af3fed4b733e424c56810b0d3e81377", + "shasum": "" + }, + "require": { + "ext-json": "*", + "laravel-lang/publisher": "^14.0 || ^15.0 || ^16.0", + "php": "^8.1" + }, + "require-dev": { + "laravel-lang/status-generator": "^2.3.1", + "phpunit/phpunit": "^10.0 || ^11.0 || ^12.0", + "symfony/var-dumper": "^6.3 || ^7.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "LaravelLang\\Actions\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "LaravelLang\\Actions\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro", + "homepage": "https://dragon-code.pro" + }, + { + "name": "Laravel Lang Team", + "homepage": "https://laravel-lang.com" + } + ], + "description": "Translation of buttons and other action elements", + "keywords": [ + "actions", + "buttons", + "lang", + "languages", + "laravel", + "translations" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/actions/issues", + "source": "https://github.com/Laravel-Lang/actions/tree/1.13.2" + }, + "funding": [ + { + "url": "https://boosty.to/dragon-code", + "type": "boosty" + }, + { + "url": "https://yoomoney.ru/to/410012608840929", + "type": "yoomoney" + } + ], + "time": "2026-05-30T12:11:11+00:00" + }, + { + "name": "laravel-lang/attributes", + "version": "2.16.4", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/attributes.git", + "reference": "4a0de72632bb1862532977fd76ed6be9c1cc65af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/attributes/zipball/4a0de72632bb1862532977fd76ed6be9c1cc65af", + "reference": "4a0de72632bb1862532977fd76ed6be9c1cc65af", + "shasum": "" + }, + "require": { + "ext-json": "*", + "laravel-lang/publisher": "^14.0 || ^15.0 || ^16.0", + "php": "^8.1" + }, + "require-dev": { + "laravel-lang/status-generator": "^1.19 || ^2.0", + "phpunit/phpunit": "^10.0 || ^11.0 || ^12.0", + "symfony/var-dumper": "^6.0 || ^7.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "LaravelLang\\Attributes\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "LaravelLang\\Attributes\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro" + }, + { + "name": "Laravel-Lang Team", + "homepage": "https://github.com/Laravel-Lang" + } + ], + "description": "Translation of form element names", + "keywords": [ + "attributes", + "fields", + "form", + "lang", + "languages", + "laravel", + "messages", + "translations", + "validation" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/attributes/issues", + "source": "https://github.com/Laravel-Lang/attributes/tree/2.16.4" + }, + "funding": [ + { + "url": "https://boosty.to/dragon-code", + "type": "boosty" + }, + { + "url": "https://yoomoney.ru/to/410012608840929", + "type": "yoomoney" + } + ], + "time": "2026-05-30T12:11:05+00:00" + }, + { + "name": "laravel-lang/common", + "version": "6.8.0", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/common.git", + "reference": "f1e3383f94c4f157f0f41324792382bf2dd84d52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/common/zipball/f1e3383f94c4f157f0f41324792382bf2dd84d52", + "reference": "f1e3383f94c4f157f0f41324792382bf2dd84d52", + "shasum": "" + }, + "require": { + "laravel-lang/actions": "^1.9", + "laravel-lang/attributes": "^2.12", + "laravel-lang/http-statuses": "^3.8", + "laravel-lang/json-fallback": "^2.2", + "laravel-lang/lang": "^13.12 || ^14.0 || ^15.5.1", + "laravel-lang/locales": "^2.10", + "laravel-lang/models": "^1.3", + "laravel-lang/moonshine": "^1.2", + "laravel-lang/publisher": "^16.5", + "laravel-lang/routes": "^1.7", + "laravel-lang/starter-kits": "^1.0", + "php": "^8.1" + }, + "require-dev": { + "dragon-code/support": "^6.13", + "orchestra/testbench": "^8.17 || ^9.1.2 || ^10.0 || ^11.0", + "phpunit/phpunit": "^10.5.20 || ^11.0 || ^12.0", + "symfony/var-dumper": "^6.4 || ^7.1.1 || ^8.0" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laravel-Lang Team", + "homepage": "https://github.com/Laravel-Lang" + }, + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro", + "homepage": "https://dragon-code.pro" + } + ], + "description": "Easily connect the necessary language packs to the application", + "keywords": [ + "Laravel-lang", + "actions", + "attribute", + "attributes", + "breeze", + "buttons", + "cashier", + "fortify", + "framework", + "http", + "http-status", + "http-status-code", + "i18n", + "jetstream", + "lang", + "language", + "languages", + "laravel", + "locale", + "locales", + "localization", + "localizations", + "moonshine", + "nova", + "publisher", + "spark", + "translation", + "translations", + "ui" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/common/issues", + "source": "https://github.com/Laravel-Lang/common" + }, + "funding": [ + { + "url": "https://boosty.to/dragon-code", + "type": "boosty" + }, + { + "url": "https://yoomoney.ru/to/410012608840929", + "type": "yoomoney" + } + ], + "time": "2026-03-17T23:15:14+00:00" + }, + { + "name": "laravel-lang/config", + "version": "1.17.0", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/config.git", + "reference": "77ad089234aa74961ca30c7e6d13db9a62654c87" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/config/zipball/77ad089234aa74961ca30c7e6d13db9a62654c87", + "reference": "77ad089234aa74961ca30c7e6d13db9a62654c87", + "shasum": "" + }, + "require": { + "archtechx/enums": "^1.0", + "illuminate/config": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "illuminate/support": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "laravel-lang/locale-list": "^1.6", + "php": "^8.1" + }, + "require-dev": { + "orchestra/testbench": "^8.23 || ^9.1 || ^10.0 || ^11.0", + "pestphp/pest": "^2.34 || ^3.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "LaravelLang\\Config\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "LaravelLang\\Config\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro", + "homepage": "https://dragon-code.pro" + }, + { + "name": "Laravel-Lang Team", + "homepage": "https://laravel-lang.com" + } + ], + "description": "The Laravel-Lang config package", + "keywords": [ + "Laravel-lang", + "Settings", + "config", + "lang", + "languages", + "laravel", + "locale", + "locales", + "localization", + "localizations", + "translation", + "translations" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/config/issues", + "source": "https://github.com/Laravel-Lang/config/tree/1.17.0" + }, + "time": "2026-03-17T21:23:36+00:00" + }, + { + "name": "laravel-lang/http-statuses", + "version": "3.13.2", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/http-statuses.git", + "reference": "2d33c7b4f5b268224c559c27c1220764b5391ae2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/http-statuses/zipball/2d33c7b4f5b268224c559c27c1220764b5391ae2", + "reference": "2d33c7b4f5b268224c559c27c1220764b5391ae2", + "shasum": "" + }, + "require": { + "ext-json": "*", + "laravel-lang/publisher": "^14.1 || ^15.0 || ^16.0", + "php": "^8.1" + }, + "require-dev": { + "laravel-lang/status-generator": "^1.19 || ^2.0", + "phpunit/phpunit": "^10.0 || ^11.0 || ^12.0", + "symfony/var-dumper": "^6.0 || ^7.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "LaravelLang\\HttpStatuses\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "LaravelLang\\HttpStatuses\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro" + }, + { + "name": "Laravel-Lang Team", + "homepage": "https://github.com/Laravel-Lang" + } + ], + "description": "Translation of HTTP statuses", + "keywords": [ + "http", + "lang", + "languages", + "laravel", + "messages", + "status", + "translations" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/http-statuses/issues", + "source": "https://github.com/Laravel-Lang/http-statuses/tree/3.13.2" + }, + "funding": [ + { + "url": "https://boosty.to/dragon-code", + "type": "boosty" + }, + { + "url": "https://yoomoney.ru/to/410012608840929", + "type": "yoomoney" + } + ], + "time": "2026-05-30T12:11:06+00:00" + }, + { + "name": "laravel-lang/json-fallback", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/json-fallback.git", + "reference": "1b02c798da837c63e2d01f45b45bb49367d5b6a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/json-fallback/zipball/1b02c798da837c63e2d01f45b45bb49367d5b6a1", + "reference": "1b02c798da837c63e2d01f45b45bb49367d5b6a1", + "shasum": "" + }, + "require": { + "illuminate/support": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "illuminate/translation": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "php": "^8.1" + }, + "require-dev": { + "orchestra/testbench": "^8.0 || ^9.0 || ^10.0 || ^11.0", + "phpunit/phpunit": "^10.0 || ^11.0 || ^12.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "LaravelLang\\JsonFallback\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro", + "homepage": "https://github.com/andrey-helldar" + }, + { + "name": "Felipe Dsdev", + "homepage": "https://github.com/felipe-dsdev" + } + ], + "description": "Adds support for fallback JSON string translation", + "support": { + "issues": "https://github.com/Laravel-Lang/json-fallback/issues", + "source": "https://github.com/Laravel-Lang/json-fallback/tree/2.3.0" + }, + "funding": [ + { + "url": "https://boosty.to/dragon-code", + "type": "boosty" + }, + { + "url": "https://yoomoney.ru/to/410012608840929", + "type": "yoomoney" + } + ], + "time": "2026-03-17T21:18:08+00:00" + }, + { + "name": "laravel-lang/lang", + "version": "15.33.0", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/lang.git", + "reference": "963488ac110876196c88630ea31594742f44358b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/lang/zipball/963488ac110876196c88630ea31594742f44358b", + "reference": "963488ac110876196c88630ea31594742f44358b", + "shasum": "" + }, + "require": { + "ext-json": "*", + "laravel-lang/publisher": "^16.0", + "php": "^8.2" + }, + "conflict": { + "laravel/framework": "<11.0.7" + }, + "require-dev": { + "dragon-code/codestyler": "^6.0", + "laravel-lang/status-generator": "^2.11", + "phpunit/phpunit": "^11.0 || ^12.0", + "symfony/var-dumper": "^7.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "LaravelLang\\Lang\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "LaravelLang\\Lang\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laravel-Lang Team", + "homepage": "https://github.com/Laravel-Lang" + } + ], + "description": "List of 126 languages for Laravel Framework, Laravel Jetstream, Laravel Fortify, Laravel Breeze, Laravel Cashier, Laravel Nova, Laravel Spark and Laravel UI", + "keywords": [ + "lang", + "languages", + "laravel", + "lpm" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/lang/issues", + "source": "https://github.com/Laravel-Lang/lang" + }, + "funding": [ + { + "url": "https://boosty.to/dragon-code", + "type": "boosty" + }, + { + "url": "https://yoomoney.ru/to/410012608840929", + "type": "yoomoney" + } + ], + "time": "2026-07-24T12:39:52+00:00" + }, + { + "name": "laravel-lang/locale-list", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/locale-list.git", + "reference": "48e61c7f0a957420d4aaf5d35653889c25c4e2d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/locale-list/zipball/48e61c7f0a957420d4aaf5d35653889c25c4e2d4", + "reference": "48e61c7f0a957420d4aaf5d35653889c25c4e2d4", + "shasum": "" + }, + "require": { + "archtechx/enums": "^0.3.2 || ^1.0", + "php": "^8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "LaravelLang\\LocaleList\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro", + "homepage": "https://dragon-code.pro" + }, + { + "name": "Laravel-Lang Team", + "homepage": "https://laravel-lang.com" + } + ], + "description": "List of localizations available in Laravel Lang projects", + "keywords": [ + "Laravel-lang", + "lang", + "languages", + "laravel", + "locale", + "locales", + "localization", + "translation", + "translations" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/locale-list/issues", + "source": "https://github.com/Laravel-Lang/locale-list" + }, + "funding": [ + { + "url": "https://boosty.to/dragon-code", + "type": "boosty" + }, + { + "url": "https://yoomoney.ru/to/410012608840929", + "type": "yoomoney" + } + ], + "time": "2026-01-20T08:17:15+00:00" + }, + { + "name": "laravel-lang/locales", + "version": "2.11.0", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/locales.git", + "reference": "761aa3cfbc5bbe29eb958c9839e7dd3806193bac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/locales/zipball/761aa3cfbc5bbe29eb958c9839e7dd3806193bac", + "reference": "761aa3cfbc5bbe29eb958c9839e7dd3806193bac", + "shasum": "" + }, + "require": { + "archtechx/enums": "^0.3.2 || ^1.0", + "dragon-code/support": "^6.11.3", + "ext-json": "*", + "illuminate/collections": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "laravel-lang/config": "^1.12", + "laravel-lang/locale-list": "^1.5", + "laravel-lang/native-country-names": "^1.5", + "laravel-lang/native-currency-names": "^1.6", + "laravel-lang/native-locale-names": "^2.5", + "php": "^8.1" + }, + "require-dev": { + "orchestra/testbench": "^8.0 || ^9.0 || ^10.0 || ^11.0", + "pestphp/pest": "^2.24.1 || ^3.0 || ^4.0", + "symfony/var-dumper": "^6.0 || ^7.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "LaravelLang\\Locales\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "LaravelLang\\Locales\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro" + }, + { + "name": "Laravel-Lang Team", + "homepage": "https://laravel-lang.com" + } + ], + "description": "Basic functionality for working with localizations", + "keywords": [ + "laravel", + "locale", + "locales", + "localization", + "translation", + "translations" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/locales/issues", + "source": "https://github.com/Laravel-Lang/locales" + }, + "time": "2026-03-17T22:40:41+00:00" + }, + { + "name": "laravel-lang/models", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/models.git", + "reference": "a9aa0205ca019f4459ace26b0432b3b6d1886f66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/models/zipball/a9aa0205ca019f4459ace26b0432b3b6d1886f66", + "reference": "a9aa0205ca019f4459ace26b0432b3b6d1886f66", + "shasum": "" + }, + "require": { + "composer/class-map-generator": "^1.3", + "dragon-code/support": "^6.13", + "illuminate/database": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "illuminate/support": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "laravel-lang/config": "^1.12", + "laravel-lang/locales": "^2.10.0", + "laravel/prompts": ">=0.1", + "php": "^8.1" + }, + "require-dev": { + "laravel/pint": "^1.20", + "orchestra/testbench": "^8.0 || ^9.0 || ^10.0 || ^11.0", + "pestphp/pest": "^2.34 || ^3.0 || ^4.0", + "pestphp/pest-plugin-laravel": "^2.4 || ^3.0 || ^4.0", + "symfony/var-dumper": "^6.0 || ^7.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "LaravelLang\\Models\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "LaravelLang\\Models\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro", + "homepage": "https://dragon-code.pro", + "role": "Maintainer" + }, + { + "name": "Andrey Sokolov", + "email": "walfireru@gmail.com", + "homepage": "https://github.com/Quiss", + "role": "Usability consultant" + }, + { + "name": "Laravel-Lang Team", + "homepage": "https://github.com/Laravel-Lang" + } + ], + "description": "Easy and fast way to localize models", + "keywords": [ + "database", + "l18n", + "languages", + "laravel", + "locales", + "localization", + "models", + "translate", + "translations" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/models/issues", + "source": "https://github.com/Laravel-Lang/models" + }, + "funding": [ + { + "url": "https://boosty.to/dragon-code", + "type": "boosty" + }, + { + "url": "https://yoomoney.ru/to/410012608840929", + "type": "yoomoney" + } + ], + "time": "2026-03-17T23:00:20+00:00" + }, + { + "name": "laravel-lang/moonshine", + "version": "1.9.2", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/moonshine.git", + "reference": "3927bcf658d392c9f2eb03c773f52ff650b37f63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/moonshine/zipball/3927bcf658d392c9f2eb03c773f52ff650b37f63", + "reference": "3927bcf658d392c9f2eb03c773f52ff650b37f63", + "shasum": "" + }, + "require": { + "ext-json": "*", + "laravel-lang/publisher": "^15.0 || ^16.4.1", + "php": "^8.1" + }, + "require-dev": { + "dragon-code/support": "^6.15.2", + "illuminate/support": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "laravel-lang/lang": "^14.8 || ^15.14", + "laravel-lang/status-generator": "^2.12", + "phpunit/phpunit": "^10.0 || ^11.0 || ^12.0", + "symfony/var-dumper": "^6.3 || ^7.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "LaravelLang\\MoonShine\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "LaravelLang\\MoonShine\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro" + }, + { + "name": "Laravel Lang Team", + "homepage": "https://laravel-lang.com" + } + ], + "description": "Translation for MoonShine admin panel", + "keywords": [ + "lang", + "languages", + "laravel", + "moonshine", + "translations" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/moonshine/issues", + "source": "https://github.com/Laravel-Lang/moonshine/tree/1.9.2" + }, + "funding": [ + { + "url": "https://boosty.to/dragon-code", + "type": "boosty" + }, + { + "url": "https://yoomoney.ru/to/410012608840929", + "type": "yoomoney" + } + ], + "time": "2026-05-30T12:11:41+00:00" + }, + { + "name": "laravel-lang/native-country-names", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/native-country-names.git", + "reference": "1d293138e34eb9e914bc4568cdebac2cb0a2eb0e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/native-country-names/zipball/1d293138e34eb9e914bc4568cdebac2cb0a2eb0e", + "reference": "1d293138e34eb9e914bc4568cdebac2cb0a2eb0e", + "shasum": "" + }, + "require": { + "dragon-code/support": "^6.11", + "ext-json": "*", + "illuminate/collections": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "php": "^8.1" + }, + "require-dev": { + "illuminate/support": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "laravel-lang/locale-list": "^1.5", + "pestphp/pest": "^2.0 || ^3.0 || ^4.0", + "punic/punic": "^3.8", + "symfony/console": "^6.0 || ^7.0 || ^8.0", + "symfony/process": "^6.0 || ^7.0 || ^8.0", + "symfony/var-dumper": "^6.0 || ^7.0 || ^8.0", + "vlucas/phpdotenv": "^5.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "LaravelLang\\NativeCountryNames\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro" + }, + { + "name": "Laravel-Lang Team", + "homepage": "https://laravel-lang.com" + } + ], + "description": "The project contains native translations of country names", + "keywords": [ + "Laravel-lang", + "countries", + "country", + "lang", + "languages", + "laravel", + "locale", + "locales", + "localization", + "territories", + "territory", + "translation", + "translations" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/native-country-names/issues", + "source": "https://github.com/Laravel-Lang/native-country-names" + }, + "funding": [ + { + "url": "https://boosty.to/dragon-code", + "type": "boosty" + }, + { + "url": "https://yoomoney.ru/to/410012608840929", + "type": "yoomoney" + } + ], + "time": "2026-03-17T22:01:15+00:00" + }, + { + "name": "laravel-lang/native-currency-names", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/native-currency-names.git", + "reference": "7ebe95a9942bebf6afd61986fa18c82dade3d583" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/native-currency-names/zipball/7ebe95a9942bebf6afd61986fa18c82dade3d583", + "reference": "7ebe95a9942bebf6afd61986fa18c82dade3d583", + "shasum": "" + }, + "require": { + "dragon-code/support": "^6.11", + "ext-json": "*", + "illuminate/collections": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "php": "^8.1" + }, + "require-dev": { + "illuminate/support": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "laravel-lang/locale-list": "^1.2", + "pestphp/pest": "^2.0 || ^3.0 || ^4.0", + "punic/punic": "^3.8", + "symfony/console": "^6.0 || ^7.0", + "symfony/process": "^6.0 || ^7.0", + "symfony/var-dumper": "^6.0 || ^7.0", + "vlucas/phpdotenv": "^5.6" + }, + "type": "library", + "autoload": { + "psr-4": { + "LaravelLang\\NativeCurrencyNames\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro" + }, + { + "name": "Laravel-Lang Team", + "homepage": "https://laravel-lang.com" + } + ], + "description": "The project contains native translations of currency names", + "keywords": [ + "Laravel-lang", + "currency", + "lang", + "languages", + "laravel", + "locale", + "locales", + "localization", + "translation", + "translations" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/native-currency-names/issues", + "source": "https://github.com/Laravel-Lang/native-currency-names" + }, + "funding": [ + { + "url": "https://boosty.to/dragon-code", + "type": "boosty" + }, + { + "url": "https://yoomoney.ru/to/410012608840929", + "type": "yoomoney" + } + ], + "time": "2026-06-20T16:50:37+00:00" + }, + { + "name": "laravel-lang/native-locale-names", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/native-locale-names.git", + "reference": "e5925182bad34654203a5d93544357ca00a6d9c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/native-locale-names/zipball/e5925182bad34654203a5d93544357ca00a6d9c0", + "reference": "e5925182bad34654203a5d93544357ca00a6d9c0", + "shasum": "" + }, + "require": { + "dragon-code/support": "^6.11", + "ext-json": "*", + "php": "^8.1" + }, + "require-dev": { + "illuminate/support": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "laravel-lang/locale-list": "^1.2", + "pestphp/pest": "^2.24.3 || ^3.0 || ^4.0", + "punic/punic": "^3.8", + "symfony/console": "^6.0 || ^7.0", + "symfony/process": "^6.0 || ^7.0", + "symfony/var-dumper": "^6.0 || ^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "LaravelLang\\NativeLocaleNames\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro" + }, + { + "name": "Laravel-Lang Team", + "homepage": "https://laravel-lang.com" + } + ], + "description": "The project contains native translations of locale names", + "keywords": [ + "Laravel-lang", + "lang", + "languages", + "laravel", + "locale", + "locales", + "localization", + "translation", + "translations" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/native-locale-names/issues", + "source": "https://github.com/Laravel-Lang/native-locale-names" + }, + "funding": [ + { + "url": "https://boosty.to/dragon-code", + "type": "boosty" + }, + { + "url": "https://yoomoney.ru/to/410012608840929", + "type": "yoomoney" + } + ], + "time": "2026-06-20T16:52:11+00:00" + }, + { + "name": "laravel-lang/publisher", + "version": "16.8.0", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/publisher.git", + "reference": "e5d3383f5385c2102f8a0d3dbe488ed86cd0250f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/publisher/zipball/e5d3383f5385c2102f8a0d3dbe488ed86cd0250f", + "reference": "e5d3383f5385c2102f8a0d3dbe488ed86cd0250f", + "shasum": "" + }, + "require": { + "composer/semver": "^3.4", + "dragon-code/pretty-array": "^4.1", + "dragon-code/support": "^6.11.3", + "ext-json": "*", + "illuminate/collections": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "illuminate/console": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "illuminate/support": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "laravel-lang/config": "^1.12", + "laravel-lang/locales": "^2.10", + "league/commonmark": "^2.4.1", + "league/config": "^1.2", + "php": "^8.1" + }, + "conflict": { + "laravel-lang/attributes": "<2.0", + "laravel-lang/http-statuses": "<3.0", + "laravel-lang/lang": "<11.0" + }, + "require-dev": { + "laravel-lang/json-fallback": "^2.2", + "orchestra/testbench": "^8.14 || ^9.0 || ^10.0 || ^11.0", + "phpunit/phpunit": "^10.4.2 || ^11.0 || ^12.0", + "symfony/var-dumper": "^6.3.6 || ^7.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "LaravelLang\\Publisher\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "LaravelLang\\Publisher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro" + }, + { + "name": "Laravel-Lang Team", + "homepage": "https://laravel-lang.com" + } + ], + "description": "Localization publisher for your Laravel application", + "keywords": [ + "Laravel-lang", + "breeze", + "cashier", + "fortify", + "framework", + "i18n", + "jetstream", + "lang", + "languages", + "laravel", + "locale", + "locales", + "localization", + "localizations", + "lpm", + "lumen", + "nova", + "publisher", + "spark", + "trans", + "translation", + "translations", + "validations" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/publisher/issues", + "source": "https://github.com/Laravel-Lang/publisher" + }, + "funding": [ + { + "url": "https://boosty.to/dragon-code", + "type": "boosty" + }, + { + "url": "https://yoomoney.ru/to/410012608840929", + "type": "yoomoney" + } + ], + "time": "2026-03-17T22:56:20+00:00" + }, + { + "name": "laravel-lang/routes", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/routes.git", + "reference": "29d7f83f2679f51187aa4e761d73bb4e0d133bf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/routes/zipball/29d7f83f2679f51187aa4e761d73bb4e0d133bf4", + "reference": "29d7f83f2679f51187aa4e761d73bb4e0d133bf4", + "shasum": "" + }, + "require": { + "codezero/browser-locale": "^3.4", + "dragon-code/support": "^6.13", + "illuminate/config": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "illuminate/http": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "illuminate/routing": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "illuminate/support": "^10.0 || ^11.0 || ^12.0 || ^13.0", + "laravel-lang/config": "^1.13", + "laravel-lang/locales": "^2.10", + "php": "^8.1" + }, + "require-dev": { + "orchestra/testbench": "^8.23 || ^9.1 || ^10.0 || ^11.0", + "pestphp/pest": "^2.34 || ^3.0 || ^4.0", + "pestphp/pest-plugin-laravel": "^2.4 || ^3.0 || ^4.0", + "symfony/var-dumper": "^6.0 || ^7.0 || ^8.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "LaravelLang\\Routes\\ServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "helpers/functions.php" + ], + "psr-4": { + "LaravelLang\\Routes\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro", + "homepage": "https://dragon-code.pro" + }, + { + "name": "Laravel-Lang Team", + "homepage": "https://github.com/Laravel-Lang" + } + ], + "description": "Easy and fast way to localize routes", + "keywords": [ + "l18n", + "languages", + "laravel", + "localization", + "routes", + "translate", + "translations" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/routes/issues", + "source": "https://github.com/Laravel-Lang/routes" + }, + "funding": [ + { + "url": "https://boosty.to/dragon-code", + "type": "boosty" + }, + { + "url": "https://yoomoney.ru/to/410012608840929", + "type": "yoomoney" + } + ], + "time": "2026-03-17T23:06:16+00:00" + }, + { + "name": "laravel-lang/starter-kits", + "version": "1.16.1", + "source": { + "type": "git", + "url": "https://github.com/Laravel-Lang/starter-kits.git", + "reference": "87c00c77bbb73385d0526b35dbe07ea2bbfc32db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Laravel-Lang/starter-kits/zipball/87c00c77bbb73385d0526b35dbe07ea2bbfc32db", + "reference": "87c00c77bbb73385d0526b35dbe07ea2bbfc32db", + "shasum": "" + }, + "require": { + "ext-json": "*", + "laravel-lang/publisher": "^16.6", + "php": "^8.1" + }, + "require-dev": { + "laravel-lang/status-generator": "^2.13", + "phpunit/phpunit": "^11.0 || ^12.0", + "symfony/var-dumper": "^7.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "LaravelLang\\StarterKits\\ServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "LaravelLang\\StarterKits\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andrey Helldar", + "email": "helldar@dragon-code.pro" + }, + { + "name": "Laravel Lang Team", + "homepage": "https://laravel-lang.com" + } + ], + "description": "Translations for Laravel Starter Kits", + "keywords": [ + "i18n", + "lang", + "languages", + "laravel", + "starter-kit", + "starter-kits", + "translations" + ], + "support": { + "issues": "https://github.com/Laravel-Lang/starter-kits/issues", + "source": "https://github.com/Laravel-Lang/starter-kits/tree/1.16.1" + }, + "funding": [ + { + "url": "https://boosty.to/dragon-code", + "type": "boosty" + }, + { + "url": "https://yoomoney.ru/to/410012608840929", + "type": "yoomoney" + } + ], + "time": "2026-07-16T18:13:26+00:00" + }, + { + "name": "laravel/agent-detector", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/agent-detector.git", + "reference": "90694b9256099591cf9e55d08c18ba7a00bf099f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/agent-detector/zipball/90694b9256099591cf9e55d08c18ba7a00bf099f", + "reference": "90694b9256099591cf9e55d08c18ba7a00bf099f", + "shasum": "" + }, + "require": { + "php": "^8.2.0" + }, + "require-dev": { + "laravel/pint": "^1.24.0", + "pestphp/pest": "^3.8.5|^4.1.0", + "pestphp/pest-plugin-type-coverage": "^3.0|^4.0.2", + "phpstan/phpstan": "^2.1.26", + "rector/rector": "^2.1.7", + "symfony/var-dumper": "^7.3.3" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Laravel\\AgentDetector\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Detect if code is running in an AI agent or automated development environment", + "homepage": "https://github.com/laravel/agent-detector", + "keywords": [ + "Agent", + "ai", + "automation", + "claude", + "cursor", + "detection", + "devin", + "php" + ], + "support": { + "issues": "https://github.com/laravel/agent-detector/issues", + "source": "https://github.com/laravel/agent-detector" + }, + "time": "2026-04-29T18:32:34+00:00" + }, + { + "name": "laravel/pail", + "version": "v1.2.7", + "source": { + "type": "git", + "url": "https://github.com/laravel/pail.git", + "reference": "2f7d27dada8effc48b8c424445a69cca7007daaa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/pail/zipball/2f7d27dada8effc48b8c424445a69cca7007daaa", + "reference": "2f7d27dada8effc48b8c424445a69cca7007daaa", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "illuminate/console": "^10.24|^11.0|^12.0|^13.0", + "illuminate/contracts": "^10.24|^11.0|^12.0|^13.0", + "illuminate/log": "^10.24|^11.0|^12.0|^13.0", + "illuminate/process": "^10.24|^11.0|^12.0|^13.0", + "illuminate/support": "^10.24|^11.0|^12.0|^13.0", + "nunomaduro/termwind": "^1.15|^2.0", + "php": "^8.2", + "symfony/console": "^6.0|^7.0|^8.0" + }, + "require-dev": { + "laravel/framework": "^10.24|^11.0|^12.0|^13.0", + "laravel/pint": "^1.13", + "orchestra/testbench-core": "^8.13|^9.17|^10.8|^11.0", + "pestphp/pest": "^2.20|^3.0|^4.0", + "pestphp/pest-plugin-type-coverage": "^2.3|^3.0|^4.0", + "phpstan/phpstan": "^1.12.27", + "symfony/var-dumper": "^6.3|^7.0|^8.0", + "symfony/yaml": "^6.3|^7.0|^8.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Pail\\PailServiceProvider" + ] + }, + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Pail\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Easily delve into your Laravel application's log files directly from the command line.", + "homepage": "https://github.com/laravel/pail", + "keywords": [ + "dev", + "laravel", + "logs", + "php", + "tail" + ], + "support": { + "issues": "https://github.com/laravel/pail/issues", + "source": "https://github.com/laravel/pail" + }, + "time": "2026-05-20T22:24:57+00:00" + }, + { + "name": "laravel/pao", + "version": "v1.1.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/pao.git", + "reference": "41b3c61ebeddce52a446afe6d21e0b02983fb2f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/pao/zipball/41b3c61ebeddce52a446afe6d21e0b02983fb2f6", + "reference": "41b3c61ebeddce52a446afe6d21e0b02983fb2f6", + "shasum": "" + }, + "require": { + "laravel/agent-detector": "^2.0.2", + "php": "^8.3" + }, + "conflict": { + "laravel/framework": "<12.0.0", + "nunomaduro/collision": "<8.9.3", + "pestphp/pest": "<4.6.3 || >=6.0.0", + "phpunit/phpunit": "<12.5.23 || >=13.0.0 <13.1.7 || >=14.0.0" + }, + "require-dev": { + "brianium/paratest": "^7.20.0", + "laravel/pint": "^1.29.1", + "orchestra/testbench": "^10.11.0 || ^11.1.0", + "pestphp/pest": "^4.7.2 || ^5.0.0", + "pestphp/pest-plugin-type-coverage": "^4.0.4 || ^5.0.0", + "phpstan/phpstan": "^2.2.2", + "rector/rector": "^2.4.5", + "symfony/process": "^7.4.8 || ^8.1.0", + "symfony/var-dumper": "^7.4.8 || ^8.1.0" + }, + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Laravel\\Pao\\Drivers\\Pest\\Plugin" + ] + }, + "laravel": { + "providers": [ + "Laravel\\Pao\\Laravel\\ServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/Autoload.php" + ], + "psr-4": { + "Laravel\\Pao\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Agent-optimized output for PHP testing tools", + "keywords": [ + "Agent", + "PHPStan", + "ai", + "dev", + "paratest", + "pest", + "php", + "phpunit", + "rector", + "testing" + ], + "support": { + "issues": "https://github.com/laravel/pao/issues", + "source": "https://github.com/laravel/pao" + }, + "time": "2026-06-22T19:58:00+00:00" + }, + { + "name": "laravel/pint", + "version": "v1.29.3", + "source": { + "type": "git", + "url": "https://github.com/laravel/pint.git", + "reference": "da1d1111a6aa2e082d2a388b194afe1ba0a05d14" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/pint/zipball/da1d1111a6aa2e082d2a388b194afe1ba0a05d14", + "reference": "da1d1111a6aa2e082d2a388b194afe1ba0a05d14", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "ext-tokenizer": "*", + "ext-xml": "*", + "php": "^8.2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.95.8", + "illuminate/view": "^12.62.0", + "larastan/larastan": "^3.10.0", + "laravel-zero/framework": "^12.1.0", + "laravel/agent-detector": "^2.0.2", + "mockery/mockery": "^1.6.12", + "nunomaduro/termwind": "^2.4.0", + "pestphp/pest": "^3.8.6" + }, + "bin": [ + "builds/pint" + ], + "type": "project", + "autoload": { + "psr-4": { + "App\\": "app/", + "Database\\Seeders\\": "database/seeders/", + "Database\\Factories\\": "database/factories/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "An opinionated code formatter for PHP.", + "homepage": "https://laravel.com", + "keywords": [ + "dev", + "format", + "formatter", + "lint", + "linter", + "php" + ], + "support": { + "issues": "https://github.com/laravel/pint/issues", + "source": "https://github.com/laravel/pint" + }, + "time": "2026-06-16T15:34:04+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.6.12", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": ">=7.3" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" + }, + "type": "library", + "autoload": { + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "https://github.com/padraic", + "role": "Author" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" + } + ], + "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": { + "docs": "https://docs.mockery.io/", + "issues": "https://github.com/mockery/mockery/issues", + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" + }, + "time": "2024-05-16T03:13:13+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "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", + "phpspec/prophecy": "^1.10", + "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.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nunomaduro/collision", + "version": "v8.9.5", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/collision.git", + "reference": "fb53eacd509a1d303858e2d20cfebf2d630254ec" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/fb53eacd509a1d303858e2d20cfebf2d630254ec", + "reference": "fb53eacd509a1d303858e2d20cfebf2d630254ec", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.18.4", + "nunomaduro/termwind": "^2.4.0", + "php": "^8.2.0", + "symfony/console": "^7.4.14 || ^8.1.1" + }, + "conflict": { + "laravel/framework": "<11.48.0 || >=14.0.0", + "phpunit/phpunit": "<11.5.50 || >=14.0.0" + }, + "require-dev": { + "brianium/paratest": "^7.8.5", + "larastan/larastan": "^3.10.0", + "laravel/framework": "^11.48.0 || ^12.56.0 || ^13.20.0", + "laravel/pint": "^1.29.3", + "orchestra/testbench-core": "^9.12.0 || ^10.12.1 || ^11.3.5", + "pestphp/pest": "^3.8.5 || ^4.7.5 || ^5.0.0", + "sebastian/environment": "^7.2.1 || ^8.1.2 || ^9.3.2" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" + ] + }, + "branch-alias": { + "dev-8.x": "8.x-dev" + } + }, + "autoload": { + "files": [ + "./src/Adapters/Phpunit/Autoload.php" + ], + "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", + "dev", + "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": "2026-07-15T19:09:14+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "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.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+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": "12.5.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "186dab580576598076de6818596d12b61801880e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/186dab580576598076de6818596d12b61801880e", + "reference": "186dab580576598076de6818596d12b61801880e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.7.0", + "php": ">=8.3", + "phpunit/php-text-template": "^5.0", + "sebastian/complexity": "^5.0", + "sebastian/environment": "^8.1.2", + "sebastian/lines-of-code": "^4.0.1", + "sebastian/version": "^6.0", + "theseer/tokenizer": "^2.0.1" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.28" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "12.5.x-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", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.7" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" + } + ], + "time": "2026-06-01T13:24:19+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5", + "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.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", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" + } + ], + "time": "2026-02-02T14:04:18+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^12.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.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": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:58:58+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/e1367a453f0eda562eedb4f659e13aa900d66c53", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "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": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:59:16+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "8.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.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", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:59:38+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "12.5.31", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "0608d157a284f15cc73b99a3327eff06b66a176d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0608d157a284f15cc73b99a3327eff06b66a176d", + "reference": "0608d157a284f15cc73b99a3327eff06b66a176d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-filter": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.3", + "phpunit/php-code-coverage": "^12.5.7", + "phpunit/php-file-iterator": "^6.0.1", + "phpunit/php-invoker": "^6.0.0", + "phpunit/php-text-template": "^5.0.0", + "phpunit/php-timer": "^8.0.0", + "sebastian/cli-parser": "^4.2.1", + "sebastian/comparator": "^7.1.8", + "sebastian/diff": "^7.0.0", + "sebastian/environment": "^8.1.2", + "sebastian/exporter": "^7.0.3", + "sebastian/global-state": "^8.0.3", + "sebastian/object-enumerator": "^7.0.0", + "sebastian/recursion-context": "^7.0.1", + "sebastian/type": "^6.0.4", + "sebastian/version": "^6.0.0", + "staabm/side-effects-detector": "^1.0.5" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "12.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", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.31" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsoring.html", + "type": "other" + } + ], + "time": "2026-07-06T14:54:16+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "4.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "7d05781b13f7dec9043a629a21d086ed74582a15" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/7d05781b13f7dec9043a629a21d086ed74582a15", + "reference": "7d05781b13f7dec9043a629a21d086ed74582a15", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.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 for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser", + "type": "tidelift" + } + ], + "time": "2026-05-17T05:29:34+00:00" + }, + { + "name": "sebastian/comparator", + "version": "7.1.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "7c65c1e79836812819705b473a90c12399542485" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/7c65c1e79836812819705b473a90c12399542485", + "reference": "7c65c1e79836812819705b473a90c12399542485", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/diff": "^7.0", + "sebastian/exporter": "^7.0.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.25" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.1-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", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-05-21T04:45:25+00:00" + }, + { + "name": "sebastian/complexity", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "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": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:55:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0", + "symfony/process": "^7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.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", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:55:46+00:00" + }, + { + "name": "sebastian/environment", + "version": "8.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "9d32c685773823b1983e256ae4ecd48a10d6e439" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/9d32c685773823b1983e256ae4ecd48a10d6e439", + "reference": "9d32c685773823b1983e256ae4ecd48a10d6e439", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.26" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.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": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/8.1.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" + } + ], + "time": "2026-05-25T13:40:20+00:00" + }, + { + "name": "sebastian/exporter", + "version": "7.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23", + "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/recursion-context": "^7.0.1" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.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", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2026-05-20T04:37:17+00:00" + }, + { + "name": "sebastian/global-state", + "version": "8.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "b164d3274d6537ab462591c5755f76a8f5b1aae9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/b164d3274d6537ab462591c5755f76a8f5b1aae9", + "reference": "b164d3274d6537ab462591c5755f76a8f5b1aae9", + "shasum": "" + }, + "require": { + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0.1" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^12.5.28" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.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": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" + } + ], + "time": "2026-06-01T15:10:33+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d543b8ef219dcd8da262cbb958639a96bedba10e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d543b8ef219dcd8da262cbb958639a96bedba10e", + "reference": "d543b8ef219dcd8da262cbb958639a96bedba10e", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.7.0", + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.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", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/lines-of-code", + "type": "tidelift" + } + ], + "time": "2026-05-19T16:22:07+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "shasum": "" + }, + "require": { + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.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", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:57:48+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "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": "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", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:58:17+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.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": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-13T04:44:59+00:00" + }, + { + "name": "sebastian/type", + "version": "6.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "82ff822c2edc46724be9f7411d3163021f602773" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/82ff822c2edc46724be9f7411d3163021f602773", + "reference": "82ff822c2edc46724be9f7411d3163021f602773", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.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 types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/6.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" + } + ], + "time": "2026-05-20T06:45:45+00:00" + }, + { + "name": "sebastian/version", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.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", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T05:00:38+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/7989e43bf381af0eac72e4f0ca5bcbfa81658be4", + "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^8.1" + }, + "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/2.0.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-12-08T11:19:18+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": "^8.3" + }, + "platform-dev": {}, + "plugin-api-version": "2.6.0" +} diff --git a/config/app.php b/config/app.php new file mode 100644 index 0000000..423eed5 --- /dev/null +++ b/config/app.php @@ -0,0 +1,126 @@ + env('APP_NAME', 'Laravel'), + + /* + |-------------------------------------------------------------------------- + | Application Environment + |-------------------------------------------------------------------------- + | + | This value determines the "environment" your application is currently + | running in. This may determine how you prefer to configure various + | services the application utilizes. Set this in your ".env" file. + | + */ + + 'env' => env('APP_ENV', 'production'), + + /* + |-------------------------------------------------------------------------- + | Application Debug Mode + |-------------------------------------------------------------------------- + | + | When your application is in debug mode, detailed error messages with + | stack traces will be shown on every error that occurs within your + | application. If disabled, a simple generic error page is shown. + | + */ + + 'debug' => (bool) env('APP_DEBUG', false), + + /* + |-------------------------------------------------------------------------- + | Application URL + |-------------------------------------------------------------------------- + | + | This URL is used by the console to properly generate URLs when using + | the Artisan command line tool. You should set this to the root of + | the application so that it's available within Artisan commands. + | + */ + + 'url' => env('APP_URL', 'http://localhost'), + + /* + |-------------------------------------------------------------------------- + | Application Timezone + |-------------------------------------------------------------------------- + | + | Here you may specify the default timezone for your application, which + | will be used by the PHP date and date-time functions. The timezone + | is set to "UTC" by default as it is suitable for most use cases. + | + */ + + 'timezone' => 'UTC', + + /* + |-------------------------------------------------------------------------- + | Application Locale Configuration + |-------------------------------------------------------------------------- + | + | The application locale determines the default locale that will be used + | by Laravel's translation / localization methods. This option can be + | set to any locale for which you plan to have translation strings. + | + */ + + 'locale' => env('APP_LOCALE', 'en'), + + 'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'), + + 'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'), + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is utilized by Laravel's encryption services and should be set + | to a random, 32 character string to ensure that all encrypted values + | are secure. You should do this prior to deploying the application. + | + */ + + 'cipher' => 'AES-256-CBC', + + 'key' => env('APP_KEY'), + + 'previous_keys' => [ + ...array_filter( + explode(',', (string) env('APP_PREVIOUS_KEYS', '')) + ), + ], + + /* + |-------------------------------------------------------------------------- + | Maintenance Mode Driver + |-------------------------------------------------------------------------- + | + | These configuration options determine the driver used to determine and + | manage Laravel's "maintenance mode" status. The "cache" driver will + | allow maintenance mode to be controlled across multiple machines. + | + | Supported drivers: "file", "cache" + | + */ + + 'maintenance' => [ + 'driver' => env('APP_MAINTENANCE_DRIVER', 'file'), + 'store' => env('APP_MAINTENANCE_STORE', 'database'), + ], + +]; diff --git a/config/auth.php b/config/auth.php new file mode 100644 index 0000000..24d8311 --- /dev/null +++ b/config/auth.php @@ -0,0 +1,122 @@ + [ + 'guard' => env('AUTH_GUARD', 'web'), + 'passwords' => env('AUTH_PASSWORD_BROKER', 'users'), + ], + + /* + |-------------------------------------------------------------------------- + | Authentication Guards + |-------------------------------------------------------------------------- + | + | Next, you may define every authentication guard for your application. + | Of course, a great default configuration has been defined for you + | which utilizes session storage plus the Eloquent user provider. + | + | All authentication guards have a user provider, which defines how the + | users are actually retrieved out of your database or other storage + | system used by the application. Typically, Eloquent is utilized. + | + | Supported: "session" + | + */ + + 'guards' => [ + 'web' => [ + 'driver' => 'session', + 'provider' => 'users', + ], + + 'api' => [ + 'driver' => 'passport', + 'provider' => 'users', + ], + ], + + /* + |-------------------------------------------------------------------------- + | User Providers + |-------------------------------------------------------------------------- + | + | All authentication guards have a user provider, which defines how the + | users are actually retrieved out of your database or other storage + | system used by the application. Typically, Eloquent is utilized. + | + | If you have multiple user tables or models you may configure multiple + | providers to represent the model / table. These providers may then + | be assigned to any extra authentication guards you have defined. + | + | Supported: "database", "eloquent" + | + */ + + 'providers' => [ + 'users' => [ + 'driver' => 'eloquent', + 'model' => env('AUTH_MODEL', User::class), + ], + + // 'users' => [ + // 'driver' => 'database', + // 'table' => 'users', + // ], + ], + + /* + |-------------------------------------------------------------------------- + | Resetting Passwords + |-------------------------------------------------------------------------- + | + | These configuration options specify the behavior of Laravel's password + | reset functionality, including the table utilized for token storage + | and the user provider that is invoked to actually retrieve users. + | + | The expiry time is the number of minutes that each reset token will be + | considered valid. This security feature keeps tokens short-lived so + | they have less time to be guessed. You may change this as needed. + | + | The throttle setting is the number of seconds a user must wait before + | generating more password reset tokens. This prevents the user from + | quickly generating a very large amount of password reset tokens. + | + */ + + 'passwords' => [ + 'users' => [ + 'provider' => 'users', + 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), + 'expire' => 60, + 'throttle' => 60, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Password Confirmation Timeout + |-------------------------------------------------------------------------- + | + | Here you may define the number of seconds before a password confirmation + | window expires and users are asked to re-enter their password via the + | confirmation screen. By default, the timeout lasts for three hours. + | + */ + + 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800), + +]; diff --git a/config/cache.php b/config/cache.php new file mode 100644 index 0000000..d7eec61 --- /dev/null +++ b/config/cache.php @@ -0,0 +1,136 @@ + env('CACHE_STORE', 'database'), + + /* + |-------------------------------------------------------------------------- + | Cache Stores + |-------------------------------------------------------------------------- + | + | Here you may define all of the cache "stores" for your application as + | well as their drivers. You may even define multiple stores for the + | same cache driver to group types of items stored in your caches. + | + | Supported drivers: "array", "database", "file", "memcached", + | "redis", "dynamodb", "storage", "octane", + | "session", "failover", "null" + | + */ + + 'stores' => [ + + 'array' => [ + 'driver' => 'array', + 'serialize' => false, + ], + + 'database' => [ + 'driver' => 'database', + 'connection' => env('DB_CACHE_CONNECTION'), + 'table' => env('DB_CACHE_TABLE', 'cache'), + 'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'), + 'lock_table' => env('DB_CACHE_LOCK_TABLE'), + ], + + 'file' => [ + 'driver' => 'file', + 'path' => storage_path('framework/cache/data'), + 'lock_path' => storage_path('framework/cache/data'), + ], + + 'storage' => [ + 'driver' => 'storage', + 'disk' => env('CACHE_STORAGE_DISK'), + 'path' => env('CACHE_STORAGE_PATH', 'framework/cache/data'), + ], + + 'memcached' => [ + 'driver' => 'memcached', + 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), + 'sasl' => [ + env('MEMCACHED_USERNAME'), + env('MEMCACHED_PASSWORD'), + ], + 'options' => [ + // Memcached::OPT_CONNECT_TIMEOUT => 2000, + ], + 'servers' => [ + [ + 'host' => env('MEMCACHED_HOST', '127.0.0.1'), + 'port' => env('MEMCACHED_PORT', 11211), + 'weight' => 100, + ], + ], + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => env('REDIS_CACHE_CONNECTION', 'cache'), + 'lock_connection' => env('REDIS_CACHE_LOCK_CONNECTION', 'default'), + ], + + 'dynamodb' => [ + 'driver' => 'dynamodb', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), + 'endpoint' => env('DYNAMODB_ENDPOINT'), + ], + + 'octane' => [ + 'driver' => 'octane', + ], + + 'failover' => [ + 'driver' => 'failover', + 'stores' => [ + 'database', + 'array', + ], + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Cache Key Prefix + |-------------------------------------------------------------------------- + | + | When utilizing the APC, database, memcached, Redis, and DynamoDB cache + | stores, there might be other applications using the same cache. For + | that reason, you may prefix every cache key to avoid collisions. + | + */ + + 'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-cache-'), + + /* + |-------------------------------------------------------------------------- + | Serializable Classes + |-------------------------------------------------------------------------- + | + | This value determines the classes that can be unserialized from cache + | storage. By default, no PHP classes will be unserialized from your + | cache to prevent gadget chain attacks if your APP_KEY is leaked. + | + */ + + 'serializable_classes' => false, + +]; diff --git a/config/database.php b/config/database.php new file mode 100644 index 0000000..abbb88e --- /dev/null +++ b/config/database.php @@ -0,0 +1,184 @@ + env('DB_CONNECTION', 'sqlite'), + + /* + |-------------------------------------------------------------------------- + | Database Connections + |-------------------------------------------------------------------------- + | + | Below are all of the database connections defined for your application. + | An example configuration is provided for each database system which + | is supported by Laravel. You're free to add / remove connections. + | + */ + + 'connections' => [ + + 'sqlite' => [ + 'driver' => 'sqlite', + 'url' => env('DB_URL'), + 'database' => env('DB_DATABASE', database_path('database.sqlite')), + 'prefix' => '', + 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), + 'busy_timeout' => null, + 'journal_mode' => null, + 'synchronous' => null, + 'transaction_mode' => 'DEFERRED', + ], + + 'mysql' => [ + 'driver' => 'mysql', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + Mysql::ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + 'mariadb' => [ + 'driver' => 'mariadb', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + Mysql::ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + 'pgsql' => [ + 'driver' => 'pgsql', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => env('DB_CHARSET', 'utf8'), + 'prefix' => '', + 'prefix_indexes' => true, + 'search_path' => 'public', + 'sslmode' => env('DB_SSLMODE', 'prefer'), + ], + + 'sqlsrv' => [ + 'driver' => 'sqlsrv', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', '1433'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => env('DB_CHARSET', 'utf8'), + 'prefix' => '', + 'prefix_indexes' => true, + // 'encrypt' => env('DB_ENCRYPT', 'yes'), + // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'), + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Migration Repository Table + |-------------------------------------------------------------------------- + | + | This table keeps track of all the migrations that have already run for + | your application. Using this information, we can determine which of + | the migrations on disk haven't actually been run on the database. + | + */ + + 'migrations' => [ + 'table' => 'migrations', + 'update_date_on_publish' => true, + ], + + /* + |-------------------------------------------------------------------------- + | Redis Databases + |-------------------------------------------------------------------------- + | + | Redis is an open source, fast, and advanced key-value store that also + | provides a richer body of commands than a typical key-value system + | such as Memcached. You may define your connection settings here. + | + */ + + 'redis' => [ + + 'client' => env('REDIS_CLIENT', 'phpredis'), + + 'options' => [ + 'cluster' => env('REDIS_CLUSTER', 'redis'), + 'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'), + 'persistent' => env('REDIS_PERSISTENT', false), + ], + + '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'), + 'max_retries' => env('REDIS_MAX_RETRIES', 3), + 'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'), + 'backoff_base' => env('REDIS_BACKOFF_BASE', 100), + 'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000), + ], + + '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'), + 'max_retries' => env('REDIS_MAX_RETRIES', 3), + 'backoff_algorithm' => env('REDIS_BACKOFF_ALGORITHM', 'decorrelated_jitter'), + 'backoff_base' => env('REDIS_BACKOFF_BASE', 100), + 'backoff_cap' => env('REDIS_BACKOFF_CAP', 1000), + ], + + ], + +]; diff --git a/config/filesystems.php b/config/filesystems.php new file mode 100644 index 0000000..37d8fca --- /dev/null +++ b/config/filesystems.php @@ -0,0 +1,80 @@ + env('FILESYSTEM_DISK', 'local'), + + /* + |-------------------------------------------------------------------------- + | Filesystem Disks + |-------------------------------------------------------------------------- + | + | Below you may configure as many filesystem disks as necessary, and you + | may even configure multiple disks for the same driver. Examples for + | most supported storage drivers are configured here for reference. + | + | Supported drivers: "local", "ftp", "sftp", "s3" + | + */ + + 'disks' => [ + + 'local' => [ + 'driver' => 'local', + 'root' => storage_path('app/private'), + 'serve' => true, + 'throw' => false, + 'report' => false, + ], + + 'public' => [ + 'driver' => 'local', + 'root' => storage_path('app/public'), + 'url' => rtrim(env('APP_URL', 'http://localhost'), '/').'/storage', + 'visibility' => 'public', + 'throw' => false, + 'report' => 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, + 'report' => 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/l5-swagger.php b/config/l5-swagger.php new file mode 100644 index 0000000..ff4c8a4 --- /dev/null +++ b/config/l5-swagger.php @@ -0,0 +1,333 @@ + 'default', + 'documentations' => [ + 'default' => [ + 'api' => [ + 'title' => 'L5 Swagger UI', + ], + + 'routes' => [ + /* + * Route for accessing api documentation interface + */ + 'api' => 'api/documentation', + ], + 'paths' => [ + /* + * Edit to include full URL in ui for assets + */ + 'use_absolute_path' => env('L5_SWAGGER_USE_ABSOLUTE_PATH', true), + + /* + * Edit to set path where swagger ui assets should be stored + */ + 'swagger_ui_assets_path' => env('L5_SWAGGER_UI_ASSETS_PATH', 'vendor/swagger-api/swagger-ui/dist/'), + + /* + * File name of the generated json documentation file + */ + 'docs_json' => 'api-docs.json', + + /* + * File name of the generated YAML documentation file + */ + 'docs_yaml' => 'api-docs.yaml', + + /* + * Set this to `json` or `yaml` to determine which documentation file to use in UI + */ + 'format_to_use_for_docs' => env('L5_FORMAT_TO_USE_FOR_DOCS', 'json'), + + /* + * Absolute paths to directory containing the swagger annotations are stored. + */ + 'annotations' => [ + base_path('app'), + ], + ], + ], + ], + 'defaults' => [ + 'routes' => [ + /* + * Route for accessing parsed swagger annotations. + */ + 'docs' => 'docs', + + /* + * Route for Oauth2 authentication callback. + */ + 'oauth2_callback' => 'api/oauth2-callback', + + /* + * Middleware allows to prevent unexpected access to API documentation + */ + 'middleware' => [ + 'api' => [], + 'asset' => [], + 'docs' => [], + 'oauth2_callback' => [], + ], + + /* + * Route Group options + */ + 'group_options' => [], + ], + + 'paths' => [ + /* + * Absolute path to location where parsed annotations will be stored + */ + 'docs' => storage_path('api-docs'), + + /* + * Absolute path to directory where to export views + */ + 'views' => base_path('resources/views/vendor/l5-swagger'), + + /* + * Edit to set the api's base path + */ + 'base' => env('L5_SWAGGER_BASE_PATH', null), + + /* + * Absolute path to directories that should be excluded from scanning + * @deprecated Please use `scanOptions.exclude` + * `scanOptions.exclude` overwrites this + */ + 'excludes' => [], + ], + + 'scanOptions' => [ + /** + * Optional CustomGeneratorInterface implementation that creates an OpenApi\Generator instance. + * Use this to provide a custom pre-configured generator. + * Accepts an instance or a class name (FQCN) implementing the interface. + * + * @see \L5Swagger\CustomGeneratorInterface + */ + 'generator_factory' => null, + + /** + * Configuration for default processors. Allows to pass processors configuration to swagger-php. + * + * @link https://zircote.github.io/swagger-php/reference/processors.html + */ + 'default_processors_configuration' => [ + /** Example */ + /** + * 'operationId.hash' => true, + * 'pathFilter' => [ + * 'tags' => [ + * '/pets/', + * '/store/', + * ], + * ],. + */ + ], + + /** + * analyser: defaults to \OpenApi\StaticAnalyser . + * + * @see \OpenApi\scan + */ + 'analyser' => null, + + /** + * analysis: defaults to a new \OpenApi\Analysis . + * + * @see \OpenApi\scan + */ + 'analysis' => null, + + /** + * Custom processors. + * + * Each entry can be: + * - A class name or instance (inserted after BuildPaths by default) + * - An array with 'class' and 'after' keys for precise positioning: + * ['class' => MyProcessor::class, 'after' => SomeProcessor::class] + * + * @link https://github.com/zircote/swagger-php/tree/master/Examples/processors/schema-query-parameter + * @see \OpenApi\scan + */ + 'processors' => [ + // \App\SwaggerProcessors\SchemaQueryParameter::class, + // ['class' => \App\SwaggerProcessors\Custom::class, 'after' => \OpenApi\Processors\AugmentSchemas::class], + ], + + /** + * pattern: string $pattern File pattern(s) to scan (default: *.php) . + * + * @see \OpenApi\scan + */ + 'pattern' => null, + + /* + * Absolute path to directories that should be excluded from scanning + * @note This option overwrites `paths.excludes` + * @see \OpenApi\scan + */ + 'exclude' => [], + + /* + * Allows to generate specs either for OpenAPI 3.0.0 or OpenAPI 3.1.0. + * By default the spec will be in version 3.0.0 + */ + 'open_api_spec_version' => env('L5_SWAGGER_OPEN_API_SPEC_VERSION', \L5Swagger\Generator::OPEN_API_DEFAULT_SPEC_VERSION), + ], + + /* + * API security definitions. Will be generated into documentation file. + */ + 'securityDefinitions' => [ + 'securitySchemes' => [ + /* + * Examples of Security schemes + */ + /* + 'api_key_security_example' => [ // Unique name of security + 'type' => 'apiKey', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2". + 'description' => 'A short description for security scheme', + 'name' => 'api_key', // The name of the header or query parameter to be used. + 'in' => 'header', // The location of the API key. Valid values are "query" or "header". + ], + 'oauth2_security_example' => [ // Unique name of security + 'type' => 'oauth2', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2". + 'description' => 'A short description for oauth2 security scheme.', + 'flow' => 'implicit', // The flow used by the OAuth2 security scheme. Valid values are "implicit", "password", "application" or "accessCode". + 'authorizationUrl' => 'http://example.com/auth', // The authorization URL to be used for (implicit/accessCode) + //'tokenUrl' => 'http://example.com/auth' // The authorization URL to be used for (password/application/accessCode) + 'scopes' => [ + 'read:projects' => 'read your projects', + 'write:projects' => 'modify projects in your account', + ] + ], + */ + + /* Open API 3.0 support + 'passport' => [ // Unique name of security + 'type' => 'oauth2', // The type of the security scheme. Valid values are "basic", "apiKey" or "oauth2". + 'description' => 'Laravel passport oauth2 security.', + 'in' => 'header', + 'scheme' => 'https', + 'flows' => [ + "password" => [ + "authorizationUrl" => config('app.url') . '/oauth/authorize', + "tokenUrl" => config('app.url') . '/oauth/token', + "refreshUrl" => config('app.url') . '/token/refresh', + "scopes" => [] + ], + ], + ], + 'sanctum' => [ // Unique name of security + 'type' => 'apiKey', // Valid values are "basic", "apiKey" or "oauth2". + 'description' => 'Enter token in format (Bearer )', + 'name' => 'Authorization', // The name of the header or query parameter to be used. + 'in' => 'header', // The location of the API key. Valid values are "query" or "header". + ], + */ + ], + 'security' => [ + /* + * Examples of Securities + */ + [ + /* + 'oauth2_security_example' => [ + 'read', + 'write' + ], + + 'passport' => [] + */ + ], + ], + ], + + /* + * Set this to `true` in development mode so that docs would be regenerated on each request + * Set this to `false` to disable swagger generation on production + */ + 'generate_always' => env('L5_SWAGGER_GENERATE_ALWAYS', false), + + /* + * Set this to `true` to generate a copy of documentation in yaml format + */ + 'generate_yaml_copy' => env('L5_SWAGGER_GENERATE_YAML_COPY', false), + + /* + * Edit to trust the proxy's ip address - needed for AWS Load Balancer + * string[] + */ + 'proxy' => false, + + /* + * Configs plugin allows to fetch external configs instead of passing them to SwaggerUIBundle. + * See more at: https://github.com/swagger-api/swagger-ui#configs-plugin + */ + 'additional_config_url' => null, + + /* + * Apply a sort to the operation list of each API. It can be 'alpha' (sort by paths alphanumerically), + * 'method' (sort by HTTP method). + * Default is the order returned by the server unchanged. + */ + 'operations_sort' => env('L5_SWAGGER_OPERATIONS_SORT', null), + + /* + * Pass the validatorUrl parameter to SwaggerUi init on the JS side. + * A null value here disables validation. + */ + 'validator_url' => null, + + /* + * Swagger UI configuration parameters + */ + 'ui' => [ + 'display' => [ + 'dark_mode' => env('L5_SWAGGER_UI_DARK_MODE', false), + /* + * Controls the default expansion setting for the operations and tags. It can be : + * 'list' (expands only the tags), + * 'full' (expands the tags and operations), + * 'none' (expands nothing). + */ + 'doc_expansion' => env('L5_SWAGGER_UI_DOC_EXPANSION', 'none'), + + /** + * If set, enables filtering. The top bar will show an edit box that + * you can use to filter the tagged operations that are shown. Can be + * Boolean to enable or disable, or a string, in which case filtering + * will be enabled using that string as the filter expression. Filtering + * is case-sensitive matching the filter expression anywhere inside + * the tag. + */ + 'filter' => env('L5_SWAGGER_UI_FILTERS', true), // true | false + ], + + 'authorization' => [ + /* + * If set to true, it persists authorization data, and it would not be lost on browser close/refresh + */ + 'persist_authorization' => env('L5_SWAGGER_UI_PERSIST_AUTHORIZATION', false), + + 'oauth2' => [ + /* + * If set to true, adds PKCE to AuthorizationCodeGrant flow + */ + 'use_pkce_with_authorization_code_grant' => false, + ], + ], + ], + /* + * Constants which can be used in annotations + */ + 'constants' => [ + 'L5_SWAGGER_CONST_HOST' => env('L5_SWAGGER_CONST_HOST', 'http://my-default-host.com'), + ], + ], +]; diff --git a/config/logging.php b/config/logging.php new file mode 100644 index 0000000..b09cb25 --- /dev/null +++ b/config/logging.php @@ -0,0 +1,132 @@ + env('LOG_CHANNEL', 'stack'), + + /* + |-------------------------------------------------------------------------- + | Deprecations Log Channel + |-------------------------------------------------------------------------- + | + | This option controls the log channel that should be used to log warnings + | regarding deprecated PHP and library features. This allows you to get + | your application ready for upcoming major versions of dependencies. + | + */ + + 'deprecations' => [ + 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), + 'trace' => env('LOG_DEPRECATIONS_TRACE', false), + ], + + /* + |-------------------------------------------------------------------------- + | Log Channels + |-------------------------------------------------------------------------- + | + | Here you may configure the log channels for your application. Laravel + | utilizes the Monolog PHP logging library, which includes a variety + | of powerful log handlers and formatters that you're free to use. + | + | Available drivers: "single", "daily", "slack", "syslog", + | "errorlog", "monolog", "custom", "stack" + | + */ + + 'channels' => [ + + 'stack' => [ + 'driver' => 'stack', + 'channels' => explode(',', (string) env('LOG_STACK', 'single')), + 'ignore_exceptions' => false, + ], + + 'single' => [ + 'driver' => 'single', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'replace_placeholders' => true, + ], + + 'daily' => [ + 'driver' => 'daily', + 'path' => storage_path('logs/laravel.log'), + 'level' => env('LOG_LEVEL', 'debug'), + 'days' => env('LOG_DAILY_DAYS', 14), + 'replace_placeholders' => true, + ], + + 'slack' => [ + 'driver' => 'slack', + 'url' => env('LOG_SLACK_WEBHOOK_URL'), + 'username' => env('LOG_SLACK_USERNAME', env('APP_NAME', 'Laravel')), + 'emoji' => env('LOG_SLACK_EMOJI', ':boom:'), + 'level' => env('LOG_LEVEL', 'critical'), + 'replace_placeholders' => true, + ], + + 'papertrail' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class), + 'handler_with' => [ + 'host' => env('PAPERTRAIL_URL'), + 'port' => env('PAPERTRAIL_PORT'), + 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'), + ], + 'processors' => [PsrLogMessageProcessor::class], + ], + + 'stderr' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => StreamHandler::class, + 'handler_with' => [ + 'stream' => 'php://stderr', + ], + 'formatter' => env('LOG_STDERR_FORMATTER'), + 'processors' => [PsrLogMessageProcessor::class], + ], + + 'syslog' => [ + 'driver' => 'syslog', + 'level' => env('LOG_LEVEL', 'debug'), + 'facility' => env('LOG_SYSLOG_FACILITY', LOG_USER), + 'replace_placeholders' => true, + ], + + 'errorlog' => [ + 'driver' => 'errorlog', + 'level' => env('LOG_LEVEL', 'debug'), + 'replace_placeholders' => true, + ], + + 'null' => [ + 'driver' => 'monolog', + 'handler' => NullHandler::class, + ], + + 'emergency' => [ + 'path' => storage_path('logs/laravel.log'), + ], + + ], + +]; diff --git a/config/mail.php b/config/mail.php new file mode 100644 index 0000000..e32e88d --- /dev/null +++ b/config/mail.php @@ -0,0 +1,118 @@ + env('MAIL_MAILER', 'log'), + + /* + |-------------------------------------------------------------------------- + | Mailer Configurations + |-------------------------------------------------------------------------- + | + | Here you may configure all of the mailers used by your application plus + | their respective settings. Several examples have been configured for + | you and you are free to add your own as your application requires. + | + | Laravel supports a variety of mail "transport" drivers that can be used + | when delivering an email. You may specify which one you're using for + | your mailers below. You may also add additional mailers if needed. + | + | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2", + | "postmark", "resend", "log", "array", + | "failover", "roundrobin" + | + */ + + 'mailers' => [ + + 'smtp' => [ + 'transport' => 'smtp', + 'scheme' => env('MAIL_SCHEME'), + 'url' => env('MAIL_URL'), + 'host' => env('MAIL_HOST', '127.0.0.1'), + 'port' => env('MAIL_PORT', 2525), + 'username' => env('MAIL_USERNAME'), + 'password' => env('MAIL_PASSWORD'), + 'timeout' => null, + 'local_domain' => env('MAIL_EHLO_DOMAIN', parse_url((string) env('APP_URL', 'http://localhost'), PHP_URL_HOST)), + ], + + 'ses' => [ + 'transport' => 'ses', + ], + + 'postmark' => [ + 'transport' => 'postmark', + // 'message_stream_id' => env('POSTMARK_MESSAGE_STREAM_ID'), + // 'client' => [ + // 'timeout' => 5, + // ], + ], + + 'resend' => [ + 'transport' => 'resend', + ], + + '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', + ], + 'retry_after' => 60, + ], + + 'roundrobin' => [ + 'transport' => 'roundrobin', + 'mailers' => [ + 'ses', + 'postmark', + ], + 'retry_after' => 60, + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Global "From" Address + |-------------------------------------------------------------------------- + | + | You may wish for all emails sent by your application to be sent from + | the same address. Here you may specify a name and address that is + | used globally for all emails that are sent by your application. + | + */ + + 'from' => [ + 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), + 'name' => env('MAIL_FROM_NAME', env('APP_NAME', 'Laravel')), + ], + +]; diff --git a/config/passport.php b/config/passport.php new file mode 100644 index 0000000..aed4358 --- /dev/null +++ b/config/passport.php @@ -0,0 +1,48 @@ + 'web', + + 'middleware' => [], + + /* + |-------------------------------------------------------------------------- + | Encryption Keys + |-------------------------------------------------------------------------- + | + | Passport uses encryption keys while generating secure access tokens for + | your application. By default, the keys are stored as local files but + | can be set via environment variables when that is more convenient. + | + */ + + 'private_key' => env('PASSPORT_PRIVATE_KEY'), + + 'public_key' => env('PASSPORT_PUBLIC_KEY'), + + /* + |-------------------------------------------------------------------------- + | Passport Database Connection + |-------------------------------------------------------------------------- + | + | By default, Passport's models will utilize your application's default + | database connection. If you wish to use a different connection you + | may specify the configured name of the database connection here. + | + */ + + 'connection' => env('PASSPORT_CONNECTION'), + +]; diff --git a/config/permission.php b/config/permission.php new file mode 100644 index 0000000..8f1f452 --- /dev/null +++ b/config/permission.php @@ -0,0 +1,219 @@ + [ + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * Eloquent model should be used to retrieve your permissions. Of course, it + * is often just the "Permission" model but you may use whatever you like. + * + * The model you want to use as a Permission model needs to implement the + * `Spatie\Permission\Contracts\Permission` contract. + */ + + 'permission' => Permission::class, + + /* + * When using the "HasRoles" trait from this package, we need to know which + * Eloquent model should be used to retrieve your roles. Of course, it + * is often just the "Role" model but you may use whatever you like. + * + * The model you want to use as a Role model needs to implement the + * `Spatie\Permission\Contracts\Role` contract. + */ + + 'role' => Role::class, + + /* + * When using the "Teams" feature from this package, we need to know which + * Eloquent model should be used to retrieve your teams. Of course, it + * is often just the "Team" model but you may use whatever you like. + */ + 'team' => null, + + /* + * When using the "HasModels" trait and passing raw IDs to syncModels, + * attachModels, or detachModels, this model class will be used to + * resolve those IDs. If null, defaults to the guard's model. + */ + 'default_model' => null, + ], + + 'table_names' => [ + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'roles' => 'roles', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your permissions. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'permissions' => 'permissions', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your models permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_permissions' => 'model_has_permissions', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your models roles. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_roles' => 'model_has_roles', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'role_has_permissions' => 'role_has_permissions', + ], + + 'column_names' => [ + /* + * Change this if you want to name the related pivots other than defaults + */ + 'role_pivot_key' => null, // default 'role_id', + 'permission_pivot_key' => null, // default 'permission_id', + + /* + * Change this if you want to name the related model primary key other than + * `model_id`. + * + * For example, this would be nice if your primary keys are all UUIDs. In + * that case, name this `model_uuid`. + */ + + 'model_morph_key' => 'model_id', + + /* + * Change this if you want to use the teams feature and your related model's + * foreign key is other than `team_id`. + */ + + 'team_foreign_key' => 'team_id', + ], + + /* + * When set to true, the method for checking permissions will be registered on the gate. + * Set this to false if you want to implement custom logic for checking permissions. + */ + + 'register_permission_check_method' => true, + + /* + * When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered + * this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated + * NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it. + */ + 'register_octane_reset_listener' => false, + + /* + * Events will fire when a role or permission is assigned/unassigned: + * \Spatie\Permission\Events\RoleAttachedEvent + * \Spatie\Permission\Events\RoleDetachedEvent + * \Spatie\Permission\Events\PermissionAttachedEvent + * \Spatie\Permission\Events\PermissionDetachedEvent + * + * To enable, set to true, and then create listeners to watch these events. + */ + 'events_enabled' => false, + + /* + * Teams Feature. + * When set to true the package implements teams using the 'team_foreign_key'. + * If you want the migrations to register the 'team_foreign_key', you must + * set this to true before doing the migration. + * If you already did the migration then you must make a new migration to also + * add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions' + * (view the latest version of this package's migration file) + */ + + 'teams' => false, + + /* + * The class to use to resolve the permissions team id + */ + 'team_resolver' => DefaultTeamResolver::class, + + /* + * Passport Client Credentials Grant + * When set to true the package will use Passports Client to check permissions + */ + + 'use_passport_client_credentials' => false, + + /* + * When set to true, the required permission names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. + */ + + 'display_permission_in_exception' => false, + + /* + * When set to true, the required role names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. + */ + + 'display_role_in_exception' => false, + + /* + * By default wildcard permission lookups are disabled. + * See documentation to understand supported syntax. + */ + + 'enable_wildcard_permission' => false, + + /* + * The class to use for interpreting wildcard permissions. + * If you need to modify delimiters, override the class and specify its name here. + */ + // 'wildcard_permission' => Spatie\Permission\WildcardPermission::class, + + /* Cache-specific settings */ + + 'cache' => [ + + /* + * By default all permissions are cached for 24 hours to speed up performance. + * When permissions or roles are updated the cache is flushed automatically. + */ + + 'expiration_time' => DateInterval::createFromDateString('24 hours'), + + /* + * The cache key used to store all permissions. + */ + + 'key' => 'spatie.permission.cache', + + /* + * You may optionally indicate a specific cache driver to use for permission and + * role caching using any of the `store` drivers listed in the cache.php config + * file. Using 'default' here means to use the `default` set in cache.php. + */ + + 'store' => 'default', + ], +]; diff --git a/config/queue.php b/config/queue.php new file mode 100644 index 0000000..79c2c0a --- /dev/null +++ b/config/queue.php @@ -0,0 +1,129 @@ + env('QUEUE_CONNECTION', 'database'), + + /* + |-------------------------------------------------------------------------- + | Queue Connections + |-------------------------------------------------------------------------- + | + | Here you may configure the connection options for every queue backend + | used by your application. An example configuration is provided for + | each backend supported by Laravel. You're also free to add more. + | + | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", + | "deferred", "background", "failover", "null" + | + */ + + 'connections' => [ + + 'sync' => [ + 'driver' => 'sync', + ], + + 'database' => [ + 'driver' => 'database', + 'connection' => env('DB_QUEUE_CONNECTION'), + 'table' => env('DB_QUEUE_TABLE', 'jobs'), + 'queue' => env('DB_QUEUE', 'default'), + 'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90), + 'after_commit' => false, + ], + + 'beanstalkd' => [ + 'driver' => 'beanstalkd', + 'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'), + 'queue' => env('BEANSTALKD_QUEUE', 'default'), + 'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90), + 'block_for' => 0, + 'after_commit' => false, + ], + + 'sqs' => [ + 'driver' => 'sqs', + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), + 'queue' => env('SQS_QUEUE', 'default'), + 'suffix' => env('SQS_SUFFIX'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + 'after_commit' => false, + ], + + 'redis' => [ + 'driver' => 'redis', + 'connection' => env('REDIS_QUEUE_CONNECTION', 'default'), + 'queue' => env('REDIS_QUEUE', 'default'), + 'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90), + 'block_for' => null, + 'after_commit' => false, + ], + + 'deferred' => [ + 'driver' => 'deferred', + ], + + 'background' => [ + 'driver' => 'background', + ], + + 'failover' => [ + 'driver' => 'failover', + 'connections' => [ + 'database', + 'deferred', + ], + ], + + ], + + /* + |-------------------------------------------------------------------------- + | Job Batching + |-------------------------------------------------------------------------- + | + | The following options configure the database and table that store job + | batching information. These options can be updated to any database + | connection and table which has been defined by your application. + | + */ + + 'batching' => [ + 'database' => env('DB_CONNECTION', 'sqlite'), + 'table' => 'job_batches', + ], + + /* + |-------------------------------------------------------------------------- + | Failed Queue Jobs + |-------------------------------------------------------------------------- + | + | These options configure the behavior of failed queue job logging so you + | can control how and where failed jobs are stored. Laravel ships with + | support for storing failed jobs in a simple file or in a database. + | + | Supported drivers: "database-uuids", "dynamodb", "file", "null" + | + */ + + 'failed' => [ + 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), + 'database' => env('DB_CONNECTION', 'sqlite'), + 'table' => 'failed_jobs', + ], + +]; diff --git a/config/services.php b/config/services.php new file mode 100644 index 0000000..6a90eb8 --- /dev/null +++ b/config/services.php @@ -0,0 +1,38 @@ + [ + 'key' => env('POSTMARK_API_KEY'), + ], + + 'resend' => [ + 'key' => env('RESEND_API_KEY'), + ], + + 'ses' => [ + 'key' => env('AWS_ACCESS_KEY_ID'), + 'secret' => env('AWS_SECRET_ACCESS_KEY'), + 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), + ], + + 'slack' => [ + 'notifications' => [ + 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), + 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), + ], + ], + +]; diff --git a/config/session.php b/config/session.php new file mode 100644 index 0000000..f574482 --- /dev/null +++ b/config/session.php @@ -0,0 +1,233 @@ + env('SESSION_DRIVER', 'database'), + + /* + |-------------------------------------------------------------------------- + | Session Lifetime + |-------------------------------------------------------------------------- + | + | Here you may specify the number of minutes that you wish the session + | to be allowed to remain idle before it expires. If you want them + | to expire immediately when the browser is closed then you may + | indicate that via the expire_on_close configuration option. + | + */ + + 'lifetime' => (int) env('SESSION_LIFETIME', 120), + + 'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false), + + /* + |-------------------------------------------------------------------------- + | Session Encryption + |-------------------------------------------------------------------------- + | + | This option allows you to easily specify that all of your session data + | should be encrypted before it's stored. All encryption is performed + | automatically by Laravel and you may use the session like normal. + | + */ + + 'encrypt' => env('SESSION_ENCRYPT', false), + + /* + |-------------------------------------------------------------------------- + | Session File Location + |-------------------------------------------------------------------------- + | + | When utilizing the "file" session driver, the session files are placed + | on disk. The default storage location is defined here; however, you + | are free to provide another location where they should be stored. + | + */ + + 'files' => storage_path('framework/sessions'), + + /* + |-------------------------------------------------------------------------- + | Session Database Connection + |-------------------------------------------------------------------------- + | + | When using the "database" or "redis" session drivers, you may specify a + | connection that should be used to manage these sessions. This should + | correspond to a connection in your database configuration options. + | + */ + + 'connection' => env('SESSION_CONNECTION'), + + /* + |-------------------------------------------------------------------------- + | Session Database Table + |-------------------------------------------------------------------------- + | + | When using the "database" session driver, you may specify the table to + | be used to store sessions. Of course, a sensible default is defined + | for you; however, you're welcome to change this to another table. + | + */ + + 'table' => env('SESSION_TABLE', 'sessions'), + + /* + |-------------------------------------------------------------------------- + | Session Cache Store + |-------------------------------------------------------------------------- + | + | When using one of the framework's cache driven session backends, you may + | define the cache store which should be used to store the session data + | between requests. This must match one of your defined cache stores. + | + | Affects: "dynamodb", "memcached", "redis" + | + */ + + 'store' => env('SESSION_STORE'), + + /* + |-------------------------------------------------------------------------- + | Session Sweeping Lottery + |-------------------------------------------------------------------------- + | + | Some session drivers must manually sweep their storage location to get + | rid of old sessions from storage. Here are the chances that it will + | happen on a given request. By default, the odds are 2 out of 100. + | + */ + + 'lottery' => [2, 100], + + /* + |-------------------------------------------------------------------------- + | Session Cookie Name + |-------------------------------------------------------------------------- + | + | Here you may change the name of the session cookie that is created by + | the framework. Typically, you should not need to change this value + | since doing so does not grant a meaningful security improvement. + | + */ + + 'cookie' => env( + 'SESSION_COOKIE', + Str::slug((string) env('APP_NAME', 'laravel')).'-session' + ), + + /* + |-------------------------------------------------------------------------- + | Session Cookie Path + |-------------------------------------------------------------------------- + | + | The session cookie path determines the path for which the cookie will + | be regarded as available. Typically, this will be the root path of + | your application, but you're free to change this when necessary. + | + */ + + 'path' => env('SESSION_PATH', '/'), + + /* + |-------------------------------------------------------------------------- + | Session Cookie Domain + |-------------------------------------------------------------------------- + | + | This value determines the domain and subdomains the session cookie is + | available to. By default, the cookie will be available to the root + | domain without subdomains. Typically, this shouldn't be changed. + | + */ + + 'domain' => env('SESSION_DOMAIN'), + + /* + |-------------------------------------------------------------------------- + | HTTPS Only Cookies + |-------------------------------------------------------------------------- + | + | By setting this option to true, session cookies will only be sent back + | to the server if the browser has a HTTPS connection. This will keep + | the cookie from being sent to you when it can't be done securely. + | + */ + + 'secure' => env('SESSION_SECURE_COOKIE'), + + /* + |-------------------------------------------------------------------------- + | HTTP Access Only + |-------------------------------------------------------------------------- + | + | Setting this value to true will prevent JavaScript from accessing the + | value of the cookie and the cookie will only be accessible through + | the HTTP protocol. It's unlikely you should disable this option. + | + */ + + 'http_only' => env('SESSION_HTTP_ONLY', true), + + /* + |-------------------------------------------------------------------------- + | Same-Site Cookies + |-------------------------------------------------------------------------- + | + | This option determines how your cookies behave when cross-site requests + | take place, and can be used to mitigate CSRF attacks. By default, we + | will set this value to "lax" to permit secure cross-site requests. + | + | See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value + | + | Supported: "lax", "strict", "none", null + | + */ + + 'same_site' => env('SESSION_SAME_SITE', 'lax'), + + /* + |-------------------------------------------------------------------------- + | Partitioned Cookies + |-------------------------------------------------------------------------- + | + | Setting this value to true will tie the cookie to the top-level site for + | a cross-site context. Partitioned cookies are accepted by the browser + | when flagged "secure" and the Same-Site attribute is set to "none". + | + */ + + 'partitioned' => env('SESSION_PARTITIONED_COOKIE', false), + + /* + |-------------------------------------------------------------------------- + | Session Serialization + |-------------------------------------------------------------------------- + | + | This value controls the serialization strategy for session data, which + | is JSON by default. Setting this to "php" allows the storage of PHP + | objects in the session but can make an application vulnerable to + | "gadget chain" serialization attacks if the APP_KEY is leaked. + | + | Supported: "json", "php" + | + */ + + 'serialization' => 'json', + +]; 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..c4ceb07 --- /dev/null +++ b/database/factories/UserFactory.php @@ -0,0 +1,45 @@ + + */ +class UserFactory extends Factory +{ + /** + * The current password being used by the factory. + */ + protected static ?string $password; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => fake()->name(), + 'email' => fake()->unique()->safeEmail(), + 'email_verified_at' => now(), + 'password' => static::$password ??= Hash::make('password'), + 'remember_token' => Str::random(10), + ]; + } + + /** + * Indicate that the model's email address should be unverified. + */ + public function unverified(): static + { + return $this->state(fn (array $attributes) => [ + 'email_verified_at' => null, + ]); + } +} diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php new file mode 100644 index 0000000..05fb5d9 --- /dev/null +++ b/database/migrations/0001_01_01_000000_create_users_table.php @@ -0,0 +1,49 @@ +id(); + $table->string('name'); + $table->string('email')->unique(); + $table->timestamp('email_verified_at')->nullable(); + $table->string('password'); + $table->rememberToken(); + $table->timestamps(); + }); + + Schema::create('password_reset_tokens', function (Blueprint $table) { + $table->string('email')->primary(); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + }); + + Schema::create('sessions', function (Blueprint $table) { + $table->string('id')->primary(); + $table->foreignId('user_id')->nullable()->index(); + $table->string('ip_address', 45)->nullable(); + $table->text('user_agent')->nullable(); + $table->longText('payload'); + $table->integer('last_activity')->index(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('users'); + Schema::dropIfExists('password_reset_tokens'); + Schema::dropIfExists('sessions'); + } +}; diff --git a/database/migrations/0001_01_01_000001_create_cache_table.php b/database/migrations/0001_01_01_000001_create_cache_table.php new file mode 100644 index 0000000..06dc7a5 --- /dev/null +++ b/database/migrations/0001_01_01_000001_create_cache_table.php @@ -0,0 +1,35 @@ +string('key')->primary(); + $table->mediumText('value'); + $table->bigInteger('expiration')->index(); + }); + + Schema::create('cache_locks', function (Blueprint $table) { + $table->string('key')->primary(); + $table->string('owner'); + $table->bigInteger('expiration')->index(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('cache'); + Schema::dropIfExists('cache_locks'); + } +}; diff --git a/database/migrations/0001_01_01_000002_create_jobs_table.php b/database/migrations/0001_01_01_000002_create_jobs_table.php new file mode 100644 index 0000000..edac6fe --- /dev/null +++ b/database/migrations/0001_01_01_000002_create_jobs_table.php @@ -0,0 +1,59 @@ +id(); + $table->string('queue')->index(); + $table->longText('payload'); + $table->unsignedSmallInteger('attempts'); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + }); + + Schema::create('job_batches', function (Blueprint $table) { + $table->string('id')->primary(); + $table->string('name'); + $table->integer('total_jobs'); + $table->integer('pending_jobs'); + $table->integer('failed_jobs'); + $table->longText('failed_job_ids'); + $table->mediumText('options')->nullable(); + $table->integer('cancelled_at')->nullable(); + $table->integer('created_at'); + $table->integer('finished_at')->nullable(); + }); + + Schema::create('failed_jobs', function (Blueprint $table) { + $table->id(); + $table->string('uuid')->unique(); + $table->string('connection'); + $table->string('queue'); + $table->longText('payload'); + $table->longText('exception'); + $table->timestamp('failed_at')->useCurrent(); + + $table->index(['connection', 'queue', 'failed_at']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('jobs'); + Schema::dropIfExists('job_batches'); + Schema::dropIfExists('failed_jobs'); + } +}; diff --git a/database/migrations/2026_07_24_225144_create_oauth_auth_codes_table.php b/database/migrations/2026_07_24_225144_create_oauth_auth_codes_table.php new file mode 100644 index 0000000..c700b50 --- /dev/null +++ b/database/migrations/2026_07_24_225144_create_oauth_auth_codes_table.php @@ -0,0 +1,39 @@ +char('id', 80)->primary(); + $table->foreignId('user_id')->index(); + $table->foreignUuid('client_id'); + $table->text('scopes')->nullable(); + $table->boolean('revoked'); + $table->dateTime('expires_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('oauth_auth_codes'); + } + + /** + * Get the migration connection name. + */ + public function getConnection(): ?string + { + return $this->connection ?? config('passport.connection'); + } +}; diff --git a/database/migrations/2026_07_24_225145_create_oauth_access_tokens_table.php b/database/migrations/2026_07_24_225145_create_oauth_access_tokens_table.php new file mode 100644 index 0000000..3e50f7f --- /dev/null +++ b/database/migrations/2026_07_24_225145_create_oauth_access_tokens_table.php @@ -0,0 +1,41 @@ +char('id', 80)->primary(); + $table->foreignId('user_id')->nullable()->index(); + $table->foreignUuid('client_id'); + $table->string('name')->nullable(); + $table->text('scopes')->nullable(); + $table->boolean('revoked'); + $table->timestamps(); + $table->dateTime('expires_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('oauth_access_tokens'); + } + + /** + * Get the migration connection name. + */ + public function getConnection(): ?string + { + return $this->connection ?? config('passport.connection'); + } +}; diff --git a/database/migrations/2026_07_24_225146_create_oauth_refresh_tokens_table.php b/database/migrations/2026_07_24_225146_create_oauth_refresh_tokens_table.php new file mode 100644 index 0000000..afb3c55 --- /dev/null +++ b/database/migrations/2026_07_24_225146_create_oauth_refresh_tokens_table.php @@ -0,0 +1,37 @@ +char('id', 80)->primary(); + $table->char('access_token_id', 80)->index(); + $table->boolean('revoked'); + $table->dateTime('expires_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('oauth_refresh_tokens'); + } + + /** + * Get the migration connection name. + */ + public function getConnection(): ?string + { + return $this->connection ?? config('passport.connection'); + } +}; diff --git a/database/migrations/2026_07_24_225147_create_oauth_clients_table.php b/database/migrations/2026_07_24_225147_create_oauth_clients_table.php new file mode 100644 index 0000000..9794dc8 --- /dev/null +++ b/database/migrations/2026_07_24_225147_create_oauth_clients_table.php @@ -0,0 +1,42 @@ +uuid('id')->primary(); + $table->nullableMorphs('owner'); + $table->string('name'); + $table->string('secret')->nullable(); + $table->string('provider')->nullable(); + $table->text('redirect_uris'); + $table->text('grant_types'); + $table->boolean('revoked'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('oauth_clients'); + } + + /** + * Get the migration connection name. + */ + public function getConnection(): ?string + { + return $this->connection ?? config('passport.connection'); + } +}; diff --git a/database/migrations/2026_07_24_225148_create_oauth_device_codes_table.php b/database/migrations/2026_07_24_225148_create_oauth_device_codes_table.php new file mode 100644 index 0000000..ea07831 --- /dev/null +++ b/database/migrations/2026_07_24_225148_create_oauth_device_codes_table.php @@ -0,0 +1,42 @@ +char('id', 80)->primary(); + $table->foreignId('user_id')->nullable()->index(); + $table->foreignUuid('client_id')->index(); + $table->char('user_code', 8)->unique(); + $table->text('scopes'); + $table->boolean('revoked'); + $table->dateTime('user_approved_at')->nullable(); + $table->dateTime('last_polled_at')->nullable(); + $table->dateTime('expires_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('oauth_device_codes'); + } + + /** + * Get the migration connection name. + */ + public function getConnection(): ?string + { + return $this->connection ?? config('passport.connection'); + } +}; diff --git a/database/migrations/2026_07_24_233201_create_permission_tables.php b/database/migrations/2026_07_24_233201_create_permission_tables.php new file mode 100644 index 0000000..8986275 --- /dev/null +++ b/database/migrations/2026_07_24_233201_create_permission_tables.php @@ -0,0 +1,137 @@ +id(); // permission id + $table->string('name'); + $table->string('guard_name'); + $table->timestamps(); + + $table->unique(['name', 'guard_name']); + }); + + /** + * See `docs/prerequisites.md` for suggested lengths on 'name' and 'guard_name' if "1071 Specified key was too long" errors are encountered. + */ + Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) { + $table->id(); // role id + if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing + $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); + $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); + } + $table->string('name'); + $table->string('guard_name'); + $table->timestamps(); + if ($teams || config('permission.testing')) { + $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); + } else { + $table->unique(['name', 'guard_name']); + } + }); + + Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) { + $table->unsignedBigInteger($pivotPermission); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->cascadeOnDelete(); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } else { + $table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } + }); + + Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) { + $table->unsignedBigInteger($pivotRole); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->cascadeOnDelete(); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } else { + $table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } + }); + + Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) { + $table->unsignedBigInteger($pivotPermission); + $table->unsignedBigInteger($pivotRole); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->cascadeOnDelete(); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->cascadeOnDelete(); + + $table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary'); + }); + + app('cache') + ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null) + ->forget(config('permission.cache.key')); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + $tableNames = config('permission.table_names'); + + throw_if(empty($tableNames), 'Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'); + + Schema::dropIfExists($tableNames['role_has_permissions']); + Schema::dropIfExists($tableNames['model_has_roles']); + Schema::dropIfExists($tableNames['model_has_permissions']); + Schema::dropIfExists($tableNames['roles']); + Schema::dropIfExists($tableNames['permissions']); + } +}; diff --git a/database/migrations/2026_07_24_233203_add_is_active_to_users_table.php b/database/migrations/2026_07_24_233203_add_is_active_to_users_table.php new file mode 100644 index 0000000..b2fa208 --- /dev/null +++ b/database/migrations/2026_07_24_233203_add_is_active_to_users_table.php @@ -0,0 +1,28 @@ +boolean('is_active')->default(true)->after('password'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('is_active'); + }); + } +}; diff --git a/database/migrations/2026_07_25_085110_create_app_user_roles_table.php b/database/migrations/2026_07_25_085110_create_app_user_roles_table.php new file mode 100644 index 0000000..d8fa3d9 --- /dev/null +++ b/database/migrations/2026_07_25_085110_create_app_user_roles_table.php @@ -0,0 +1,32 @@ +id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->foreignUuid('client_id')->constrained('oauth_clients')->cascadeOnDelete(); + $table->string('role'); + $table->timestamps(); + + $table->unique(['user_id', 'client_id', 'role']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('app_user_roles'); + } +}; diff --git a/database/migrations/2026_07_25_100506_create_login_events_table.php b/database/migrations/2026_07_25_100506_create_login_events_table.php new file mode 100644 index 0000000..0973ab1 --- /dev/null +++ b/database/migrations/2026_07_25_100506_create_login_events_table.php @@ -0,0 +1,35 @@ +id(); + $table->foreignId('user_id')->nullable()->constrained()->nullOnDelete(); + $table->foreignUuid('client_id')->nullable()->constrained('oauth_clients')->nullOnDelete(); + $table->string('type'); // 'session' (panel admin) | 'oauth' (token emitido para una app) + $table->string('ip_address', 45)->nullable(); + $table->string('user_agent')->nullable(); + $table->timestamp('created_at')->nullable(); + + $table->index(['created_at']); + $table->index(['client_id', 'created_at']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('login_events'); + } +}; diff --git a/database/migrations/2026_07_25_114228_add_deleted_at_to_users_table.php b/database/migrations/2026_07_25_114228_add_deleted_at_to_users_table.php new file mode 100644 index 0000000..f3bc08c --- /dev/null +++ b/database/migrations/2026_07_25_114228_add_deleted_at_to_users_table.php @@ -0,0 +1,28 @@ +softDeletes()->after('is_active'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropSoftDeletes(); + }); + } +}; diff --git a/database/migrations/2026_07_25_190704_add_approved_at_to_users_table.php b/database/migrations/2026_07_25_190704_add_approved_at_to_users_table.php new file mode 100644 index 0000000..49805c6 --- /dev/null +++ b/database/migrations/2026_07_25_190704_add_approved_at_to_users_table.php @@ -0,0 +1,37 @@ +timestamp('approved_at')->nullable()->after('is_active'); + }); + + // Los usuarios que ya existían antes de este cambio quedan aprobados + // (si no, todos se bloquean de golpe la primera vez que se corre esto). + DB::table('users')->whereNull('approved_at')->update(['approved_at' => now()]); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('approved_at'); + }); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php new file mode 100644 index 0000000..93a0c1a --- /dev/null +++ b/database/seeders/DatabaseSeeder.php @@ -0,0 +1,170 @@ +call(RolesAndPermissionsSeeder::class); + + $adminUser = User::firstOrCreate( + ['email' => 'admin@example.com'], + ['name' => 'Admin', 'password' => Hash::make('password'), 'approved_at' => now()] + ); + $adminUser->syncRoles(['admin']); + + // Usuarios extra solo para que las analíticas (sección "usuarios más activos") + // tengan algo interesante que mostrar de entrada. + $extraUsers = collect([ + ['name' => 'Carla Gómez', 'email' => 'carla@example.com'], + ['name' => 'Martín Ruiz', 'email' => 'martin@example.com'], + ['name' => 'Bea Fernández', 'email' => 'bea@example.com'], + ])->map(function (array $data) { + $u = User::firstOrCreate(['email' => $data['email']], ['name' => $data['name'], 'password' => Hash::make('password'), 'approved_at' => now()]); + $u->syncRoles(['user']); + + return $u; + }); + + // Usuario de ejemplo que se autorregistró y todavía nadie aprobó, para + // que la pestaña "Pendientes" del panel de usuarios no arranque vacía. + User::firstOrCreate( + ['email' => 'nuevo@example.com'], + ['name' => 'Nuevo Registro', 'password' => Hash::make('password'), 'approved_at' => null] + )->assignRole('user'); + + // Cliente requerido para poder emitir Personal Access Tokens (grant "personal_access"). + if (! Client::where('personal_access_client', true)->exists()) { + $clients->createPersonalAccessGrantClient('Personal Access Client'); + } + + // Cliente de demo para el flujo Authorization Code + PKCE (público, sin secret). + $pkceClient = $adminUser->oauthApps()->firstOrCreate( + ['name' => 'Demo SPA (Authorization Code + PKCE)'], + [ + 'secret' => null, + 'redirect_uris' => ['http://localhost:8000/callback'], + 'grant_types' => ['authorization_code', 'refresh_token'], + 'revoked' => false, + ] + ); + + // Cliente de demo para Client Credentials (machine-to-machine). + $ccClient = $adminUser->oauthApps()->firstOrCreate( + ['name' => 'Demo Service (Client Credentials)'], + [ + 'secret' => Str::random(40), + 'redirect_uris' => [], + 'grant_types' => ['client_credentials'], + 'revoked' => false, + ] + ); + + // Cliente de demo para Password Grant. + $pwClient = $adminUser->oauthApps()->firstOrCreate( + ['name' => 'Demo First-Party App (Password Grant)'], + [ + 'secret' => Str::random(40), + 'redirect_uris' => [], + 'grant_types' => ['password', 'refresh_token'], + 'revoked' => false, + ] + ); + + // Cliente confidencial para probar el botón "Authorize" de Swagger UI + // (usa /api/oauth2-callback, que ya registra L5-Swagger). + $swaggerClient = $adminUser->oauthApps()->firstOrCreate( + ['name' => 'Swagger UI'], + [ + 'secret' => Str::random(40), + 'redirect_uris' => [rtrim(config('app.url'), '/').'/api/oauth2-callback'], + 'grant_types' => ['authorization_code', 'client_credentials', 'refresh_token'], + 'revoked' => false, + ] + ); + + // Rol por-app de ejemplo: carla@example.com es "admin" dentro de la app + // "Demo SPA", aunque globalmente sea un usuario normal. Muestra que el + // rol puede variar según qué app pregunte por el usuario. + DB::table('app_user_roles')->insertOrIgnore([ + 'user_id' => $extraUsers->first()->id, + 'client_id' => $pkceClient->id, + 'role' => 'admin', + 'created_at' => now(), + 'updated_at' => now(), + ]); + + if (! LoginEvent::query()->exists()) { + $this->seedLoginEvents( + $extraUsers->push($adminUser), + collect([$pkceClient, $ccClient, $pwClient, $swaggerClient]), + ); + } + + $this->command?->info('Admin: admin@example.com / password'); + $this->command?->info('Client Credentials -> client_id: '.$ccClient->id.' secret: '.($ccClient->plainSecret ?? '(ya existía, ver DB)')); + $this->command?->info('Password Grant -> client_id: '.$pwClient->id.' secret: '.($pwClient->plainSecret ?? '(ya existía, ver DB)')); + $this->command?->info('Swagger UI -> client_id: '.$swaggerClient->id.' secret: '.($swaggerClient->plainSecret ?? '(ya existía, ver DB)')); + } + + /** + * Historial sintético de logins (últimos 30 días) para que la pantalla de + * analíticas tenga algo real que graficar apenas se levanta la app. + * + * @param \Illuminate\Support\Collection $users + * @param \Illuminate\Support\Collection $clients + */ + private function seedLoginEvents($users, $clients): void + { + $rows = []; + + $now = now(); + + for ($daysAgo = 29; $daysAgo >= 0; $daysAgo--) { + $dayStart = $now->copy()->subDays($daysAgo)->startOfDay(); + $maxMinute = $daysAgo === 0 ? $now->diffInMinutes($dayStart) : 1439; + $isWeekend = $dayStart->isWeekend(); + $oauthLogins = random_int($isWeekend ? 1 : 4, $isWeekend ? 6 : 14); + $sessionLogins = random_int($isWeekend ? 0 : 1, $isWeekend ? 2 : 5); + + for ($i = 0; $i < $oauthLogins; $i++) { + $rows[] = [ + 'user_id' => $users->random()->id, + 'client_id' => $clients->random()->id, + 'type' => 'oauth', + 'ip_address' => '10.0.'.random_int(0, 9).'.'.random_int(2, 254), + 'user_agent' => 'Mozilla/5.0 (demo seed)', + 'created_at' => $dayStart->copy()->addMinutes(random_int(0, max(0, $maxMinute))), + ]; + } + + for ($i = 0; $i < $sessionLogins; $i++) { + $rows[] = [ + 'user_id' => $users->random()->id, + 'client_id' => null, + 'type' => 'session', + 'ip_address' => '10.0.'.random_int(0, 9).'.'.random_int(2, 254), + 'user_agent' => 'Mozilla/5.0 (demo seed)', + 'created_at' => $dayStart->copy()->addMinutes(random_int(0, max(0, $maxMinute))), + ]; + } + } + + foreach (array_chunk($rows, 200) as $chunk) { + LoginEvent::insert($chunk); + } + } +} diff --git a/database/seeders/RolesAndPermissionsSeeder.php b/database/seeders/RolesAndPermissionsSeeder.php new file mode 100644 index 0000000..3b66af0 --- /dev/null +++ b/database/seeders/RolesAndPermissionsSeeder.php @@ -0,0 +1,27 @@ +syncPermissions(Permission::all()); + Role::findOrCreate('user'); + } +} diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..e8acfbf --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,102 @@ +name: oauth-iam + +services: + app: + build: + context: . + target: app + image: oauth-iam-app:latest + restart: unless-stopped + environment: + CONTAINER_ROLE: app + env_file: .env.production + volumes: + - app_storage:/var/www/html/storage + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + healthcheck: + test: ["CMD", "nc", "-z", "127.0.0.1", "9000"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 30s + + queue: + build: + context: . + target: app + image: oauth-iam-app:latest + restart: unless-stopped + command: php artisan queue:work redis --sleep=3 --tries=3 --max-time=3600 + env_file: .env + volumes: + - app_storage:/var/www/html/storage + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + + scheduler: + build: + context: . + target: app + image: oauth-iam-app:latest + restart: unless-stopped + command: php artisan schedule:work + env_file: .env.production + volumes: + - app_storage:/var/www/html/storage + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy + + nginx: + build: + context: . + target: nginx + restart: unless-stopped + ports: + - "80:80" + # - "443:443" + volumes: + - ./docker/nginx/certs:/etc/nginx/certs:ro + depends_on: + - app + + db: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_DB: ${DB_DATABASE} + POSTGRES_USER: ${DB_USERNAME} + POSTGRES_PASSWORD: ${DB_PASSWORD} + volumes: + - db_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${DB_USERNAME} -d ${DB_DATABASE}"] + interval: 10s + timeout: 5s + retries: 5 + + redis: + image: redis:7-alpine + restart: unless-stopped + command: redis-server --save 60 1 --appendonly yes + volumes: + - redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + +volumes: + app_storage: + db_data: + redis_data: diff --git a/docker/nginx/default.conf b/docker/nginx/default.conf new file mode 100644 index 0000000..f9542e9 --- /dev/null +++ b/docker/nginx/default.conf @@ -0,0 +1,68 @@ +server { + listen 80; + server_name _; + root /var/www/html/public; + index index.php; + + client_max_body_size 20m; + + # DNS embebido de Docker: resuelve "app" en cada request (en vez de una sola vez + # al arrancar nginx) para no quedarse con una IP vieja si el contenedor "app" se + # recrea (redeploy, restart) mientras nginx sigue corriendo. + resolver 127.0.0.11 valid=10s; + + # Descomentar cuando haya certificados en /etc/nginx/certs (ver docs/despliegue.md) + # y quitar el "location /" de abajo para forzar HTTPS: + # location / { return 301 https://$host$request_uri; } + + location = /favicon.ico { access_log off; log_not_found off; } + location = /robots.txt { access_log off; log_not_found off; } + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ \.php$ { + set $php_upstream app:9000; + fastcgi_pass $php_upstream; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME /var/www/html/public$fastcgi_script_name; + include fastcgi_params; + fastcgi_read_timeout 60; + fastcgi_hide_header X-Powered-By; + } + + location ~ /\.(?!well-known) { + deny all; + } + + gzip on; + gzip_types text/css application/javascript application/json image/svg+xml; +} + +# server { +# listen 443 ssl http2; +# server_name tu-dominio.com; +# root /var/www/html/public; +# index index.php; +# +# ssl_certificate /etc/nginx/certs/fullchain.pem; +# ssl_certificate_key /etc/nginx/certs/privkey.pem; +# ssl_protocols TLSv1.2 TLSv1.3; +# +# client_max_body_size 20m; +# resolver 127.0.0.11 valid=10s; +# +# location / { +# try_files $uri $uri/ /index.php?$query_string; +# } +# +# location ~ \.php$ { +# set $php_upstream app:9000; +# fastcgi_pass $php_upstream; +# fastcgi_index index.php; +# fastcgi_param SCRIPT_FILENAME /var/www/html/public$fastcgi_script_name; +# include fastcgi_params; +# fastcgi_read_timeout 60; +# } +# } diff --git a/docker/php/entrypoint.sh b/docker/php/entrypoint.sh new file mode 100644 index 0000000..efbf6b8 --- /dev/null +++ b/docker/php/entrypoint.sh @@ -0,0 +1,24 @@ +#!/bin/sh +set -e + +cd /var/www/html + +if [ "$CONTAINER_ROLE" = "app" ]; then + chown -R www-data:www-data storage bootstrap/cache + + if [ ! -f storage/oauth-private.key ]; then + echo "Generando claves de Passport (primer arranque)..." + su-exec www-data php artisan passport:keys + fi + + echo "Aplicando migraciones..." + su-exec www-data php artisan migrate --force + + echo "Cacheando configuración, rutas y vistas..." + su-exec www-data php artisan config:cache + su-exec www-data php artisan route:cache + su-exec www-data php artisan view:cache + su-exec www-data php artisan l5-swagger:generate +fi + +exec "$@" diff --git a/docker/php/opcache.ini b/docker/php/opcache.ini new file mode 100644 index 0000000..6c96a0b --- /dev/null +++ b/docker/php/opcache.ini @@ -0,0 +1,7 @@ +opcache.enable = 1 +opcache.enable_cli = 0 +opcache.memory_consumption = 128 +opcache.interned_strings_buffer = 16 +opcache.max_accelerated_files = 20000 +opcache.validate_timestamps = 0 +opcache.save_comments = 1 diff --git a/docker/php/production.ini b/docker/php/production.ini new file mode 100644 index 0000000..02fc48f --- /dev/null +++ b/docker/php/production.ini @@ -0,0 +1,8 @@ +memory_limit = 256M +upload_max_filesize = 20M +post_max_size = 20M +max_execution_time = 60 +expose_php = Off +display_errors = Off +log_errors = On +error_log = /proc/self/fd/2 diff --git a/docs/despliegue.md b/docs/despliegue.md new file mode 100644 index 0000000..62f3817 --- /dev/null +++ b/docs/despliegue.md @@ -0,0 +1,108 @@ +# Despliegue en producción (Docker) + +Stack: `app` (PHP-FPM 8.3) + `nginx` (estáticos y proxy) + `db` (PostgreSQL 16) + `redis` +(cache/sesión/colas) + `queue` (worker) + `scheduler`. + +## 1. Preparar el `.env` + +```bash +cp .env.production.example .env +``` + +Editar `.env` y completar como mínimo: + +- `APP_URL` — dominio real, con `https://`. +- `DB_PASSWORD` — contraseña de PostgreSQL (usada también por el servicio `db`). +- `SESSION_DOMAIN` — dominio (o `.tu-dominio.com` si hay subdominios). +- `APP_KEY` — generar uno nuevo, no reutilizar el de desarrollo: + +```bash +docker run --rm -v "$PWD":/app -w /app composer:2 sh -c \ + "php -r \"echo 'base64:'.base64_encode(random_bytes(32)).PHP_EOL;\"" +``` + +Pegar el resultado en `APP_KEY=`. + +## 2. Levantar el stack + +```bash +docker compose -f docker-compose.prod.yml up -d --build +``` + +En el primer arranque, el contenedor `app` (ver `docker/php/entrypoint.sh`): + +1. Genera las claves RSA de Passport (`storage/oauth-private.key` / `.pub`) si no existen — + quedan persistidas en el volumen `app_storage`, **no se regeneran en cada deploy** (regenerarlas + invalidaría todos los tokens ya emitidos). +2. Corre `php artisan migrate --force`. +3. Cachea config/rutas/vistas y regenera la documentación de Swagger. + +```bash +docker compose -f docker-compose.prod.yml logs -f app +``` + +## 3. Crear el primer usuario admin + +**Recomendado para producción:** no correr `db:seed` (ese seeder crea datos de demo con +contraseñas conocidas). En su lugar, con la base de usuarios vacía, andá a `/register` y creá una +cuenta — al ser la primera, el sistema la marca automáticamente como **admin**, aprobada y con la +sesión iniciada (no hace falta que nadie la apruebe, porque todavía no existe ningún admin). Cualquier +registro posterior vuelve a requerir aprobación manual desde el panel de usuarios. + +Alternativa solo para entornos de prueba/demo, con datos de ejemplo (usuarios, clientes OAuth, historial +de logins falso): + +```bash +docker compose -f docker-compose.prod.yml exec app php artisan db:seed --force +``` + +Esto sí crea `admin@example.com` / `password` — **cambiar la contraseña de inmediato** si se usa +fuera de un entorno descartable. + +## 4. TLS + +Por defecto `nginx` sirve solo HTTP en el puerto 80 (`docker/nginx/default.conf`). Para HTTPS: + +1. Conseguir certificado (por ejemplo con `certbot --webroot` apuntando a + `docker/nginx/certs`, o cualquier CA propia). +2. Colocar `fullchain.pem` y `privkey.pem` en `docker/nginx/certs/`. +3. Descomentar el bloque `server { listen 443 ... }` en `docker/nginx/default.conf` y el + `location /` que redirige a HTTPS. +4. Descomentar `"443:443"` en `docker-compose.prod.yml` (servicio `nginx`). +5. `docker compose -f docker-compose.prod.yml up -d --build nginx`. + +## 5. Desplegar una actualización + +```bash +git pull # o el mecanismo que uses para traer código nuevo +docker compose -f docker-compose.prod.yml up -d --build +``` + +Las claves de Passport, la base de datos y Redis viven en volúmenes con nombre +(`app_storage`, `db_data`, `redis_data`) — sobreviven a los rebuilds. El código y los assets del +frontend, en cambio, se hornean dentro de la imagen en cada build (no hay bind mounts de código en +producción). + +## 6. Backups + +Mínimo indispensable a respaldar: + +- Volumen `db_data` (o mejor, `pg_dump` periódico contra el servicio `db`). +- Volumen `app_storage` — contiene, entre otras cosas, las claves de Passport. Perderlas invalida + todos los tokens emitidos hasta ese momento. + +```bash +docker compose -f docker-compose.prod.yml exec db \ + pg_dump -U "$DB_USERNAME" "$DB_DATABASE" > backup-$(date +%F).sql +``` + +## Servicios del compose + +| Servicio | Rol | +|-------------|-----| +| `app` | PHP-FPM. Corre migraciones y cacheo al arrancar (`CONTAINER_ROLE=app`). | +| `queue` | `php artisan queue:work redis` — misma imagen que `app`, sin el paso de migración. | +| `scheduler` | `php artisan schedule:work` — reemplaza al cron del sistema. | +| `nginx` | Sirve `public/build` y hace proxy de `*.php` a `app:9000`. | +| `db` | PostgreSQL 16, datos en el volumen `db_data`. | +| `redis` | Cache, sesiones y colas, datos en el volumen `redis_data` (AOF activado). | diff --git a/docs/integracion-iam.html b/docs/integracion-iam.html new file mode 100644 index 0000000..f06dc13 --- /dev/null +++ b/docs/integracion-iam.html @@ -0,0 +1,602 @@ + + + + +Integrar tu app con este servidor de identidad (IAM) + + + + +
+ Guía para desarrolladores +

Integrar tu app con este servidor de identidad

+

Cómo conectar Angular, NestJS, Flutter (o cualquier otro stack) a este servidor OAuth2 + para usarlo como IAM centralizado: login, usuarios, roles globales y roles por-app.

+
+ Laravel 12 · Passport 13 (OAuth2, tokens JWT firmados RS256)
+ Generado el 25 de julio de 2026 +
+
+ +
+

Índice

+
    +
  • 1.Qué te da este servidor
  • +
  • 2.Paso 1 — Registrar tu app como cliente OAuth
  • +
  • 3.Elegí el flujo según tu arquitectura
  • +
  • 4.Angular (SPA) — Authorization Code + PKCE
  • +
  • 5.NestJS — backend con login de usuarios
  • +
  • 6.NestJS — servicio backend-a-backend (Client Credentials)
  • +
  • 7.Flutter / apps móviles
  • +
  • 8.Validar el token en tu backend
  • +
  • 9.Roles: global vs. por-app
  • +
  • 10.Refrescar y revocar tokens
  • +
  • 11.Checklist de seguridad
  • +
  • 12.Referencia rápida
  • +
+
+ +
+

1. Qué te da este servidor

+

+Es un servidor de identidad OAuth2 (Laravel Passport). En vez de que cada app tuya tenga su +propia tabla de usuarios y su propio login, todas hablan con este servidor: acá vive el usuario, la +contraseña, y quién es qué. +

+
    +
  • Login centralizado — un solo lugar donde el usuario pone su contraseña. Tu app nunca + la ve: recibe un access token después de que el usuario aprueba el acceso.
  • +
  • Datos de usuario vía API — GET /api/user con el token te devuelve nombre, + email, y sus roles.
  • +
  • Roles globales y roles por-app — un usuario puede ser admin en el sistema + en general, pero además tener un rol específico de tu app (por ejemplo editor en la + tuya y nada en la de otro equipo). Ver sección 9.
  • +
  • Gestión completa por API — si querés armar tu propio panel de administración de + usuarios (en vez de usar el que trae este servidor), toda la funcionalidad está expuesta en + /api/* con Bearer token. No hace falta sesión ni cookies de este servidor.
  • +
+
+ Dos capas de permisos, no confundirlas: los scopes del token OAuth ( + users:read, users:write...) definen qué puede hacer ese token contra la + API de este servidor. Los roles por-app definen qué puede hacer ese usuario dentro de tu app + — son datos que vos leés e interpretás como quieras, este servidor no te impone nada sobre cómo usarlos. +
+
+ +
+

2. Paso 1 — Registrar tu app como cliente OAuth

+

Cada aplicación que se conecte necesita ser un cliente registrado. Se hace una sola vez, desde el +panel (/clients, con cualquier cuenta) o por API:

+
curl -X POST http://localhost:8000/api/clients \
+  -H "Authorization: Bearer {tu_access_token}" \
+  -H "Content-Type: application/json" \
+  -d '{
+    "name": "Mi App Angular",
+    "grant_type": "authorization_code",
+    "redirect_uris": ["https://mi-app.com/callback"],
+    "confidential": false
+  }'
+

La respuesta trae client.id (el client_id) y, si es confidencial, +plain_secret — se muestra una sola vez, guardalo ya.

+ + + + + +
CampoQué significa
grant_typeauthorization_code, client_credentials o password — ver sección 3 para elegir.
redirect_urisA dónde te devuelve el navegador después del login. Tiene que matchear exacto con lo que mandes en /oauth/authorize.
confidentialfalse = cliente público, sin secret (SPA, apps móviles — usan PKCE en su lugar). true = confidencial, con secret (backends que pueden guardar un secreto de forma segura).
+
+ +
+

3. Elegí el flujo según tu arquitectura

+
+
+

SPA pura (Angular, React, Vue)

+

Sin backend propio, corre 100% en el navegador. Authorization Code + PKCE, cliente + público. El token queda en el navegador.

+
+
+

App con backend (NestJS, Express)

+

El backend maneja el login por vos (patrón BFF). Authorization Code, cliente + confidencial — el secret vive en el backend, nunca llega al navegador.

+
+
+

App móvil (Flutter, RN)

+

Authorization Code + PKCE, cliente público, con un redirect por deep link / custom + scheme.

+
+
+

Servicio sin usuario final

+

Un cron, un worker, otro backend que llama a la API por su cuenta. Client Credentials.

+
+
+
+ Password Grant existe pero evitalo salvo que sea estrictamente first-party (una app que vos + mismo controlás de punta a punta): tu app le pide el usuario y contraseña directamente al usuario y se los + manda a este servidor. Ningún cliente de terceros debería tocar la contraseña del usuario. +
+
+ +
+

4. Angular (SPA) — Authorization Code + PKCE

+

Ejemplo con angular-oauth2-oidc (también podés implementarlo a mano, son cuatro pasos: generar +verifier/challenge, redirigir, recibir el code, canjearlo).

+ +app.config.ts +
import { provideOAuthClient, OAuthModule } from 'angular-oauth2-oidc';
+
+export const authConfig = {
+  issuer: 'http://localhost:8000',
+  loginUrl: 'http://localhost:8000/oauth/authorize',
+  tokenEndpoint: 'http://localhost:8000/oauth/token',
+  redirectUri: window.location.origin + '/callback',
+  clientId: '{tu_client_id}',
+  responseType: 'code',
+  scope: 'users:read account:read',
+  useHttpBasicAuth: false,
+  disablePKCE: false, // clave: PKCE prendido, es cliente público
+  oidc: false, // este servidor es OAuth2 puro, no expone id_token/OIDC
+};
+ +auth.service.ts +
import { Injectable } from '@angular/core';
+import { OAuthService } from 'angular-oauth2-oidc';
+
+@Injectable({ providedIn: 'root' })
+export class AuthService {
+  constructor(private oauth: OAuthService) {}
+
+  login() {
+    this.oauth.initCodeFlow(); // redirige a /oauth/authorize con PKCE
+  }
+
+  async me() {
+    const token = this.oauth.getAccessToken();
+    const res = await fetch('http://localhost:8000/api/user', {
+      headers: { Authorization: `Bearer ${token}` },
+    });
+    return res.json(); // { id, name, email, roles, app_roles, ... }
+  }
+}
+

Si preferís no traer una librería, el flujo manual es: generar code_verifier (random) y +code_challenge (SHA-256 + base64url del verifier), armar la URL de +/oauth/authorize con esos parámetros + state, y en el callback hacer +POST /oauth/token con grant_type=authorization_code, el code recibido y +el code_verifier original.

+
+ +
+

5. NestJS — backend con login de usuarios

+

Tu backend actúa de cliente confidencial (guarda el secret) y maneja el flujo Authorization +Code por el usuario — patrón "backend for frontend": el navegador nunca ve el access token, solo una cookie de +sesión propia de tu app.

+ +auth.controller.ts +
@Controller('auth')
+export class AuthController {
+  private readonly IAM = 'http://localhost:8000';
+  private readonly CLIENT_ID = process.env.IAM_CLIENT_ID;
+  private readonly CLIENT_SECRET = process.env.IAM_CLIENT_SECRET;
+
+  @Get('login')
+  login(@Res() res: Response) {
+    const params = new URLSearchParams({
+      client_id: this.CLIENT_ID,
+      redirect_uri: 'https://mi-backend.com/auth/callback',
+      response_type: 'code',
+      scope: 'users:read',
+      state: crypto.randomUUID(),
+    });
+    res.redirect(`${this.IAM}/oauth/authorize?${params}`);
+  }
+
+  @Get('callback')
+  async callback(@Query('code') code: string, @Session() session: Record) {
+    const { data } = await axios.post(`${this.IAM}/oauth/token`, {
+      grant_type: 'authorization_code',
+      client_id: this.CLIENT_ID,
+      client_secret: this.CLIENT_SECRET,
+      code,
+      redirect_uri: 'https://mi-backend.com/auth/callback',
+    });
+
+    session.accessToken = data.access_token; // guardalo del lado del servidor
+    session.refreshToken = data.refresh_token;
+  }
+}
+

Como es cliente confidencial, no hace falta PKCE (aunque no molesta agregarlo igual). El token queda +guardado en la sesión del backend, nunca expuesto al navegador.

+
+ +
+

6. NestJS — servicio backend-a-backend (Client Credentials)

+

Para un servicio que consulta/gestiona usuarios por su cuenta, sin un usuario logueado de por medio — +scheduler, worker, integración entre sistemas.

+ +iam-client.service.ts +
@Injectable()
+export class IamClientService {
+  private token: string | null = null;
+  private expiresAt = 0;
+
+  private async getToken(): Promise {
+    if (this.token && Date.now() < this.expiresAt) return this.token;
+
+    const { data } = await axios.post('http://localhost:8000/oauth/token', {
+      grant_type: 'client_credentials',
+      client_id: process.env.IAM_SERVICE_CLIENT_ID,
+      client_secret: process.env.IAM_SERVICE_CLIENT_SECRET,
+      scope: 'users:read users:write',
+    });
+
+    this.token = data.access_token;
+    this.expiresAt = Date.now() + (data.expires_in - 60) * 1000; // renovar 1 min antes
+    return this.token;
+  }
+
+  async listUsers() {
+    const token = await this.getToken();
+    const { data } = await axios.get('http://localhost:8000/api/users', {
+      headers: { Authorization: `Bearer ${token}` },
+    });
+    return data;
+  }
+}
+
+ Un token de client_credentials no tiene usuario asociado: actúa como "servicio de confianza". + En este servidor eso significa que puede usar /api/users y /api/apps con el + scope como único chequeo (sin necesitar un permiso de usuario admin) — pero no puede tocar + recursos "de cuenta propia" (/api/clients, personal access tokens, apps autorizadas), porque + esos son inherentemente de un usuario y no tiene ninguno asociado. +
+
+ +
+

7. Flutter / apps móviles

+

Con el paquete flutter_appauth (implementa Authorization Code + PKCE con el navegador del +sistema, que es lo recomendado — evita usar un WebView embebido).

+ +auth_service.dart +
final appAuth = FlutterAppAuth();
+
+Future<String?> login() async {
+  final result = await appAuth.authorizeAndExchangeCode(
+    AuthorizationTokenRequest(
+      '{tu_client_id}',
+      'com.miapp://callback', // custom scheme registrado en tu app
+      serviceConfiguration: const AuthorizationServiceConfiguration(
+        authorizationEndpoint: 'http://localhost:8000/oauth/authorize',
+        tokenEndpoint: 'http://localhost:8000/oauth/token',
+      ),
+      scopes: ['users:read'],
+    ),
+  );
+
+  return result.accessToken;
+}
+
+Future<Map<String, dynamic>> me(String token) async {
+  final res = await http.get(
+    Uri.parse('http://localhost:8000/api/user'),
+    headers: {'Authorization': 'Bearer $token'},
+  );
+  return jsonDecode(res.body);
+}
+

El redirect_uri (com.miapp://callback) tiene que estar registrado exactamente +igual en el cliente OAuth (sección 2) y configurado en tu app nativa (Android: intent filter; iOS: URL scheme +en el Info.plist).

+
+ +
+

8. Validar el token en tu backend

+

Cuando tu backend recibe un request con Authorization: Bearer {token} y necesita confirmar que +es válido, hay dos caminos:

+ +

Opción A — Preguntarle a este servidor (más simple)

+
GET /api/user
+Authorization: Bearer {token}
+

Si responde 200 con los datos del usuario, el token es válido. Es un request de red extra por +cada llamada, pero es siempre exacto: refleja revocaciones al instante.

+ +

Opción B — Validar el JWT localmente (sin red)

+

Los access tokens de este servidor son JWT firmados con RS256 (no opacos). Tu backend puede +verificar la firma con la clave pública de Passport (storage/oauth-public.key en este proyecto — +pedísela al equipo que administra este servidor) sin llamarlo por red:

+
// Node.js, con la librería "jsonwebtoken"
+import jwt from 'jsonwebtoken';
+import fs from 'fs';
+
+const publicKey = fs.readFileSync('oauth-public.key');
+
+function verify(token: string) {
+  return jwt.verify(token, publicKey, { algorithms: ['RS256'] });
+  // -> { aud, jti, iat, nbf, exp, sub, scopes: string[] }
+}
+

Claims relevantes: sub = id del usuario (ausente en tokens de client_credentials), +aud = client_id que lo pidió, scopes = array de scopes otorgados, +exp = expiración (unix timestamp).

+
+ Trade-off: la validación local es más rápida (sin red) pero no se entera si el token fue + revocado antes de su expiración natural — solo valida firma y expiración. Para casos sensibles + (borrar acceso ya mismo), usá la Opción A o combiná: validación local + chequeo periódico contra el + servidor. +
+
+ +
+

9. Roles: global vs. por-app

+

GET /api/user (o /api/users/{id}) devuelve, entre otros campos:

+
{
+  "id": 2,
+  "name": "Carla Gómez",
+  "email": "carla@example.com",
+  "roles": ["user"],
+  "app_roles": ["editor"]
+}
+
    +
  • roles — el rol global del usuario en este servidor (admin o + user). No dice nada sobre qué puede hacer en tu app.
  • +
  • app_roles — los roles que un admin le asignó a ese usuario específicamente para el + cliente OAuth que hizo la request (o sea: tu app). Se resuelve solo, según el client_id + del token usado — no hace falta pedir nada extra.
  • +
+

Un admin asigna estos roles desde /admin/users → editar usuario → "Roles por app", eligiendo tu +app de la lista. Interpretalos como quieras del lado tuyo (un guard de NestJS, un route guard de Angular, etc.) +— este servidor solo los guarda y te los devuelve.

+nestjs guard (ejemplo) +
@Injectable()
+export class AppRoleGuard implements CanActivate {
+  async canActivate(context: ExecutionContext): Promise<boolean> {
+    const req = context.switchToHttp().getRequest();
+    const user = await this.iam.me(req.headers.authorization);
+    return user.app_roles.includes('editor');
+  }
+}
+
+ +
+

10. Refrescar y revocar tokens

+

Los access tokens expiran (15 días por defecto en este servidor). Si tu cliente pidió el scope +refresh_token implícito del grant (viene incluido en authorization_code y +password), la respuesta de /oauth/token trae un refresh_token — usalo +así, sin volver a pasar por el login:

+
curl -X POST http://localhost:8000/oauth/token \
+  -d grant_type=refresh_token \
+  -d refresh_token={tu_refresh_token} \
+  -d client_id={tu_client_id} \
+  -d client_secret={tu_client_secret}   # si tu cliente es confidencial
+

client_credentials no tiene refresh token — simplemente pedís uno nuevo cuando el anterior +expira (ver el ejemplo de NestJS en la sección 6).

+

El usuario puede revocarte el acceso en cualquier momento desde /authorized-apps en el panel de +este servidor — a partir de ahí tus requests con ese token empiezan a devolver 401.

+
+ +
+

11. Checklist de seguridad

+
    +
  • ☐ Clientes públicos (SPA, móvil) — siempre PKCE, nunca les asignes un secret que + tendrían que embeber en código que corre en el dispositivo del usuario.
  • +
  • ☐ Pedí solo los scopes que realmente necesitás — no hay scope por defecto en este + servidor, es intencional.
  • +
  • ☐ En una SPA, si podés, no guardes el access token en localStorage (vulnerable a XSS) — + preferí el patrón backend-for-frontend (sección 5) con cookie httpOnly.
  • +
  • ☐ Verificá siempre el parámetro state en el callback de Authorization Code (protección + CSRF del flujo).
  • +
  • ☐ Los client_secret de clientes confidenciales van en variables de entorno del backend, + nunca en el repo ni en el bundle del frontend.
  • +
  • ☐ Si tu servicio backend-a-backend solo necesita leer, pedile a un admin que te dé un cliente con scope + users:read nada más (no users:write).
  • +
+
+ +
+

12. Referencia rápida

+ + + + + + + + + +
EndpointPara qué
GET /oauth/authorizeInicio del flujo Authorization Code (redirige al login/consentimiento).
POST /oauth/tokenCanjear código, credenciales o refresh token por un access token.
GET /api/userWhoami — usuario del token actual + roles global y por-app.
GET/POST/PUT/DELETE /api/usersDirectorio de usuarios (scope users:read/write).
GET/POST/DELETE /api/users/{id}/app-rolesRoles por-app de un usuario (para asignarlos vos mismo por API, si tenés permiso).
GET /api/appsListado de apps (clientes OAuth) registradas.
GET/POST/PUT/DELETE /api/clientsGestionar tus propios clientes OAuth por API.
+ + + + + + +
ScopeDa acceso a
users:readLeer usuarios y sus roles (global + por-app).
users:writeCrear/editar/borrar usuarios, asignar roles por-app.
account:readLeer tus propios clientes OAuth, tokens y apps autorizadas.
account:writeGestionar tus propios clientes OAuth, tokens y apps autorizadas.
+

Documentación interactiva (Swagger, para probar todo esto desde el navegador) en +/api/documentation. Manual completo del panel de administración en +docs/manual-usuario.pdf.

+ +
Guía de integración IAM — generada automáticamente a partir del estado actual del proyecto.
+
+ + + diff --git a/docs/integracion-iam.pdf b/docs/integracion-iam.pdf new file mode 100644 index 0000000..4ff0925 Binary files /dev/null and b/docs/integracion-iam.pdf differ diff --git a/docs/manual-usuario.html b/docs/manual-usuario.html new file mode 100644 index 0000000..da437ff --- /dev/null +++ b/docs/manual-usuario.html @@ -0,0 +1,620 @@ + + + + +OAuth2 Server — Manual de uso + + + + +
+ Documentación técnica +

OAuth2 Identity Server

+

Manual de uso: puesta en marcha, panel de administración, flujos OAuth2 y API REST completa para construir cualquier cliente propio (Angular, Flutter, NestJS, etc.).

+
+ Laravel 12 · Passport 13 · Vue 3 + TypeScript · SCSS/BEM · spatie/laravel-permission · L5-Swagger
+ Generado el 25 de julio de 2026 +
+
+ +
+

Índice

+
    +
  • 1.Qué es esta app
  • +
  • 2.Puesta en marcha
  • +
  • 3.Usuarios por defecto (seed)
  • +
  • 4.Uso de la interfaz web (panel de administración)
  • +
  • 5.Analíticas: sesiones, logins y actividad
  • +
  • 6.Roles, permisos y roles por app
  • +
  • 7.Idioma (español / inglés / catalán)
  • +
  • 8.Cómo conectar tu app (Angular, NestJS, Flutter, etc.)
  • +
  • 9.API REST completa: construí tu propio panel admin
  • +
  • 10.Documentación interactiva (Swagger)
  • +
  • 11.Referencia rápida de rutas
  • +
+
+ +
+

1. Qué es esta app

+

+Es un servidor de identidad / OAuth2 (Laravel Passport) pensado para ser el centro único de +usuarios, autenticación y permisos de un conjunto de aplicaciones propias. No es una app de negocio con datos +propios: su único "recurso" son los usuarios y sus roles. +

+

Provee tres piezas:

+
    +
  • Servidor de autorización OAuth2 — login, pantalla de consentimiento, y emisión de tokens + con los cuatro flujos estándar: Authorization Code + PKCE, Client Credentials, Password Grant y Personal + Access Tokens.
  • +
  • Panel de administración en Vue 3 + TypeScript — la UI que trae la app "de fábrica", + servida por el propio Laravel, autenticada por sesión. Estilos en SCSS con nomenclatura BEM + (resources/scss/), sin frameworks de utility-classes.
  • +
  • API REST completa (/api/*) — el mismo conjunto de funciones del + panel Vue (usuarios, roles por app, clientes OAuth, personal access tokens, apps autorizadas), pero + autenticada 100% por token OAuth2 (sin sesión ni cookies). Con esto se puede construir otro admin + entero en Angular, Flutter, o cualquier otro cliente — no hace falta usar la SPA Vue.
  • +
+
+ Idea central: cada aplicación externa que quiera loguear usuarios se registra acá como un + cliente OAuth ("app"). Un mismo usuario puede tener un rol global (admin/user) y, además, un rol + distinto dentro de cada app (por ejemplo: admin en la app de Angular, usuario normal en la de NestJS). +
+
+ Dos frentes, misma API subyacente: la SPA Vue (sesión + cookies, pensada para uso interno + en el navegador de este mismo servidor) y la API REST (Bearer token, pensada para cualquier cliente externo) + exponen las mismas capacidades. Elegí el que corresponda: sesión para la UI que ya viene incluida, + token para construir la tuya propia. +
+
+ +
+

2. Puesta en marcha

+

Requisitos

+
    +
  • PHP 8.4+, Composer
  • +
  • Node.js + npm
  • +
  • SQLite (por defecto, cero configuración — se puede cambiar en .env)
  • +
+

Instalación (primera vez)

+
composer install
+npm install
+php artisan migrate --seed
+npm run build
+
+ Frontend: TypeScript (npm run type-check corre vue-tsc sin + emitir archivos) y SCSS compilado por Vite (paquete sass) desde + resources/scss/app.scss — no hay ningún framework de CSS utility-first instalado. +
+

Levantar todo en desarrollo

+
composer run dev
+

Este comando levanta en paralelo: el servidor PHP (php artisan serve), Vite en modo watch, la +cola de trabajos y los logs. La app queda disponible en http://localhost:8000.

+
+ Reiniciar todo de cero: php artisan migrate:fresh --seed borra la base y + vuelve a crear los usuarios/clientes de ejemplo descriptos en la sección 3. Los client_secret + de los clientes OAuth de ejemplo se imprimen en la consola en ese momento — solo se muestran una vez. +
+
+ +
+

3. Usuarios por defecto (seed)

+

El seeder (database/seeders/DatabaseSeeder.php) crea una cuenta admin y algunos usuarios de +ejemplo para poder probar la app de inmediato:

+
+
+

🛡 Administrador

+
Email: admin@example.com
+
Password: password
+
Rol global: admin
+
Acceso al panel de administración de usuarios.
+
+
+

👤 Usuario normal

+
Email: carla@example.com
+
Password: password
+
Rol global: user
+
Sin acceso al panel admin. Es admin dentro de la app "Demo SPA" (rol por-app de ejemplo). Junto con martin@example.com y bea@example.com, solo están para que la sección de analíticas tenga datos de entrada.
+
+
+
+ El seeder también crea nuevo@example.com / password sin aprobar, para que la + pestaña "Pendientes" del panel de usuarios (sección 4) no arranque vacía. +
+
+ Instalación sin seed (producción): si nunca corriste db:seed, no hay ningún + usuario en la base. En ese caso, la primera cuenta que se registre por /register + queda automáticamente como admin, aprobada y con la sesión iniciada — no hace falta que + nadie más la apruebe, porque todavía no existe ningún admin que pudiera hacerlo. La pantalla de registro + avisa de esto con un mensaje cuando detecta que la base está vacía. A partir del segundo registro, vuelve + a regir el flujo normal (pendiente de aprobación por un admin). +
+ +

Clientes OAuth (apps) de ejemplo

+

El seeder también registra apps de prueba, una por cada flujo soportado. El client_id es fijo +por ejecución (UUID nuevo cada migrate:fresh); el client_secret solo se muestra por +consola al sembrar — si lo perdiste, hay que crear un cliente nuevo desde el panel (sección 4) o volver a +sembrar.

+ + + + + + + +
NombreGrant typeUso
Demo SPA (Authorization Code + PKCE)authorization_codeCliente público (sin secret), para apps tipo SPA/móvil.
Demo Service (Client Credentials)client_credentialsComunicación máquina-a-máquina, sin usuario.
Demo First-Party App (Password Grant)passwordApps propias de confianza, login directo usuario/contraseña.
Swagger UIauthorization_code, client_credentialsPara probar el botón "Authorize" de la documentación interactiva.
Personal Access Clientpersonal_accessInterno — necesario para poder emitir Personal Access Tokens.
+
+ +
+

4. Uso de la interfaz web (panel de administración)

+

Todo se maneja desde http://localhost:8000 (Vue Router, sin recargar página).

+
+ Modo oscuro: el ícono de sol/luna en la barra de navegación alterna entre claro y oscuro. + La preferencia se guarda en localStorage (por navegador, no por cuenta) y, si nunca la tocaste, + arranca siguiendo el modo del sistema operativo. El cambio se aplica antes del primer render — no hay + parpadeo del tema equivocado al cargar la página. +
+ +

Login / Registro

+

/login y /register. El registro crea la cuenta con rol global user +automáticamente, pero no loguea al usuario: queda pendiente de aprobación. La pantalla muestra +un mensaje de "Cuenta creada" y el login devuelve error hasta que un admin la aprueba desde el panel de +usuarios (sección 4). Excepción: si la base de usuarios está vacía, esa primera cuenta se +crea como admin y queda logueada de una (ver sección 3).

+ +

Dashboard

+

/ — accesos rápidos a Clientes OAuth, Personal Access Tokens, Apps autorizadas y (si sos admin) +Usuarios.

+ +

Clientes OAuth

+

/clients — cada usuario ve y gestiona sus propios clientes (apps que registró). Al +crear uno se elige:

+
    +
  • Authorization Code + PKCE: hay que indicar redirect URIs; opcionalmente "confidencial" + (con secret) o público (solo PKCE, sin secret).
  • +
  • Client Credentials: siempre confidencial, sin redirect URI.
  • +
  • Password Grant: para apps propias de confianza.
  • +
+
El client_secret se muestra una sola vez, justo después de +crear el cliente. Copiarlo en ese momento.
+ +

Personal Access Tokens

+

/tokens — tokens tipo "API key" para el propio usuario, sin pasar por el flujo de consentimiento. +Se elige nombre y scopes. El token completo también se muestra una única vez.

+ +

Apps autorizadas

+

/authorized-apps — lista de apps de terceros a las que el usuario le dio acceso a su cuenta vía +/oauth/authorize, con opción de revocar el acceso en cualquier momento.

+ +

Usuarios (solo admins)

+

/admin/users — alta, edición (nombre, email, contraseña, rol global, activo/desactivado) y baja +de cualquier usuario del sistema. Protecciones incluidas: no podés borrarte a vos mismo, no podés quitarte el +rol admin a vos mismo, no se puede borrar al último administrador. Un usuario desactivado no puede iniciar +sesión.

+
+ Borrado = papelera (soft delete). "Borrar" un usuario no lo elimina de la base: lo oculta + (desaparece de logins, listados y de cualquier chequeo de permisos) pero queda recuperable. El botón + "Ver papelera" arriba a la derecha del listado muestra los usuarios borrados, con dos + acciones por fila: Restaurar (vuelve a aparecer tal cual estaba) o Borrar + definitivamente (ahí sí, sin vuelta atrás). +
+
+ Registros pendientes de aprobación. Arriba a la derecha del listado hay tres pestañas: + Ver activos, Pendientes (con un contador si hay alguno) y Ver papelera. + Todo el que se registra por /register aparece en "Pendientes" hasta que un admin lo + aprueba (ahí ya puede iniciar sesión) o lo rechaza (va a la papelera, + igual que un borrado normal — se puede restaurar). Un usuario dado de alta directamente por un admin + (el formulario "Nuevo usuario" de esta misma pantalla) queda aprobado automáticamente. +
+ +

Analíticas (solo admins)

+

/admin/analytics — ver sección 5.

+
+ +
+

5. Analíticas: sesiones, logins y actividad

+

+Pantalla de monitoreo para el admin: cada vez que Passport emite un access token (cualquier grant type) o alguien +inicia sesión en el panel, queda un registro en login_events. Con eso se arman: +

+
    +
  • KPIs: usuarios totales, sesiones activas (tokens OAuth vigentes, no revocados ni + expirados), logins de hoy y de los últimos 7 días.
  • +
  • Logins por día (últimos 7/30/90 días, elegible arriba a la derecha): barras apiladas — + logins de apps (OAuth) vs. logins al panel admin (sesión) — con tooltip al pasar el mouse sobre cada día.
  • +
  • Logins por app: qué apps se usan más.
  • +
  • Usuarios más activos: ranking por cantidad de logins en el período.
  • +
  • Actividad reciente: tabla con los últimos eventos (usuario, app, tipo, IP, cuándo).
  • +
+
+ El seeder de demo genera 30 días de actividad sintética para que estos gráficos no arranquen vacíos. En un + despliegue real, los datos son 100% eventos reales — no hay nada que "activar". +
+

Misma info disponible por API: GET /api/analytics/summary?days=30 (scope +users:read, requiere el permiso users.view — igual que el resto del panel admin).

+
+ +
+

6. Roles, permisos y roles por app

+

Roles globales

+

Gobiernan el panel de administración de esta app (spatie/laravel-permission):

+ + + + +
RolPermisos
adminusers.view, users.create, users.update, users.delete
userNinguno extra — gestiona su propio perfil, sus clientes OAuth y sus tokens.
+ +

Roles por app

+

Independientes del rol global: un admin le puede asignar a cualquier usuario un rol (admin o +user) específico de una app registrada, desde el panel de edición de usuario +("Roles por app"). Sirve para que, por ejemplo, un usuario sea admin dentro de la app Angular +pero solo user dentro de la app NestJS — sin tocar su rol global.

+

Esa información viaja en la respuesta de la API (app_roles, ver sección 8): cada app ve +únicamente el rol que ese usuario tiene en ella, resuelto automáticamente según el client_id +del token usado en la request.

+
+ +
+

7. Idioma (español / inglés / catalán)

+

El selector está arriba a la derecha en la barra de navegación (ES / EN / CA). Cambiarlo +hace un POST a /web-api/locale, guarda el idioma en la sesión y recarga la página — el servidor +decide el idioma con el que renderiza esa carga (mensajes de validación/autenticación vía +laravel-lang, más los textos propios de la app en lang/{es,en,ca}/messages.php).

+ + + + + +
IdiomaMensajes del servidorInterfaz (SPA Vue)
ES (español, por defecto)✓✓
EN (inglés)✓✓ — traducción completa (nav, botones, formularios, tablas, gráficos)
CA (catalán)✓✓ — traducción completa
+

La interfaz usa vue-i18n con diccionarios en resources/js/i18n/{es,en,ca}.js +(168 claves cada uno). El locale de arranque del frontend se inyecta desde el servidor +(window.__LOCALE__ en app.blade.php), así que la SPA nace ya en el idioma correcto +sin parpadeo.

+
+ +
+

8. Cómo conectar tu app (Angular, NestJS, Flutter, etc.)

+

Paso 1 — Registrar la app

+

Desde /clients, crear un cliente OAuth eligiendo el grant type según el tipo de app:

+ + + + + +
Tipo de appGrant recomendado
SPA (Angular, React) servida al navegadorAuthorization Code + PKCE, cliente público
Backend con secret propio (NestJS, Express)Authorization Code, cliente confidencial, o Client Credentials si no hay usuario final
Servicio interno máquina-a-máquinaClient Credentials
+ +

Paso 2 — Login del usuario (Authorization Code + PKCE, ej. Angular)

+

Redirigir el navegador a:

+
GET /oauth/authorize
+    ?client_id={client_id}
+    &redirect_uri={tu_callback}
+    &response_type=code
+    &scope=users:read users:write
+    &state={valor_random}
+    &code_challenge={challenge_pkce}
+    &code_challenge_method=S256
+

El usuario loguea (o ya tiene sesión) y aprueba el acceso. Passport redirige a redirect_uri +con ?code=...&state=....

+ +

Paso 3 — Canjear el código por un token

+
curl -X POST http://localhost:8000/oauth/token \
+  -d grant_type=authorization_code \
+  -d client_id={client_id} \
+  -d code={code} \
+  -d redirect_uri={tu_callback} \
+  -d code_verifier={verifier_pkce}
+ +

Alternativa — Client Credentials (ej. NestJS backend-a-backend)

+
curl -X POST http://localhost:8000/oauth/token \
+  -d grant_type=client_credentials \
+  -d client_id={client_id} \
+  -d client_secret={client_secret} \
+  -d scope="users:read"
+
Un token de client_credentials no tiene usuario asociado: actúa como +"servicio de confianza" y puede listar/crear/editar usuarios sin más chequeo que el scope del token.
+
+ +
+

9. API REST completa: construí tu propio panel admin

+

+Todo lo que hace la SPA Vue por sesión también está disponible por token OAuth2. Esto significa que un cliente +Angular, Flutter, o lo que sea, puede ser un admin completo — no solo consultar usuarios — +sin depender de la sesión de Laravel. Todos los endpoints requieren Authorization: Bearer {access_token}. +

+ +

Usuarios y roles (scopes users:read / users:write)

+ + + + + + + + + + + + + + + + + + +
MétodoRutaDescripción
GET/api/userWhoami: usuario del token actual, con roles y app_roles.
GET/api/usersListar usuarios (requiere users.view, salvo client_credentials).
POST/api/usersCrear usuario (requiere users.create).
GET/api/users/{id}Ver un usuario. Siempre permitido sobre uno mismo.
PUT/api/users/{id}Editar. Uno mismo puede cambiar nombre/email/contraseña; rol/estado y otros usuarios requieren users.update.
DELETE/api/users/{id}Borrar — soft delete, va a la papelera (requiere users.delete; no se puede autoeliminar).
GET/api/users/trashedListar usuarios en la papelera (requiere users.view).
POST/api/users/{id}/restoreRestaurar un usuario borrado (requiere users.update).
DELETE/api/users/{id}/forceBorrado definitivo, sin vuelta atrás (requiere users.delete; el usuario tiene que estar en la papelera).
GET/api/users/pendingListar registros pendientes de aprobación (requiere users.view).
POST/api/users/{id}/approveAprobar un registro pendiente (requiere users.update).
GET/api/users/{id}/app-rolesRoles del usuario por app (propio siempre permitido; otros requieren users.view).
POST/api/users/{id}/app-rolesAsignar un rol en una app (requiere users.update, sin excepción para uno mismo).
DELETE/api/users/{id}/app-roles/{clientId}/{role}Quitar un rol en una app (requiere users.update).
GET/api/appsListar todas las apps registradas (requiere users.view) — para armar el selector de roles-por-app.
GET/api/analytics/summary?days=30KPIs, logins por día/app, usuarios más activos y actividad reciente (requiere users.view).
+ +

Ejemplo de respuesta de usuario

+
{
+  "id": 2,
+  "name": "Carla Gómez",
+  "email": "carla@example.com",
+  "is_active": true,
+  "roles": ["user"],
+  "app_roles": ["admin"],
+  "created_at": "2026-07-25T08:52:39.000000Z"
+}
+

roles es el rol global; app_roles es el rol de ese usuario específicamente en la +app dueña del token usado para la request (útil para que cada app decida qué mostrar/permitir sin tener su +propio sistema de permisos). El campo deleted_at solo aparece (no-null) en las respuestas de +/api/users/trashed.

+ +

Cuenta propia: clientes, tokens y apps autorizadas (scopes account:read / account:write)

+

Equivalente por token a /web-api/clients, /personal-access-tokens y +/authorized-apps de la sección 4 — siempre sobre el usuario dueño del token (no aplica a +client_credentials, que no tiene usuario asociado):

+ + + + + + + + + + + +
MétodoRutaDescripción
GET/api/clientsListar tus clientes OAuth (apps que registraste).
POST/api/clientsRegistrar un cliente nuevo (devuelve plain_secret una sola vez).
PUT/api/clients/{id}Editar nombre/redirect URIs.
DELETE/api/clients/{id}Revocar el cliente y sus tokens.
GET/api/personal-access-tokensListar tus Personal Access Tokens.
POST/api/personal-access-tokensCrear uno (el token se muestra una sola vez).
DELETE/api/personal-access-tokens/{id}Revocarlo.
GET/api/authorized-appsApps de terceros a las que le diste acceso.
DELETE/api/authorized-apps/{tokenId}Revocarle el acceso a una.
+ +

Scopes disponibles

+ + + + + + +
ScopePermite
users:readLeer usuarios y roles por app
users:writeCrear, editar y borrar usuarios; asignar roles por app
account:readLeer tus clientes OAuth, tokens y apps autorizadas
account:writeGestionar tus clientes OAuth, tokens y apps autorizadas
+

No hay scope por defecto: cada app debe pedir explícitamente los que necesita al iniciar el flujo OAuth +(ej. scope=users:read users:write account:read account:write para un admin completo).

+
+ Client credentials y recursos self-service: un token client_credentials no + tiene usuario asociado, así que /api/clients, /api/personal-access-tokens y + /api/authorized-apps le devuelven 422 (no hay "cuenta propia" que gestionar). + Sí puede usar /api/users y /api/apps como servicio de confianza. +
+
+ +
+

10. Documentación interactiva (Swagger)

+

Disponible en http://localhost:8000/api/documentation. Incluye el botón Authorize +para probar los tres flujos (Authorization Code, Client Credentials, Password) directamente desde el navegador, +usando el cliente de ejemplo "Swagger UI" sembrado (sección 3).

+

El JSON crudo de la especificación OpenAPI está en /docs.

+
+ +
+

11. Referencia rápida de rutas

+

Interfaz web (sesión, uso interno de esta app)

+ + + + + + + + + + + + + + + + + + +
RutaQué hace
POST /web-api/registerRegistro — crea la cuenta pendiente, no loguea.
POST /web-api/login / /logoutAutenticación por sesión.
GET /web-api/meUsuario logueado (con roles/permisos).
GET/POST/PUT/DELETE /web-api/clientsClientes OAuth propios.
GET/POST/DELETE /web-api/personal-access-tokensPersonal Access Tokens propios.
GET/DELETE /web-api/authorized-appsApps de terceros autorizadas.
GET/POST/PUT/DELETE /web-api/admin/usersAdministración de usuarios (admin).
GET /web-api/admin/users/trashedUsuarios en la papelera (admin).
POST /web-api/admin/users/{id}/restoreRestaurar un usuario borrado (admin).
DELETE /web-api/admin/users/{id}/forceBorrado definitivo (admin).
GET /web-api/admin/users/pendingRegistros pendientes de aprobación (admin).
POST /web-api/admin/users/{id}/approveAprobar un registro pendiente (admin).
GET /web-api/admin/appsListado de todas las apps registradas (admin).
GET/POST/DELETE /web-api/admin/users/{id}/app-rolesRoles por app de un usuario (admin).
GET /web-api/admin/analytics/summarySesiones, logins y actividad (admin).
POST /web-api/localeCambiar idioma (es/en/ca).
+ +

OAuth2 (Passport)

+ + + + +
RutaQué hace
GET /oauth/authorizeInicio del flujo Authorization Code (pantalla de consentimiento).
POST /oauth/tokenEmisión/renovación de tokens (todos los grant types).
+ +

API REST (Bearer token, para apps externas — Angular, Flutter, lo que sea)

+ + + + + + + + + + + + + + + +
RutaQué hace
GET /api/userWhoami.
GET/POST/PUT/DELETE /api/usersGestión de usuarios (DELETE = papelera).
GET /api/users/trashedUsuarios en la papelera.
POST /api/users/{id}/restoreRestaurar un usuario borrado.
DELETE /api/users/{id}/forceBorrado definitivo.
GET /api/users/pendingRegistros pendientes de aprobación.
POST /api/users/{id}/approveAprobar un registro pendiente.
GET/POST/DELETE /api/users/{id}/app-rolesRoles por app de un usuario.
GET /api/appsListado de apps registradas.
GET /api/analytics/summarySesiones, logins y actividad.
GET/POST/PUT/DELETE /api/clientsTus clientes OAuth.
GET/POST/DELETE /api/personal-access-tokensTus Personal Access Tokens.
GET/DELETE /api/authorized-appsApps de terceros que autorizaste.
+ +
OAuth2 Identity Server — Manual de uso · generado automáticamente a partir del estado actual del proyecto.
+
+ + + diff --git a/docs/manual-usuario.pdf b/docs/manual-usuario.pdf new file mode 100644 index 0000000..f1ed30c Binary files /dev/null and b/docs/manual-usuario.pdf differ diff --git a/lang/ca.json b/lang/ca.json new file mode 100644 index 0000000..5eee002 --- /dev/null +++ b/lang/ca.json @@ -0,0 +1,229 @@ +{ + "(and :count more error)": "(i :count error més)", + "(and :count more errors)": "(i :count error més)|(i :count errors més)|(i :count errors més)", + "A decryption key is required.": "Cal una clau de desxifrat.", + "A Timeout Occurred": "S'ha produït un temps mort", + "Accept": "Acceptar", + "Accepted": "Acceptat", + "Action": "Acció", + "Actions": "Accions", + "Add": "Afegir", + "Add :name": "Afegeix :name", + "Admin": "Admin", + "Agree": "Acordar", + "All rights reserved.": "Tots els drets reservats.", + "Already Reported": "Ja s'ha informat", + "Archive": "Arxiu", + "Assign": "Assignar", + "Associate": "Associat", + "Attach": "Adjuntar", + "Bad Gateway": "Porta d'enllaç incorrecta", + "Bad Request": "Petició dolenta", + "Bandwidth Limit Exceeded": "Límit d'ample de banda excedit", + "Browse": "Navega", + "Cancel": "Cancel·la", + "Choose": "Triar", + "Choose :name": "Trieu :name", + "Choose File": "Tria el fitxer", + "Choose Image": "Trieu Imatge", + "Click to copy": "Feu clic per copiar", + "Client Closed Request": "Sol·licitud de client tancada", + "Close": "Tancar", + "Collapse": "Col·lapse", + "Collapse All": "Col · lapsar tot", + "Comment": "Comenta", + "Confirm": "Confirmar", + "Conflict": "Conflicte", + "Connect": "Connecta't", + "Connection Closed Without Response": "Connexió tancada sense resposta", + "Connection Timed Out": "Temps de connexió esgotat", + "Continue": "Continua", + "Create": "Crear", + "Create :name": "Crea :name", + "Created": "Creat", + "Delete": "Esborrar", + "Delete :name": "Suprimeix :name", + "Detach": "Separa", + "Details": "Detalls", + "Disable": "Deshabilitar", + "Discard": "Descartar", + "Done": "Fet", + "Down": "Avall", + "Duplicate": "Duplicat", + "Duplicate :name": "Duplicat: nom", + "Edit": "Edita", + "Edit :name": "Edita :name", + "Enable": "Habilitar", + "Encrypted environment file already exists.": "El fitxer d'entorn xifrat ja existeix.", + "Encrypted environment file not found.": "No s'ha trobat el fitxer d'entorn xifrat.", + "Environment file already exists.": "El fitxer d'entorn ja existeix.", + "Environment file not found.": "No s'ha trobat el fitxer d'entorn.", + "errors": "errors", + "Expand": "Ampliar", + "Expand All": "Amplia-ho tot", + "Expectation Failed": "L'expectativa ha fallat", + "Explanation": "Explicació", + "Export": "Exporta", + "Export :name": "Export :name", + "Failed Dependency": "Dependència fallida", + "File": "Dossier", + "Files": "Fitxers", + "Forbidden": "Prohibit", + "Found": "Trobat", + "Gateway Timeout": "Temps d'espera de la passarel·la", + "Go Home": "Aneu a l'inici", + "Go to page :page": "Aneu a la pàgina :page", + "Gone": "Desaparegut", + "Hello!": "Hola!", + "Hide": "Amaga", + "Hide :name": "Amaga :name", + "Home": "Inici", + "HTTP Version Not Supported": "Versió HTTP no compatible", + "I'm a teapot": "Sóc una tetera", + "If you did not create an account, no further action is required.": "Si no heu creat cap compte, no es requereix cap acció adicional.", + "If you did not request a password reset, no further action is required.": "Si no heu sol·licitat el restabliment de la contrasenya, obvieu aquest correu electrònic.", + "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Si té algun problema per a fer clic al botó \":actionText\", copii i enganxi la següent URL al seu navegador web", + "IM Used": "IM utilitzat", + "Image": "Imatge", + "Impersonate": "Suplantar", + "Impersonation": "Suplantació d'identitat", + "Import": "Importar", + "Import :name": "Importació :name", + "Insufficient Storage": "Emmagatzematge insuficient", + "Internal Server Error": "error del servidor intern", + "Introduction": "Introducció", + "Invalid filename.": "Nom de fitxer no vàlid.", + "Invalid JSON was returned from the route.": "S'ha retornat un JSON no vàlid des de la ruta.", + "Invalid SSL Certificate": "Certificat SSL no vàlid", + "Length Required": "Longitud requerida", + "Like": "M'agrada", + "Load": "Carrega", + "Localize": "Localitza", + "Location": "Ubicació", + "Locked": "Tancat", + "Log In": "Entrar", + "Log Out": "Sortir", + "Login": "Entrar", + "Logout": "Sortir", + "Loop Detected": "Bucle detectat", + "Maintenance Mode": "mode de manteniment", + "Method Not Allowed": "Mètode no permès", + "Misdirected Request": "Sol·licitud mal dirigida", + "Moved Permanently": "Mogut permanentment", + "Multi-Status": "Multiestatus", + "Multiple Choices": "Opcions múltiples", + "Network Authentication Required": "Es requereix autenticació de xarxa", + "Network Connect Timeout Error": "Error de temps d'espera de connexió a la xarxa", + "Network Read Timeout Error": "Error de temps d'espera de lectura de la xarxa", + "New": "Nous", + "New :name": "Nou :name", + "No": "No", + "No Content": "Sense contingut", + "Non-Authoritative Information": "Informació no autoritzada", + "Not Acceptable": "No és acceptable", + "Not Extended": "No ampliat", + "Not Found": "No trobat", + "Not Implemented": "No implementat", + "Not Modified": "No modificat", + "of": "de", + "OK": "D'acord", + "Open": "Obert", + "Open in a current window": "Obre en una finestra actual", + "Open in a new window": "Obre en una finestra nova", + "Open in a parent frame": "Obre en un marc principal", + "Open in the topmost frame": "Obre al marc superior", + "Open on the website": "Obert al lloc web", + "Origin Is Unreachable": "L'origen és inabastable", + "Page Expired": "Pàgina caducada", + "Pagination Navigation": "Navegació de la paginació", + "Partial Content": "Contingut parcial", + "Payload Too Large": "Càrrega útil massa gran", + "Payment Required": "Pagament obligatori", + "Permanent Redirect": "Redirecció permanent", + "Please click the button below to verify your email address.": "Si us plau, feu clic al botó inferior per verificar la vostra adreça electrònica.", + "Precondition Failed": "La condició prèvia ha fallat", + "Precondition Required": "Condició prèvia requerida", + "Preview": "Vista prèvia", + "Price": "Preu", + "Processing": "Processament", + "Proxy Authentication Required": "Es requereix autenticació de proxy", + "Railgun Error": "Error de canó de ferrocarril", + "Range Not Satisfiable": "Interval no satisfactori", + "Record": "Registre", + "Regards,": "Salutacions,", + "Register": "Registre", + "Request Header Fields Too Large": "Els camps de la capçalera de la sol·licitud són massa grans", + "Request Timeout": "Temps d'espera esgotat", + "Reset Content": "Restablir contingut", + "Reset Password": "Restablir contrasenya", + "Reset your password": "Restableix la teva contrasenya", + "Restore": "Restauració", + "Restore :name": "Restaurar :name", + "results": "resultats", + "Retry With": "Torna-ho a provar amb", + "Save": "Desar", + "Save & Close": "Desa i tanca", + "Save & Return": "Guarda i torna", + "Save :name": "Estalvia :name", + "Search": "Cerca", + "Search :name": "Cerca :name", + "See Other": "Vegeu Altres", + "Select": "Seleccioni", + "Select All": "Seleccionar tot", + "Send": "Enviar", + "Server Error": "Error del servidor", + "Service Unavailable": "Servei no disponible", + "Session Has Expired": "La sessió ha caducat", + "Settings": "Configuració", + "Show": "Espectacle", + "Show :name": "Mostra :name", + "Show All": "Mostrar tots", + "Showing": "Mostrant", + "Sign In": "Inicieu la sessió", + "Solve": "Resol", + "SSL Handshake Failed": "SSL Handshake ha fallat", + "Start": "Començar", + "Stop": "Atura", + "Submit": "Presentar", + "Subscribe": "Subscrigui", + "Switch": "Interruptor", + "Switch To Role": "Canvia al rol", + "Switching Protocols": "Protocols de commutació", + "Tag": "Etiqueta", + "Tags": "Etiquetes", + "Temporary Redirect": "Redirecció temporal", + "The given data was invalid.": "Les dades proporcionades no eren vàlides.", + "The response is not a streamed response.": "La resposta no és una resposta en streaming.", + "The response is not a view.": "La resposta no és una visió.", + "This action is unauthorized.": "Aquesta acció no està autoritzada.", + "This password reset link will expire in :count minutes.": "Aquest enllaç de restabliment de contrasenya caducarà en :count minuts.", + "to": "a", + "Toggle navigation": "Commutar navegació", + "Too Early": "Massa aviat", + "Too Many Requests": "Massa peticions", + "Translate": "Traduir", + "Translate It": "Tradueix-ho", + "Unauthorized": "No autoritzat", + "Unavailable For Legal Reasons": "No disponible per motius legals", + "Unknown Error": "Error desconegut", + "Unpack": "Desempaquetar", + "Unprocessable Entity": "Entitat no processable", + "Unsubscribe": "Cancel·la la subscripció", + "Unsupported Media Type": "Tipus de suport no compatible", + "Up": "Amunt", + "Update": "Actualització", + "Update :name": "Actualització :name", + "Upgrade Required": "Actualització necessària", + "URI Too Long": "URI massa llarg", + "Use Proxy": "Utilitzeu el proxy", + "User": "Usuari", + "Variant Also Negotiates": "La variant també es negocia", + "Verify Email Address": "Confirmeu la vostra adreça electrònica", + "Verify your email address": "Verifica la teva adreça de correu electrònic", + "View": "Vista", + "View :name": "Vista :name", + "Web Server is Down": "El servidor web està caigut", + "Whoops!": "Vaja!", + "Yes": "Sí", + "You are receiving this email because we received a password reset request for your account.": "Heu rebut aquest correu electrònic perquè s'ha solicitat el restabliment de la contrasenya per al vostre compte." +} \ No newline at end of file diff --git a/lang/ca/actions.php b/lang/ca/actions.php new file mode 100644 index 0000000..2c5f748 --- /dev/null +++ b/lang/ca/actions.php @@ -0,0 +1,119 @@ + 'Acceptar', + 'action' => 'Acció', + 'actions' => 'Accions', + 'add' => 'Afegeix', + 'admin' => 'Admin', + 'agree' => 'Acordar', + 'archive' => 'Arxiu', + 'assign' => 'Assignar', + 'associate' => 'Associat', + 'attach' => 'Adjuntar', + 'browse' => 'Navega', + 'cancel' => 'Cancel · lar', + 'choose' => 'Tria', + 'choose_file' => 'Trieu l\'arxiu', + 'choose_image' => 'Trieu Imatge', + 'click_to_copy' => 'Feu clic per copiar', + 'close' => 'Tanca', + 'collapse' => 'Col·lapse', + 'collapse_all' => 'Col · lapsar tot', + 'comment' => 'Comenta', + 'confirm' => 'Confirmeu', + 'connect' => 'Connecta\'t', + 'create' => 'Crear', + 'delete' => 'Suprimeix', + 'detach' => 'Separar', + 'details' => 'Detalls', + 'disable' => 'Desactivar', + 'discard' => 'Descartar', + 'done' => 'Fet', + 'down' => 'Avall', + 'duplicate' => 'Duplicat', + 'edit' => 'Edita', + 'enable' => 'Activa', + 'expand' => 'Ampliar', + 'expand_all' => 'Amplia-ho tot', + 'explanation' => 'Explicació', + 'export' => 'Exporta', + 'file' => 'Dossier', + 'files' => 'Fitxers', + 'go_home' => 'Aneu a l\'inici', + 'hide' => 'Amaga', + 'home' => 'Inici', + 'image' => 'Imatge', + 'impersonate' => 'Suplantar la identitat', + 'impersonation' => 'Suplantació d\'identitat', + 'import' => 'Importar', + 'introduction' => 'Introducció', + 'like' => 'M\'agrada', + 'load' => 'Carrega', + 'localize' => 'Localitza', + 'log_in' => 'Iniciar Sessió', + 'log_out' => 'Tancar sessió', + 'named' => [ + 'add' => 'Afegeix :name', + 'choose' => 'Trieu :name', + 'create' => 'Crea :name', + 'delete' => 'Suprimeix :name', + 'duplicate' => 'Duplicat: nom', + 'edit' => 'Edita :name', + 'export' => 'Export :name', + 'hide' => 'Amaga :name', + 'import' => 'Importació :name', + 'new' => 'Nou :name', + 'restore' => 'Restaurar :name', + 'save' => 'Estalvia :name', + 'search' => 'Cerca :name', + 'show' => 'Mostra :name', + 'update' => 'Actualització :name', + 'view' => 'Vista :name', + ], + 'new' => 'Nou', + 'no' => 'No', + 'open' => 'Obert', + 'open_website' => 'Obert al lloc web', + 'preview' => 'Vista prèvia', + 'price' => 'Preu', + 'record' => 'Registre', + 'restore' => 'Restaurar', + 'save' => 'Desa', + 'save_and_close' => 'Desa i tanca', + 'save_and_return' => 'Guarda i torna', + 'search' => 'Cerca', + 'select' => 'Seleccioneu', + 'select_all' => 'Seleccionar tot', + 'send' => 'Enviar', + 'settings' => 'Configuració', + 'show' => 'Espectacle', + 'show_all' => 'Mostrar tots', + 'sign_in' => 'Inicieu la sessió', + 'solve' => 'Resol', + 'start' => 'Començar', + 'stop' => 'Atura', + 'submit' => 'Presentar', + 'subscribe' => 'Subscriu-te', + 'switch' => 'Interruptor', + 'switch_to_role' => 'Canvia al rol', + 'tag' => 'Etiqueta', + 'tags' => 'Etiquetes', + 'target_link' => [ + 'blank' => 'Obre en una finestra nova', + 'parent' => 'Obre en un marc principal', + 'self' => 'Obre en una finestra actual', + 'top' => 'Obre al marc superior', + ], + 'translate' => 'Traduir', + 'translate_it' => 'Tradueix-ho', + 'unpack' => 'Desempaquetar', + 'unsubscribe' => 'Cancel·la la subscripció', + 'up' => 'Amunt', + 'update' => 'Actualització', + 'user' => 'Usuari', + 'view' => 'Veure', + 'yes' => 'Sí', +]; diff --git a/lang/ca/auth.php b/lang/ca/auth.php new file mode 100644 index 0000000..8d7e7a6 --- /dev/null +++ b/lang/ca/auth.php @@ -0,0 +1,9 @@ + 'Aquestes credencials no concorden amb els nostres registres.', + 'password' => 'La contrasenya és incorrecta.', + 'throttle' => 'Ha superat el nombre màxim d\'intents d\'accés. Si us plau, torni a intentar-ho en :seconds segons.', +]; diff --git a/lang/ca/http-statuses.php b/lang/ca/http-statuses.php new file mode 100644 index 0000000..7397e01 --- /dev/null +++ b/lang/ca/http-statuses.php @@ -0,0 +1,84 @@ + 'Error desconegut', + '100' => 'Continua', + '101' => 'Protocols de commutació', + '102' => 'Processament', + '200' => 'D\'acord', + '201' => 'Creat', + '202' => 'Acceptat', + '203' => 'Informació no autoritzada', + '204' => 'Sense contingut', + '205' => 'Restablir contingut', + '206' => 'Contingut parcial', + '207' => 'Multiestatus', + '208' => 'Ja s\'ha informat', + '226' => 'IM utilitzat', + '300' => 'Opcions múltiples', + '301' => 'Mogut permanentment', + '302' => 'Trobat', + '303' => 'Vegeu Altres', + '304' => 'No modificat', + '305' => 'Utilitzeu el proxy', + '307' => 'Redirecció temporal', + '308' => 'Redirecció permanent', + '400' => 'Petició dolenta', + '401' => 'No autoritzat', + '402' => 'Pagament obligatori', + '403' => 'Prohibit', + '404' => 'No trobat', + '405' => 'Mètode no permès', + '406' => 'No és acceptable', + '407' => 'Es requereix autenticació de proxy', + '408' => 'Temps d\'espera esgotat', + '409' => 'Conflicte', + '410' => 'Desaparegut', + '411' => 'Longitud requerida', + '412' => 'La condició prèvia ha fallat', + '413' => 'Càrrega útil massa gran', + '414' => 'URI massa llarg', + '415' => 'Tipus de suport no compatible', + '416' => 'Interval no satisfactori', + '417' => 'L\'expectativa ha fallat', + '418' => 'Sóc una tetera', + '419' => 'La sessió ha caducat', + '421' => 'Sol·licitud mal dirigida', + '422' => 'Entitat no processable', + '423' => 'Tancat', + '424' => 'Dependència fallida', + '425' => 'Massa aviat', + '426' => 'Actualització necessària', + '428' => 'Condició prèvia requerida', + '429' => 'Massa sol·licituds', + '431' => 'Els camps de la capçalera de la sol·licitud són massa grans', + '444' => 'Connexió tancada sense resposta', + '449' => 'Torna-ho a provar amb', + '451' => 'No disponible per motius legals', + '499' => 'Sol·licitud de client tancada', + '500' => 'error del servidor intern', + '501' => 'No implementat', + '502' => 'Porta d\'enllaç incorrecta', + '503' => 'mode de manteniment', + '504' => 'Temps d\'espera de la passarel·la', + '505' => 'Versió HTTP no compatible', + '506' => 'La variant també es negocia', + '507' => 'Emmagatzematge insuficient', + '508' => 'Bucle detectat', + '509' => 'Límit d\'ample de banda excedit', + '510' => 'No ampliat', + '511' => 'Es requereix autenticació de xarxa', + '520' => 'Error desconegut', + '521' => 'El servidor web està caigut', + '522' => 'Temps de connexió esgotat', + '523' => 'L\'origen és inabastable', + '524' => 'S\'ha produït un temps mort', + '525' => 'SSL Handshake ha fallat', + '526' => 'Certificat SSL no vàlid', + '527' => 'Error de canó de ferrocarril', + '598' => 'Error de temps d\'espera de lectura de la xarxa', + '599' => 'Error de temps d\'espera de connexió a la xarxa', + 'unknownError' => 'Error desconegut', +]; diff --git a/lang/ca/messages.php b/lang/ca/messages.php new file mode 100644 index 0000000..a8ea3a7 --- /dev/null +++ b/lang/ca/messages.php @@ -0,0 +1,13 @@ + 'Credencials no vàlides.', + 'account_deactivated' => 'Aquest compte està desactivat.', + 'account_pending_approval' => 'El teu compte està pendent d\'aprovació per un administrador.', + 'registration_pending' => 'Compte creat. Un administrador l\'ha d\'aprovar abans que puguis entrar.', + 'cannot_remove_own_admin_role' => 'No et pots treure el rol d\'admin a tu mateix.', + 'cannot_delete_self' => 'No pots esborrar el teu propi compte.', + 'cannot_delete_last_admin' => 'No pots esborrar l\'últim administrador.', + 'self_service_requires_user' => 'Aquest recurs és self-service: necessita un token amb un usuari associat (no funciona amb client_credentials).', + 'user_not_trashed' => 'Aquest usuari no és a la paperera.', +]; diff --git a/lang/ca/pagination.php b/lang/ca/pagination.php new file mode 100644 index 0000000..b59cafe --- /dev/null +++ b/lang/ca/pagination.php @@ -0,0 +1,8 @@ + 'Següent »', + 'previous' => '« Anterior', +]; diff --git a/lang/ca/passwords.php b/lang/ca/passwords.php new file mode 100644 index 0000000..c99cdb2 --- /dev/null +++ b/lang/ca/passwords.php @@ -0,0 +1,11 @@ + 'La contrasenya s\'ha restablert!', + 'sent' => 'Li hem enviat un correu electrònic amb un enllaç per a reiniciar la teva contrasenya!', + 'throttled' => 'Si us plau, esperi abans de tornar-ho a intentar.', + 'token' => 'Aquest token de recuperació de contrasenya és invàlid.', + 'user' => 'No existeix cap usuari amb aquest correu.', +]; diff --git a/lang/ca/validation.php b/lang/ca/validation.php new file mode 100644 index 0000000..210bc80 --- /dev/null +++ b/lang/ca/validation.php @@ -0,0 +1,289 @@ + ':Attribute ha de ser acceptat.', + 'accepted_if' => 'El :attribute s\'ha d\'acceptar quan :other és :value.', + 'active_url' => ':Attribute no és un URL vàlid.', + 'after' => ':Attribute ha de ser una data posterior a :date.', + 'after_or_equal' => ':Attribute ha de ser una data posterior o igual a :date.', + 'alpha' => ':Attribute només pot contenir lletres.', + 'alpha_dash' => ':Attribute només pot contenir lletres, números i guions.', + 'alpha_num' => ':Attribute només pot contenir lletres i números.', + 'any_of' => 'El camp :attribute no és vàlid.', + 'array' => ':Attribute ha de ser una matriu.', + 'ascii' => 'El :attribute només ha de contenir caràcters i símbols alfanumèrics d\'un sol byte.', + 'base64' => 'The :attribute field must be a valid Base64 string.', + 'before' => ':Attribute ha de ser una data anterior a :date.', + 'before_or_equal' => ':Attribute ha de ser una data anterior o igual a :date.', + 'between' => [ + 'array' => ':Attribute ha de tenir entre :min - :max ítems.', + 'file' => ':Attribute ha de pesar entre :min - :max kilobytes.', + 'numeric' => ':Attribute ha d\'estar entre :min - :max.', + 'string' => ':Attribute ha de tenir entre :min - :max caràcters.', + ], + 'boolean' => 'El camp :attribute ha de ser verdader o fals', + 'can' => 'El camp :attribute conté un valor no autoritzat.', + 'confirmed' => 'La confirmació de :attribute no coincideix.', + 'contains' => 'Al camp :attribute li falta un valor obligatori.', + 'current_password' => 'La contrasenya és incorrecta.', + 'date' => ':Attribute no és una data vàlida.', + 'date_equals' => 'El :attribute ha de ser una data igual a :date.', + 'date_format' => 'El camp :attribute no concorda amb el format :format.', + 'decimal' => 'El :attribute ha de tenir :decimal decimals.', + 'declined' => 'Els :attribute s\'han de rebutjar.', + 'declined_if' => 'El :attribute s\'ha de rebutjar quan :other és :value.', + 'different' => ':Attribute i :other han de ser diferents.', + 'digits' => ':Attribute ha de tenir :digits dígits.', + 'digits_between' => ':Attribute ha de tenir entre :min i :max dígits.', + 'dimensions' => 'Les dimensions de la imatge :attribute no són vàlides.', + 'distinct' => 'El camp :attribute té un valor duplicat.', + 'doesnt_contain' => 'El camp :attribute no ha de contenir cap dels següents: :values.', + 'doesnt_end_with' => 'El :attribute no pot acabar amb un dels següents: :values.', + 'doesnt_start_with' => 'El :attribute no pot començar amb un dels següents: :values.', + 'email' => ':Attribute no és un e-mail vàlid', + 'encoding' => 'El camp :attribute s\'ha de codificar en :encoding.', + 'ends_with' => 'La :attribute ha d\'acabar amb una de les següents: :values.', + 'enum' => 'El :attribute seleccionat no és vàlid.', + 'exists' => ':Attribute és invàlid.', + 'extensions' => 'El camp :attribute ha de tenir una de les extensions següents: :values.', + 'file' => 'El camp :attribute ha de ser un arxiu.', + 'filled' => 'El camp :attribute és obligatori.', + 'gt' => [ + 'array' => 'El :attribute ha de tenir més de :value ítems.', + 'file' => 'El :attribute ha de ser superior a :value kilobytes.', + 'numeric' => 'El :attribute ha de ser superior a :value.', + 'string' => 'El :attribute ha de superar els :value caràcters.', + ], + 'gte' => [ + 'array' => 'El :attribute ha de tenir :value ítems o més.', + 'file' => 'El :attribute ha de ser igual o superior a :value kilobytes.', + 'numeric' => 'El :attribute ha de ser igual o superior a :value.', + 'string' => 'El :attribute ha de ser igual o superior a :value caràcters.', + ], + 'hex_color' => 'El camp :attribute ha de ser un color hexadecimal vàlid.', + 'image' => ':Attribute ha de ser una imatge.', + 'in' => ':Attribute és invàlid', + 'in_array' => 'El camp :attribute no existeix dintre de :other.', + 'in_array_keys' => 'El camp :attribute ha de contenir almenys una de les tecles següents: :values.', + 'integer' => ':Attribute ha de ser un nombre enter.', + 'ip' => ':Attribute ha de ser una adreça IP vàlida.', + 'ipv4' => ':Attribute ha de ser una adreça IPv4 vàlida.', + 'ipv6' => ':Attribute ha de ser una adreça IPv6 vàlida.', + 'json' => 'El camp :attribute ha de ser una cadena JSON vàlida.', + 'list' => 'El camp :attribute ha de ser una llista.', + 'lowercase' => 'El :attribute ha d\'anar en minúscula.', + 'lt' => [ + 'array' => 'El :attribute ha de tenir menys de :value ítems.', + 'file' => 'El :attribute ha de ser inferior a :value kilobytes.', + 'numeric' => 'El :attribute ha de ser inferior a :value.', + 'string' => 'El :attribute no ha de superar els :value caràcters.', + ], + 'lte' => [ + 'array' => 'El :attribute no ha de tenir més de :value ítems.', + 'file' => 'El :attribute ha de ser igual o inferior a :value kilobytes.', + 'numeric' => 'El :attribute ha de ser igual o inferior a :value.', + 'string' => 'El :attribute ha de ser igual o inferior a :value caràcters.', + ], + 'mac_address' => 'El :attribute ha de ser una adreça MAC vàlida.', + 'max' => [ + 'array' => ':Attribute no pot tenir més de :max ítems.', + 'file' => ':Attribute no pot ser més gran que :max kilobytes.', + 'numeric' => ':Attribute no pot ser més gran que :max.', + 'string' => ':Attribute no pot ser més gran que :max caràcters.', + ], + 'max_digits' => 'El :attribute no ha de tenir més de :max dígits.', + 'mimes' => ':Attribute ha de ser un arxiu amb format: :values.', + 'mimetypes' => ':Attribute ha de ser un arxiu amb format: :values.', + 'min' => [ + 'array' => ':Attribute ha de tenir almenys :min ítems.', + 'file' => 'La mida de :attribute ha de ser d\'almenys :min kilobytes.', + 'numeric' => 'La mida de :attribute ha de ser d\'almenys :min.', + 'string' => ':Attribute ha de contenir almenys :min caràcters.', + ], + 'min_digits' => 'El :attribute ha de tenir almenys :min dígits.', + 'missing' => 'El camp :attribute ha de faltar.', + 'missing_if' => 'El camp :attribute ha de faltar quan :other és :value.', + 'missing_unless' => 'El camp :attribute ha de faltar, tret que :other sigui :value.', + 'missing_with' => 'El camp :attribute ha de faltar quan hi ha :values.', + 'missing_with_all' => 'El camp :attribute ha de faltar quan n\'hi ha :values.', + 'multiple_of' => 'La :attribute ha de ser un múltiple de :value', + 'not_in' => ':Attribute és invàlid.', + 'not_regex' => 'El format de :attribute no és vàlid.', + 'numeric' => ':Attribute ha de ser numèric.', + 'password' => [ + 'letters' => 'El :attribute ha de contenir almenys una lletra.', + 'mixed' => 'El :attribute ha de contenir almenys una lletra majúscula i una minúscula.', + 'numbers' => 'El :attribute ha de contenir almenys un número.', + 'symbols' => 'El :attribute ha de contenir almenys un símbol.', + 'uncompromised' => 'El :attribute donat ha aparegut en una filtració de dades. Si us plau, trieu un :attribute diferent.', + ], + 'present' => 'El camp :attribute ha d\'existir.', + 'present_if' => 'El camp :attribute ha d\'estar present quan :other és :value.', + 'present_unless' => 'El camp :attribute ha d\'estar present tret que :other sigui :value.', + 'present_with' => 'El camp :attribute ha d\'estar present quan n\'hi ha :values.', + 'present_with_all' => 'El camp :attribute ha d\'estar present quan n\'hi ha :values.', + 'prohibited' => 'La :attribute camp està prohibit.', + 'prohibited_if' => 'La :attribute camp és prohibida quan :other és :value.', + 'prohibited_if_accepted' => 'El camp :attribute està prohibit quan s’accepta :other.', + 'prohibited_if_declined' => 'El camp :attribute està prohibit quan es declina :other.', + 'prohibited_unless' => 'La :attribute camp és prohibida, tret que :other és en :values.', + 'prohibits' => 'El camp :attribute prohibeix la presència de :other.', + 'regex' => 'El format de :attribute és invàlid.', + 'required' => 'El camp :attribute és obligatori.', + 'required_array_keys' => 'El camp :attribute ha de contenir entrades per a: :values.', + 'required_if' => 'El camp :attribute és obligatori quan :other és :value.', + 'required_if_accepted' => 'El camp :attribute és obligatori quan s\'accepta :other.', + 'required_if_declined' => 'El camp :attribute és obligatori quan es rebutja :other.', + 'required_unless' => 'El camp :attribute és obligatori a no ser que :other sigui a :values.', + 'required_with' => 'El camp :attribute és obligatori quan hi ha :values.', + 'required_with_all' => 'El camp :attribute és obligatori quan hi ha :values.', + 'required_without' => 'El camp :attribute és obligatori quan no hi ha :values.', + 'required_without_all' => 'El camp :attribute és obligatori quan no hi ha cap valor dels següents: :values.', + 'same' => ':Attribute i :other han de coincidir.', + 'size' => [ + 'array' => ':Attribute ha de contenir :size ítems.', + 'file' => 'La mida de :attribute ha de ser :size kilobytes.', + 'numeric' => 'La mida de :attribute ha de ser :size.', + 'string' => ':Attribute ha de contenir :size caràcters.', + ], + 'starts_with' => 'El :attribute ha de començar per un dels valors següents: :values', + 'string' => 'El camp :attribute ha de ser una cadena.', + 'timezone' => 'El camp :attribute ha de ser una zona vàlida.', + 'ulid' => 'El :attribute ha de ser un ULID vàlid.', + 'unique' => ':Attribute ja està registrat i no es pot repetir.', + 'uploaded' => ':Attribute ha fallat al pujar.', + 'uppercase' => 'El :attribute ha d\'anar en majúscula.', + 'url' => ':Attribute no és una adreça web vàlida.', + 'uuid' => 'El :attribute ha de ser un indentificador únic universal (UUID) vàlid.', + 'attributes' => [ + 'address' => 'adreça', + 'affiliate_url' => 'URL d\'afiliat', + 'age' => 'edat', + 'amount' => 'quantitat', + 'announcement' => 'anunci', + 'area' => 'zona', + 'audience_prize' => 'premi del públic', + 'audience_winner' => 'audience winner', + 'available' => 'disponible', + 'birthday' => 'aniversari', + 'body' => 'cos', + 'city' => 'ciutat', + 'color' => 'color', + 'company' => 'company', + 'compilation' => 'compilació', + 'concept' => 'concepte', + 'conditions' => 'condicions', + 'content' => 'contingut', + 'contest' => 'contest', + 'country' => 'país', + 'cover' => 'coberta', + 'created_at' => 'creat a', + 'creator' => 'creador', + 'currency' => 'moneda', + 'current_password' => 'Contrasenya actual', + 'customer' => 'client', + 'date' => 'data', + 'date_of_birth' => 'data de naixement', + 'dates' => 'dates', + 'day' => 'dia', + 'deleted_at' => 'esborrat a les', + 'description' => 'descripció', + 'display_type' => 'tipus de visualització', + 'district' => 'districte', + 'duration' => 'durada', + 'email' => 'adreça electrònica', + 'excerpt' => 'extracte', + 'filter' => 'filtre', + 'finished_at' => 'acabat a les', + 'first_name' => 'nom', + 'gender' => 'gènere', + 'grand_prize' => 'gran premi', + 'group' => 'grup', + 'hour' => 'hora', + 'image' => 'imatge', + 'image_desktop' => 'imatge d\'escriptori', + 'image_main' => 'imatge principal', + 'image_mobile' => 'imatge mòbil', + 'images' => 'imatges', + 'is_audience_winner' => 'és el guanyador del públic', + 'is_hidden' => 'està amagat', + 'is_subscribed' => 'està subscrit', + 'is_visible' => 'és visible', + 'is_winner' => 'és el guanyador', + 'items' => 'elements', + 'key' => 'clau', + 'last_name' => 'cognom', + 'lesson' => 'lliçó', + 'line_address_1' => 'adreça de línia 1', + 'line_address_2' => 'adreça de línia 2', + 'login' => 'iniciar Sessió', + 'message' => 'missatge', + 'middle_name' => 'segon nom', + 'minute' => 'minut', + 'mobile' => 'mòbil', + 'month' => 'mes', + 'name' => 'nom', + 'national_code' => 'codi nacional', + 'number' => 'nombre', + 'password' => 'contrasenya', + 'password_confirmation' => 'confirmació de la contrasenya', + 'phone' => 'telèfon', + 'photo' => 'foto', + 'portfolio' => 'cartera', + 'postal_code' => 'Codi Postal', + 'preview' => 'Vista prèvia', + 'price' => 'preu', + 'product_id' => 'ID del producte', + 'product_uid' => 'UID del producte', + 'product_uuid' => 'UUID del producte', + 'promo_code' => 'codi promocional', + 'province' => 'província', + 'quantity' => 'quantitat', + 'reason' => 'raó', + 'recaptcha_response_field' => 'camp de resposta recaptcha', + 'referee' => 'àrbitre', + 'referees' => 'àrbitres', + 'region' => 'region', + 'reject_reason' => 'rebutjar la raó', + 'remember' => 'recorda', + 'restored_at' => 'restaurat a', + 'result_text_under_image' => 'text del resultat sota la imatge', + 'role' => 'paper', + 'rule' => 'regla', + 'rules' => 'regles', + 'second' => 'segon', + 'sex' => 'sexe', + 'shipment' => 'enviament', + 'short_text' => 'text breu', + 'size' => 'mida', + 'skills' => 'habilitats', + 'slug' => 'llimac', + 'specialization' => 'especialització', + 'started_at' => 'va començar a', + 'state' => 'estat', + 'status' => 'estat', + 'street' => 'carrer', + 'student' => 'estudiant', + 'subject' => 'assumpte', + 'tag' => 'etiqueta', + 'tags' => 'etiquetes', + 'teacher' => 'professor', + 'terms' => 'termes', + 'test_description' => 'descripció de la prova', + 'test_locale' => 'localització de prova', + 'test_name' => 'nom de la prova', + 'text' => 'text', + 'time' => 'hora', + 'title' => 'títol', + 'type' => 'tipus', + 'updated_at' => 'actualitzat a', + 'user' => 'usuari', + 'username' => 'usuari', + 'value' => 'valor', + 'winner' => 'winner', + 'work' => 'work', + 'year' => 'any', + ], +]; diff --git a/lang/en.json b/lang/en.json new file mode 100644 index 0000000..e7d6c0e --- /dev/null +++ b/lang/en.json @@ -0,0 +1,229 @@ +{ + "(and :count more error)": "(and :count more error)", + "(and :count more errors)": "(and :count more error)|(and :count more errors)|(and :count more errors)", + "A decryption key is required.": "A decryption key is required.", + "A Timeout Occurred": "A Timeout Occurred", + "Accept": "Accept", + "Accepted": "Accepted", + "Action": "Action", + "Actions": "Actions", + "Add": "Add", + "Add :name": "Add :name", + "Admin": "Admin", + "Agree": "Agree", + "All rights reserved.": "All rights reserved.", + "Already Reported": "Already Reported", + "Archive": "Archive", + "Assign": "Assign", + "Associate": "Associate", + "Attach": "Attach", + "Bad Gateway": "Bad Gateway", + "Bad Request": "Bad Request", + "Bandwidth Limit Exceeded": "Bandwidth Limit Exceeded", + "Browse": "Browse", + "Cancel": "Cancel", + "Choose": "Choose", + "Choose :name": "Choose :name", + "Choose File": "Choose File", + "Choose Image": "Choose Image", + "Click to copy": "Click to copy", + "Client Closed Request": "Client Closed Request", + "Close": "Close", + "Collapse": "Collapse", + "Collapse All": "Collapse All", + "Comment": "Comment", + "Confirm": "Confirm", + "Conflict": "Conflict", + "Connect": "Connect", + "Connection Closed Without Response": "Connection Closed Without Response", + "Connection Timed Out": "Connection Timed Out", + "Continue": "Continue", + "Create": "Create", + "Create :name": "Create :name", + "Created": "Created", + "Delete": "Delete", + "Delete :name": "Delete :name", + "Detach": "Detach", + "Details": "Details", + "Disable": "Disable", + "Discard": "Discard", + "Done": "Done", + "Down": "Down", + "Duplicate": "Duplicate", + "Duplicate :name": "Duplicate :name", + "Edit": "Edit", + "Edit :name": "Edit :name", + "Enable": "Enable", + "Encrypted environment file already exists.": "Encrypted environment file already exists.", + "Encrypted environment file not found.": "Encrypted environment file not found.", + "Environment file already exists.": "Environment file already exists.", + "Environment file not found.": "Environment file not found.", + "errors": "errors", + "Expand": "Expand", + "Expand All": "Expand All", + "Expectation Failed": "Expectation Failed", + "Explanation": "Explanation", + "Export": "Export", + "Export :name": "Export :name", + "Failed Dependency": "Failed Dependency", + "File": "File", + "Files": "Files", + "Forbidden": "Forbidden", + "Found": "Found", + "Gateway Timeout": "Gateway Timeout", + "Go Home": "Go Home", + "Go to page :page": "Go to page :page", + "Gone": "Gone", + "Hello!": "Hello!", + "Hide": "Hide", + "Hide :name": "Hide :name", + "Home": "Home", + "HTTP Version Not Supported": "HTTP Version Not Supported", + "I'm a teapot": "I'm a teapot", + "If you did not create an account, no further action is required.": "If you did not create an account, no further action is required.", + "If you did not request a password reset, no further action is required.": "If you did not request a password reset, no further action is required.", + "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:", + "IM Used": "IM Used", + "Image": "Image", + "Impersonate": "Impersonate", + "Impersonation": "Impersonation", + "Import": "Import", + "Import :name": "Import :name", + "Insufficient Storage": "Insufficient Storage", + "Internal Server Error": "Internal Server Error", + "Introduction": "Introduction", + "Invalid filename.": "Invalid filename.", + "Invalid JSON was returned from the route.": "Invalid JSON was returned from the route.", + "Invalid SSL Certificate": "Invalid SSL Certificate", + "Length Required": "Length Required", + "Like": "Like", + "Load": "Load", + "Localize": "Localize", + "Location": "Location", + "Locked": "Locked", + "Log In": "Log In", + "Log Out": "Log Out", + "Login": "Login", + "Logout": "Logout", + "Loop Detected": "Loop Detected", + "Maintenance Mode": "Maintenance Mode", + "Method Not Allowed": "Method Not Allowed", + "Misdirected Request": "Misdirected Request", + "Moved Permanently": "Moved Permanently", + "Multi-Status": "Multi-Status", + "Multiple Choices": "Multiple Choices", + "Network Authentication Required": "Network Authentication Required", + "Network Connect Timeout Error": "Network Connect Timeout Error", + "Network Read Timeout Error": "Network Read Timeout Error", + "New": "New", + "New :name": "New :name", + "No": "No", + "No Content": "No Content", + "Non-Authoritative Information": "Non-Authoritative Information", + "Not Acceptable": "Not Acceptable", + "Not Extended": "Not Extended", + "Not Found": "Not Found", + "Not Implemented": "Not Implemented", + "Not Modified": "Not Modified", + "of": "of", + "OK": "OK", + "Open": "Open", + "Open in a current window": "Open in a current window", + "Open in a new window": "Open in a new window", + "Open in a parent frame": "Open in a parent frame", + "Open in the topmost frame": "Open in the topmost frame", + "Open on the website": "Open on the website", + "Origin Is Unreachable": "Origin Is Unreachable", + "Page Expired": "Page Expired", + "Pagination Navigation": "Pagination Navigation", + "Partial Content": "Partial Content", + "Payload Too Large": "Payload Too Large", + "Payment Required": "Payment Required", + "Permanent Redirect": "Permanent Redirect", + "Please click the button below to verify your email address.": "Please click the button below to verify your email address.", + "Precondition Failed": "Precondition Failed", + "Precondition Required": "Precondition Required", + "Preview": "Preview", + "Price": "Price", + "Processing": "Processing", + "Proxy Authentication Required": "Proxy Authentication Required", + "Railgun Error": "Railgun Error", + "Range Not Satisfiable": "Range Not Satisfiable", + "Record": "Record", + "Regards,": "Regards,", + "Register": "Register", + "Request Header Fields Too Large": "Request Header Fields Too Large", + "Request Timeout": "Request Timeout", + "Reset Content": "Reset Content", + "Reset Password": "Reset Password", + "Reset your password": "Reset your password", + "Restore": "Restore", + "Restore :name": "Restore :name", + "results": "results", + "Retry With": "Retry With", + "Save": "Save", + "Save & Close": "Save & Close", + "Save & Return": "Save & Return", + "Save :name": "Save :name", + "Search": "Search", + "Search :name": "Search :name", + "See Other": "See Other", + "Select": "Select", + "Select All": "Select All", + "Send": "Send", + "Server Error": "Server Error", + "Service Unavailable": "Service Unavailable", + "Session Has Expired": "Session Has Expired", + "Settings": "Settings", + "Show": "Show", + "Show :name": "Show :name", + "Show All": "Show All", + "Showing": "Showing", + "Sign In": "Sign In", + "Solve": "Solve", + "SSL Handshake Failed": "SSL Handshake Failed", + "Start": "Start", + "Stop": "Stop", + "Submit": "Submit", + "Subscribe": "Subscribe", + "Switch": "Switch", + "Switch To Role": "Switch To Role", + "Switching Protocols": "Switching Protocols", + "Tag": "Tag", + "Tags": "Tags", + "Temporary Redirect": "Temporary Redirect", + "The given data was invalid.": "The given data was invalid.", + "The response is not a streamed response.": "The response is not a streamed response.", + "The response is not a view.": "The response is not a view.", + "This action is unauthorized.": "This action is unauthorized.", + "This password reset link will expire in :count minutes.": "This password reset link will expire in :count minutes.", + "to": "to", + "Toggle navigation": "Toggle navigation", + "Too Early": "Too Early", + "Too Many Requests": "Too Many Requests", + "Translate": "Translate", + "Translate It": "Translate It", + "Unauthorized": "Unauthorized", + "Unavailable For Legal Reasons": "Unavailable For Legal Reasons", + "Unknown Error": "Unknown Error", + "Unpack": "Unpack", + "Unprocessable Entity": "Unprocessable Entity", + "Unsubscribe": "Unsubscribe", + "Unsupported Media Type": "Unsupported Media Type", + "Up": "Up", + "Update": "Update", + "Update :name": "Update :name", + "Upgrade Required": "Upgrade Required", + "URI Too Long": "URI Too Long", + "Use Proxy": "Use Proxy", + "User": "User", + "Variant Also Negotiates": "Variant Also Negotiates", + "Verify Email Address": "Verify Email Address", + "Verify your email address": "Verify your email address", + "View": "View", + "View :name": "View :name", + "Web Server is Down": "Web Server is Down", + "Whoops!": "Whoops!", + "Yes": "Yes", + "You are receiving this email because we received a password reset request for your account.": "You are receiving this email because we received a password reset request for your account." +} \ No newline at end of file diff --git a/lang/en/actions.php b/lang/en/actions.php new file mode 100644 index 0000000..7ff2b68 --- /dev/null +++ b/lang/en/actions.php @@ -0,0 +1,119 @@ + 'Accept', + 'action' => 'Action', + 'actions' => 'Actions', + 'add' => 'Add', + 'admin' => 'Admin', + 'agree' => 'Agree', + 'archive' => 'Archive', + 'assign' => 'Assign', + 'associate' => 'Associate', + 'attach' => 'Attach', + 'browse' => 'Browse', + 'cancel' => 'Cancel', + 'choose' => 'Choose', + 'choose_file' => 'Choose File', + 'choose_image' => 'Choose Image', + 'click_to_copy' => 'Click to copy', + 'close' => 'Close', + 'collapse' => 'Collapse', + 'collapse_all' => 'Collapse All', + 'comment' => 'Comment', + 'confirm' => 'Confirm', + 'connect' => 'Connect', + 'create' => 'Create', + 'delete' => 'Delete', + 'detach' => 'Detach', + 'details' => 'Details', + 'disable' => 'Disable', + 'discard' => 'Discard', + 'done' => 'Done', + 'down' => 'Down', + 'duplicate' => 'Duplicate', + 'edit' => 'Edit', + 'enable' => 'Enable', + 'expand' => 'Expand', + 'expand_all' => 'Expand All', + 'explanation' => 'Explanation', + 'export' => 'Export', + 'file' => 'File', + 'files' => 'Files', + 'go_home' => 'Go Home', + 'hide' => 'Hide', + 'home' => 'Home', + 'image' => 'Image', + 'impersonate' => 'Impersonate', + 'impersonation' => 'Impersonation', + 'import' => 'Import', + 'introduction' => 'Introduction', + 'like' => 'Like', + 'load' => 'Load', + 'localize' => 'Localize', + 'log_in' => 'Log In', + 'log_out' => 'Log Out', + 'named' => [ + 'add' => 'Add :name', + 'choose' => 'Choose :name', + 'create' => 'Create :name', + 'delete' => 'Delete :name', + 'duplicate' => 'Duplicate :name', + 'edit' => 'Edit :name', + 'export' => 'Export :name', + 'hide' => 'Hide :name', + 'import' => 'Import :name', + 'new' => 'New :name', + 'restore' => 'Restore :name', + 'save' => 'Save :name', + 'search' => 'Search :name', + 'show' => 'Show :name', + 'update' => 'Update :name', + 'view' => 'View :name', + ], + 'new' => 'New', + 'no' => 'No', + 'open' => 'Open', + 'open_website' => 'Open on the website', + 'preview' => 'Preview', + 'price' => 'Price', + 'record' => 'Record', + 'restore' => 'Restore', + 'save' => 'Save', + 'save_and_close' => 'Save & Close', + 'save_and_return' => 'Save & Return', + 'search' => 'Search', + 'select' => 'Select', + 'select_all' => 'Select All', + 'send' => 'Send', + 'settings' => 'Settings', + 'show' => 'Show', + 'show_all' => 'Show All', + 'sign_in' => 'Sign In', + 'solve' => 'Solve', + 'start' => 'Start', + 'stop' => 'Stop', + 'submit' => 'Submit', + 'subscribe' => 'Subscribe', + 'switch' => 'Switch', + 'switch_to_role' => 'Switch To Role', + 'tag' => 'Tag', + 'tags' => 'Tags', + 'target_link' => [ + 'blank' => 'Open in a new window', + 'parent' => 'Open in a parent frame', + 'self' => 'Open in a current window', + 'top' => 'Open in the topmost frame', + ], + 'translate' => 'Translate', + 'translate_it' => 'Translate It', + 'unpack' => 'Unpack', + 'unsubscribe' => 'Unsubscribe', + 'up' => 'Up', + 'update' => 'Update', + 'user' => 'User', + 'view' => 'View', + 'yes' => 'Yes', +]; diff --git a/lang/en/auth.php b/lang/en/auth.php new file mode 100644 index 0000000..ad725d8 --- /dev/null +++ b/lang/en/auth.php @@ -0,0 +1,9 @@ + '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/http-statuses.php b/lang/en/http-statuses.php new file mode 100644 index 0000000..3346f78 --- /dev/null +++ b/lang/en/http-statuses.php @@ -0,0 +1,84 @@ + 'Unknown Error', + '100' => 'Continue', + '101' => 'Switching Protocols', + '102' => 'Processing', + '200' => 'OK', + '201' => 'Created', + '202' => 'Accepted', + '203' => 'Non-Authoritative Information', + '204' => 'No Content', + '205' => 'Reset Content', + '206' => 'Partial Content', + '207' => 'Multi-Status', + '208' => 'Already Reported', + '226' => 'IM Used', + '300' => 'Multiple Choices', + '301' => 'Moved Permanently', + '302' => 'Found', + '303' => 'See Other', + '304' => 'Not Modified', + '305' => 'Use Proxy', + '307' => 'Temporary Redirect', + '308' => 'Permanent Redirect', + '400' => 'Bad Request', + '401' => 'Unauthorized', + '402' => 'Payment Required', + '403' => 'Forbidden', + '404' => 'Not Found', + '405' => 'Method Not Allowed', + '406' => 'Not Acceptable', + '407' => 'Proxy Authentication Required', + '408' => 'Request Timeout', + '409' => 'Conflict', + '410' => 'Gone', + '411' => 'Length Required', + '412' => 'Precondition Failed', + '413' => 'Payload Too Large', + '414' => 'URI Too Long', + '415' => 'Unsupported Media Type', + '416' => 'Range Not Satisfiable', + '417' => 'Expectation Failed', + '418' => 'I\'m a teapot', + '419' => 'Session Has Expired', + '421' => 'Misdirected Request', + '422' => 'Unprocessable Entity', + '423' => 'Locked', + '424' => 'Failed Dependency', + '425' => 'Too Early', + '426' => 'Upgrade Required', + '428' => 'Precondition Required', + '429' => 'Too Many Requests', + '431' => 'Request Header Fields Too Large', + '444' => 'Connection Closed Without Response', + '449' => 'Retry With', + '451' => 'Unavailable For Legal Reasons', + '499' => 'Client Closed Request', + '500' => 'Internal Server Error', + '501' => 'Not Implemented', + '502' => 'Bad Gateway', + '503' => 'Maintenance Mode', + '504' => 'Gateway Timeout', + '505' => 'HTTP Version Not Supported', + '506' => 'Variant Also Negotiates', + '507' => 'Insufficient Storage', + '508' => 'Loop Detected', + '509' => 'Bandwidth Limit Exceeded', + '510' => 'Not Extended', + '511' => 'Network Authentication Required', + '520' => 'Unknown Error', + '521' => 'Web Server is Down', + '522' => 'Connection Timed Out', + '523' => 'Origin Is Unreachable', + '524' => 'A Timeout Occurred', + '525' => 'SSL Handshake Failed', + '526' => 'Invalid SSL Certificate', + '527' => 'Railgun Error', + '598' => 'Network Read Timeout Error', + '599' => 'Network Connect Timeout Error', + 'unknownError' => 'Unknown Error', +]; diff --git a/lang/en/messages.php b/lang/en/messages.php new file mode 100644 index 0000000..0100f4a --- /dev/null +++ b/lang/en/messages.php @@ -0,0 +1,13 @@ + 'Invalid credentials.', + 'account_deactivated' => 'This account is deactivated.', + 'account_pending_approval' => 'Your account is pending approval by an administrator.', + 'registration_pending' => 'Account created. An administrator has to approve it before you can sign in.', + 'cannot_remove_own_admin_role' => "You can't remove your own admin role.", + 'cannot_delete_self' => "You can't delete your own account.", + 'cannot_delete_last_admin' => "You can't delete the last administrator.", + 'self_service_requires_user' => 'This resource is self-service: it needs a token with an associated user (does not work with client_credentials).', + 'user_not_trashed' => 'This user is not in the trash.', +]; diff --git a/lang/en/pagination.php b/lang/en/pagination.php new file mode 100644 index 0000000..f4cedde --- /dev/null +++ b/lang/en/pagination.php @@ -0,0 +1,8 @@ + 'Next »', + 'previous' => '« Previous', +]; diff --git a/lang/en/passwords.php b/lang/en/passwords.php new file mode 100644 index 0000000..f3b65ba --- /dev/null +++ b/lang/en/passwords.php @@ -0,0 +1,11 @@ + '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..71ffea4 --- /dev/null +++ b/lang/en/validation.php @@ -0,0 +1,289 @@ + 'The :attribute field must be accepted.', + 'accepted_if' => 'The :attribute field must be accepted when :other is :value.', + 'active_url' => 'The :attribute field must be a valid URL.', + 'after' => 'The :attribute field must be a date after :date.', + 'after_or_equal' => 'The :attribute field must be a date after or equal to :date.', + 'alpha' => 'The :attribute field must only contain letters.', + 'alpha_dash' => 'The :attribute field must only contain letters, numbers, dashes, and underscores.', + 'alpha_num' => 'The :attribute field must only contain letters and numbers.', + 'any_of' => 'The :attribute field is invalid.', + 'array' => 'The :attribute field must be an array.', + 'ascii' => 'The :attribute field must only contain single-byte alphanumeric characters and symbols.', + 'base64' => 'The :attribute field must be a valid Base64 string.', + 'before' => 'The :attribute field must be a date before :date.', + 'before_or_equal' => 'The :attribute field must be a date before or equal to :date.', + 'between' => [ + 'array' => 'The :attribute field must have between :min and :max items.', + 'file' => 'The :attribute field must be between :min and :max kilobytes.', + 'numeric' => 'The :attribute field must be between :min and :max.', + 'string' => 'The :attribute field must be between :min and :max characters.', + ], + 'boolean' => 'The :attribute field must be true or false.', + 'can' => 'The :attribute field contains an unauthorized value.', + 'confirmed' => 'The :attribute field confirmation does not match.', + 'contains' => 'The :attribute field is missing a required value.', + 'current_password' => 'The password is incorrect.', + 'date' => 'The :attribute field must be a valid date.', + 'date_equals' => 'The :attribute field must be a date equal to :date.', + 'date_format' => 'The :attribute field must match the format :format.', + 'decimal' => 'The :attribute field must have :decimal decimal places.', + 'declined' => 'The :attribute field must be declined.', + 'declined_if' => 'The :attribute field must be declined when :other is :value.', + 'different' => 'The :attribute field and :other must be different.', + 'digits' => 'The :attribute field must be :digits digits.', + 'digits_between' => 'The :attribute field must be between :min and :max digits.', + 'dimensions' => 'The :attribute field has invalid image dimensions.', + 'distinct' => 'The :attribute field has a duplicate value.', + 'doesnt_contain' => 'The :attribute field must not contain any of the following: :values.', + 'doesnt_end_with' => 'The :attribute field must not end with one of the following: :values.', + 'doesnt_start_with' => 'The :attribute field must not start with one of the following: :values.', + 'email' => 'The :attribute field must be a valid email address.', + 'encoding' => 'The :attribute field must be encoded in :encoding.', + 'ends_with' => 'The :attribute field must end with one of the following: :values.', + 'enum' => 'The selected :attribute is invalid.', + 'exists' => 'The selected :attribute is invalid.', + 'extensions' => 'The :attribute field must have one of the following extensions: :values.', + 'file' => 'The :attribute field must be a file.', + 'filled' => 'The :attribute field must have a value.', + 'gt' => [ + 'array' => 'The :attribute field must have more than :value items.', + 'file' => 'The :attribute field must be greater than :value kilobytes.', + 'numeric' => 'The :attribute field must be greater than :value.', + 'string' => 'The :attribute field must be greater than :value characters.', + ], + 'gte' => [ + 'array' => 'The :attribute field must have :value items or more.', + 'file' => 'The :attribute field must be greater than or equal to :value kilobytes.', + 'numeric' => 'The :attribute field must be greater than or equal to :value.', + 'string' => 'The :attribute field must be greater than or equal to :value characters.', + ], + 'hex_color' => 'The :attribute field must be a valid hexadecimal color.', + 'image' => 'The :attribute field must be an image.', + 'in' => 'The selected :attribute is invalid.', + 'in_array' => 'The :attribute field must exist in :other.', + 'in_array_keys' => 'The :attribute field must contain at least one of the following keys: :values.', + 'integer' => 'The :attribute field must be an integer.', + 'ip' => 'The :attribute field must be a valid IP address.', + 'ipv4' => 'The :attribute field must be a valid IPv4 address.', + 'ipv6' => 'The :attribute field must be a valid IPv6 address.', + 'json' => 'The :attribute field must be a valid JSON string.', + 'list' => 'The :attribute field must be a list.', + 'lowercase' => 'The :attribute field must be lowercase.', + 'lt' => [ + 'array' => 'The :attribute field must have less than :value items.', + 'file' => 'The :attribute field must be less than :value kilobytes.', + 'numeric' => 'The :attribute field must be less than :value.', + 'string' => 'The :attribute field must be less than :value characters.', + ], + 'lte' => [ + 'array' => 'The :attribute field must not have more than :value items.', + 'file' => 'The :attribute field must be less than or equal to :value kilobytes.', + 'numeric' => 'The :attribute field must be less than or equal to :value.', + 'string' => 'The :attribute field must be less than or equal to :value characters.', + ], + 'mac_address' => 'The :attribute field must be a valid MAC address.', + 'max' => [ + 'array' => 'The :attribute field must not have more than :max items.', + 'file' => 'The :attribute field must not be greater than :max kilobytes.', + 'numeric' => 'The :attribute field must not be greater than :max.', + 'string' => 'The :attribute field must not be greater than :max characters.', + ], + 'max_digits' => 'The :attribute field must not have more than :max digits.', + 'mimes' => 'The :attribute field must be a file of type: :values.', + 'mimetypes' => 'The :attribute field must be a file of type: :values.', + 'min' => [ + 'array' => 'The :attribute field must have at least :min items.', + 'file' => 'The :attribute field must be at least :min kilobytes.', + 'numeric' => 'The :attribute field must be at least :min.', + 'string' => 'The :attribute field must be at least :min characters.', + ], + 'min_digits' => 'The :attribute field must have at least :min digits.', + 'missing' => 'The :attribute field must be missing.', + 'missing_if' => 'The :attribute field must be missing when :other is :value.', + 'missing_unless' => 'The :attribute field must be missing unless :other is :value.', + 'missing_with' => 'The :attribute field must be missing when :values is present.', + 'missing_with_all' => 'The :attribute field must be missing when :values are present.', + 'multiple_of' => 'The :attribute field must be a multiple of :value.', + 'not_in' => 'The selected :attribute is invalid.', + 'not_regex' => 'The :attribute field format is invalid.', + 'numeric' => 'The :attribute field must be a number.', + 'password' => [ + 'letters' => 'The :attribute field must contain at least one letter.', + 'mixed' => 'The :attribute field must contain at least one uppercase and one lowercase letter.', + 'numbers' => 'The :attribute field must contain at least one number.', + 'symbols' => 'The :attribute field 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.', + 'present_if' => 'The :attribute field must be present when :other is :value.', + 'present_unless' => 'The :attribute field must be present unless :other is :value.', + 'present_with' => 'The :attribute field must be present when :values is present.', + 'present_with_all' => 'The :attribute field must be present when :values are present.', + 'prohibited' => 'The :attribute field is prohibited.', + 'prohibited_if' => 'The :attribute field is prohibited when :other is :value.', + 'prohibited_if_accepted' => 'The :attribute field is prohibited when :other is accepted.', + 'prohibited_if_declined' => 'The :attribute field is prohibited when :other is declined.', + 'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.', + 'prohibits' => 'The :attribute field prohibits :other from being present.', + 'regex' => 'The :attribute field 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_if_declined' => 'The :attribute field is required when :other is declined.', + '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 field must match :other.', + 'size' => [ + 'array' => 'The :attribute field must contain :size items.', + 'file' => 'The :attribute field must be :size kilobytes.', + 'numeric' => 'The :attribute field must be :size.', + 'string' => 'The :attribute field must be :size characters.', + ], + 'starts_with' => 'The :attribute field must start with one of the following: :values.', + 'string' => 'The :attribute field must be a string.', + 'timezone' => 'The :attribute field must be a valid timezone.', + 'ulid' => 'The :attribute field must be a valid ULID.', + 'unique' => 'The :attribute has already been taken.', + 'uploaded' => 'The :attribute failed to upload.', + 'uppercase' => 'The :attribute field must be uppercase.', + 'url' => 'The :attribute field must be a valid URL.', + 'uuid' => 'The :attribute field must be a valid UUID.', + 'attributes' => [ + 'address' => 'address', + 'affiliate_url' => 'affiliate URL', + 'age' => 'age', + 'amount' => 'amount', + 'announcement' => 'announcement', + 'area' => 'area', + 'audience_prize' => 'audience prize', + 'audience_winner' => 'audience winner', + 'available' => 'available', + 'birthday' => 'birthday', + 'body' => 'body', + 'city' => 'city', + 'color' => 'color', + 'company' => 'company', + 'compilation' => 'compilation', + 'concept' => 'concept', + 'conditions' => 'conditions', + 'content' => 'content', + 'contest' => 'contest', + 'country' => 'country', + 'cover' => 'cover', + 'created_at' => 'created at', + 'creator' => 'creator', + 'currency' => 'currency', + 'current_password' => 'current password', + 'customer' => 'customer', + 'date' => 'date', + 'date_of_birth' => 'date of birth', + 'dates' => 'dates', + 'day' => 'day', + 'deleted_at' => 'deleted at', + 'description' => 'description', + 'display_type' => 'display type', + 'district' => 'district', + 'duration' => 'duration', + 'email' => 'email', + 'excerpt' => 'excerpt', + 'filter' => 'filter', + 'finished_at' => 'finished at', + 'first_name' => 'first name', + 'gender' => 'gender', + 'grand_prize' => 'grand prize', + 'group' => 'group', + 'hour' => 'hour', + 'image' => 'image', + 'image_desktop' => 'desktop image', + 'image_main' => 'main image', + 'image_mobile' => 'mobile image', + 'images' => 'images', + 'is_audience_winner' => 'is audience winner', + 'is_hidden' => 'is hidden', + 'is_subscribed' => 'is subscribed', + 'is_visible' => 'is visible', + 'is_winner' => 'is winner', + 'items' => 'items', + 'key' => 'key', + 'last_name' => 'last name', + 'lesson' => 'lesson', + 'line_address_1' => 'line address 1', + 'line_address_2' => 'line address 2', + 'login' => 'login', + 'message' => 'message', + 'middle_name' => 'middle name', + 'minute' => 'minute', + 'mobile' => 'mobile', + 'month' => 'month', + 'name' => 'name', + 'national_code' => 'national code', + 'number' => 'number', + 'password' => 'password', + 'password_confirmation' => 'password confirmation', + 'phone' => 'phone', + 'photo' => 'photo', + 'portfolio' => 'portfolio', + 'postal_code' => 'postal code', + 'preview' => 'preview', + 'price' => 'price', + 'product_id' => 'product ID', + 'product_uid' => 'product UID', + 'product_uuid' => 'product UUID', + 'promo_code' => 'promo code', + 'province' => 'province', + 'quantity' => 'quantity', + 'reason' => 'reason', + 'recaptcha_response_field' => 'recaptcha response field', + 'referee' => 'referee', + 'referees' => 'referees', + 'region' => 'region', + 'reject_reason' => 'reject reason', + 'remember' => 'remember', + 'restored_at' => 'restored at', + 'result_text_under_image' => 'result text under image', + 'role' => 'role', + 'rule' => 'rule', + 'rules' => 'rules', + 'second' => 'second', + 'sex' => 'sex', + 'shipment' => 'shipment', + 'short_text' => 'short text', + 'size' => 'size', + 'skills' => 'skills', + 'slug' => 'slug', + 'specialization' => 'specialization', + 'started_at' => 'started at', + 'state' => 'state', + 'status' => 'status', + 'street' => 'street', + 'student' => 'student', + 'subject' => 'subject', + 'tag' => 'tag', + 'tags' => 'tags', + 'teacher' => 'teacher', + 'terms' => 'terms', + 'test_description' => 'test description', + 'test_locale' => 'test locale', + 'test_name' => 'test name', + 'text' => 'text', + 'time' => 'time', + 'title' => 'title', + 'type' => 'type', + 'updated_at' => 'updated at', + 'user' => 'user', + 'username' => 'username', + 'value' => 'value', + 'winner' => 'winner', + 'work' => 'work', + 'year' => 'year', + ], +]; diff --git a/lang/es.json b/lang/es.json new file mode 100644 index 0000000..70ce891 --- /dev/null +++ b/lang/es.json @@ -0,0 +1,229 @@ +{ + "(and :count more error)": "(y :count error más)", + "(and :count more errors)": "(y :count error más)|(y :count errores más)|(y :count errores más)", + "A decryption key is required.": "Se requiere una clave de descifrado.", + "A Timeout Occurred": "Se produjo un tiempo de espera", + "Accept": "Aceptar", + "Accepted": "Aceptado", + "Action": "Acción", + "Actions": "Acciones", + "Add": "Añadir", + "Add :name": "Agregar :name", + "Admin": "Administrar", + "Agree": "Aceptar", + "All rights reserved.": "Todos los derechos reservados.", + "Already Reported": "Ya Reportado", + "Archive": "Archivar", + "Assign": "Asignar", + "Associate": "Asociar", + "Attach": "Adjuntar", + "Bad Gateway": "Mala puerta de enlace", + "Bad Request": "Solicitud incorrecta", + "Bandwidth Limit Exceeded": "Límite de ancho de banda excedido", + "Browse": "Navegar", + "Cancel": "Cancelar", + "Choose": "Elija", + "Choose :name": "Elegir :name", + "Choose File": "Elija archivo", + "Choose Image": "Elegir Imagen", + "Click to copy": "Haga clic para copiar", + "Client Closed Request": "Solicitud cerrada del cliente", + "Close": "Cerrar", + "Collapse": "Colapsar", + "Collapse All": "Colapsar todo", + "Comment": "Comentar", + "Confirm": "Confirmar", + "Conflict": "Conflicto", + "Connect": "Conectar", + "Connection Closed Without Response": "Conexión cerrada sin respuesta", + "Connection Timed Out": "Tiempo de conexión agotado", + "Continue": "Continuar", + "Create": "Crear", + "Create :name": "Crear :name", + "Created": "Creado", + "Delete": "Eliminar", + "Delete :name": "Eliminar :name", + "Detach": "Desvincular", + "Details": "Detalles", + "Disable": "Deshabilitar", + "Discard": "Descartar", + "Done": "Hecho", + "Down": "Abajo", + "Duplicate": "Duplicar", + "Duplicate :name": "Duplicar :name", + "Edit": "Editar", + "Edit :name": "Editar :name", + "Enable": "Habilitar", + "Encrypted environment file already exists.": "El archivo de entorno cifrado ya existe.", + "Encrypted environment file not found.": "No se encontró el archivo de entorno cifrado.", + "Environment file already exists.": "El archivo de entorno ya existe.", + "Environment file not found.": "Archivo de entorno no encontrado.", + "errors": "errores", + "Expand": "Expandir", + "Expand All": "Expandir todo", + "Expectation Failed": "Expectativa fallida", + "Explanation": "Explicación", + "Export": "Exportar", + "Export :name": "Exportar :name", + "Failed Dependency": "Dependencia fallida", + "File": "Archivo", + "Files": "Archivos", + "Forbidden": "Prohibido", + "Found": "Encontrado", + "Gateway Timeout": "Tiempo de espera de puerta de enlace", + "Go Home": "Ir a inicio", + "Go to page :page": "Ir a la página :page", + "Gone": "Recurso no disponible", + "Hello!": "¡Hola!", + "Hide": "Ocultar", + "Hide :name": "Ocultar :name", + "Home": "Inicio", + "HTTP Version Not Supported": "Versión HTTP no compatible", + "I'm a teapot": "Soy una tetera", + "If you did not create an account, no further action is required.": "Si no ha creado una cuenta, no se requiere ninguna acción adicional.", + "If you did not request a password reset, no further action is required.": "Si no ha solicitado el restablecimiento de contraseña, omita este mensaje de correo electrónico.", + "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Si está teniendo problemas al hacer clic en el botón \":actionText\", copie y pegue la URL de abajo\nen su navegador web:", + "IM Used": "IM usado", + "Image": "Imagen", + "Impersonate": "Personificar", + "Impersonation": "Personificación", + "Import": "Importar", + "Import :name": "Importar :name", + "Insufficient Storage": "Espacio insuficiente", + "Internal Server Error": "Error interno del servidor", + "Introduction": "Introducción", + "Invalid filename.": "Nombre de archivo no válido.", + "Invalid JSON was returned from the route.": "Se devolvió un JSON no válido desde la ruta.", + "Invalid SSL Certificate": "Certificado SSL no válido", + "Length Required": "Longitud requerida", + "Like": "Me gusta", + "Load": "Cargar", + "Localize": "Localizar", + "Location": "Ubicación", + "Locked": "Bloqueado", + "Log In": "Iniciar sesión", + "Log Out": "Finalizar sesión", + "Login": "Iniciar sesión", + "Logout": "Finalizar sesión", + "Loop Detected": "Bucle detectado", + "Maintenance Mode": "Modo de mantenimiento", + "Method Not Allowed": "Método no permitido", + "Misdirected Request": "Solicitud mal dirigida", + "Moved Permanently": "Movido permanentemente", + "Multi-Status": "Multiestado", + "Multiple Choices": "Múltiples opciones", + "Network Authentication Required": "Se requiere autenticación de red", + "Network Connect Timeout Error": "Error de tiempo de espera de conexión de red", + "Network Read Timeout Error": "Error de tiempo de espera de lectura de red", + "New": "Nuevo", + "New :name": "Nuevo :name", + "No": "No", + "No Content": "Sin contenido", + "Non-Authoritative Information": "Información no autorizada", + "Not Acceptable": "Inaceptable", + "Not Extended": "no extendido", + "Not Found": "No encontrado", + "Not Implemented": "No se ha implementado", + "Not Modified": "No modificado", + "of": "de", + "OK": "Correcto", + "Open": "Abrir", + "Open in a current window": "Abrir en una ventana actual", + "Open in a new window": "Abrir en una ventana nueva", + "Open in a parent frame": "Abrir en un marco principal", + "Open in the topmost frame": "Abrir en el marco superior", + "Open on the website": "Abrir en el sitio web", + "Origin Is Unreachable": "El origen es inalcanzable", + "Page Expired": "Página expirada", + "Pagination Navigation": "Navegación por los enlaces de paginación", + "Partial Content": "Contenido parcial", + "Payload Too Large": "Solicitud demasiado grande", + "Payment Required": "Pago requerido", + "Permanent Redirect": "Redirección permanente", + "Please click the button below to verify your email address.": "Por favor, haga clic en el botón de abajo para verificar su dirección de correo electrónico.", + "Precondition Failed": "Error de condición previa", + "Precondition Required": "Precondición requerida", + "Preview": "Previsualizar", + "Price": "Precio", + "Processing": "Procesando", + "Proxy Authentication Required": "Se requiere autenticación proxy", + "Railgun Error": "Error de cañón de riel", + "Range Not Satisfiable": "Rango no satisfactorio", + "Record": "Registro", + "Regards,": "Saludos,", + "Register": "Registrarse", + "Request Header Fields Too Large": "Campos de encabezado de solicitud demasiado grandes", + "Request Timeout": "Solicitud de tiempo de espera", + "Reset Content": "Restablecer contenido", + "Reset Password": "Restablecer contraseña", + "Reset your password": "Restablezca su contraseña", + "Restore": "Restaurar", + "Restore :name": "Restaurar :name", + "results": "resultados", + "Retry With": "Reintentar con", + "Save": "Guardar", + "Save & Close": "Guardar y cerrar", + "Save & Return": "Guardar y volver", + "Save :name": "Guardar :name", + "Search": "Buscar", + "Search :name": "Buscar :name", + "See Other": "Ver otros", + "Select": "Seleccione", + "Select All": "Seleccionar todo", + "Send": "Enviar", + "Server Error": "Error del servidor", + "Service Unavailable": "Servicio no disponible", + "Session Has Expired": "La sesión ha expirado", + "Settings": "Ajustes", + "Show": "Mostrar", + "Show :name": "Mostrar :name", + "Show All": "Mostrar todo", + "Showing": "Mostrando", + "Sign In": "Iniciar sesión", + "Solve": "Resolver", + "SSL Handshake Failed": "Protocolo de enlace SSL fallido", + "Start": "Comenzar", + "Stop": "Detener", + "Submit": "Enviar", + "Subscribe": "Suscriba", + "Switch": "Cambiar", + "Switch To Role": "Cambiar de rol", + "Switching Protocols": "Protocolos de conmutación", + "Tag": "Etiqueta", + "Tags": "Etiquetas", + "Temporary Redirect": "Redirección temporal", + "The given data was invalid.": "Los datos proporcionados no son válidos.", + "The response is not a streamed response.": "La respuesta no es una respuesta transmitida.", + "The response is not a view.": "La respuesta no es una vista.", + "This action is unauthorized.": "Esta acción no está autorizada.", + "This password reset link will expire in :count minutes.": "Este enlace de restablecimiento de contraseña expirará en :count minutos.", + "to": "al", + "Toggle navigation": "Alternar navegación", + "Too Early": "Demasiado temprano", + "Too Many Requests": "Demasiadas peticiones", + "Translate": "Traducir", + "Translate It": "Traducirlo", + "Unauthorized": "No autorizado", + "Unavailable For Legal Reasons": "No disponible por razones legales", + "Unknown Error": "Error desconocido", + "Unpack": "Desglosar", + "Unprocessable Entity": "Entidad no procesable", + "Unsubscribe": "Darse de baja", + "Unsupported Media Type": "Tipo de medio no admitido", + "Up": "Arriba", + "Update": "Actualizar", + "Update :name": "Actualizar :name", + "Upgrade Required": "Se requiere actualización", + "URI Too Long": "URI demasiado largo", + "Use Proxy": "Usa proxy", + "User": "Usuario", + "Variant Also Negotiates": "Variante También Negocia", + "Verify Email Address": "Confirme su correo electrónico", + "Verify your email address": "Verifique su dirección de correo electrónico", + "View": "Ver", + "View :name": "Ver :name", + "Web Server is Down": "El servidor web está caído", + "Whoops!": "¡Ups!", + "Yes": "Sí", + "You are receiving this email because we received a password reset request for your account.": "Ha recibido este mensaje porque se solicitó un restablecimiento de contraseña para su cuenta." +} \ No newline at end of file diff --git a/lang/es/actions.php b/lang/es/actions.php new file mode 100644 index 0000000..f709ddf --- /dev/null +++ b/lang/es/actions.php @@ -0,0 +1,119 @@ + 'Aceptar', + 'action' => 'Acción', + 'actions' => 'Acciones', + 'add' => 'Agregar', + 'admin' => 'Administrar', + 'agree' => 'Aceptar', + 'archive' => 'Archivar', + 'assign' => 'Asignar', + 'associate' => 'Asociar', + 'attach' => 'Adjuntar', + 'browse' => 'Navegar', + 'cancel' => 'Cancelar', + 'choose' => 'Elegir', + 'choose_file' => 'Elegir archivo', + 'choose_image' => 'Elegir Imagen', + 'click_to_copy' => 'Haga clic para copiar', + 'close' => 'Cerrar', + 'collapse' => 'Colapsar', + 'collapse_all' => 'Colapsar todo', + 'comment' => 'Comentar', + 'confirm' => 'Confirmar', + 'connect' => 'Conectar', + 'create' => 'Crear', + 'delete' => 'Borrar', + 'detach' => 'Desasociar', + 'details' => 'Detalles', + 'disable' => 'Desactivar', + 'discard' => 'Descartar', + 'done' => 'Hecho', + 'down' => 'Abajo', + 'duplicate' => 'Duplicar', + 'edit' => 'Editar', + 'enable' => 'Permitir', + 'expand' => 'Expandir', + 'expand_all' => 'Expandir todo', + 'explanation' => 'Explicación', + 'export' => 'Exportar', + 'file' => 'Archivo', + 'files' => 'Archivos', + 'go_home' => 'Ir a Inicio', + 'hide' => 'Ocultar', + 'home' => 'Inicio', + 'image' => 'Imagen', + 'impersonate' => 'Personificar', + 'impersonation' => 'Personificación', + 'import' => 'Importar', + 'introduction' => 'Introducción', + 'like' => 'Me gusta', + 'load' => 'Cargar', + 'localize' => 'Localizar', + 'log_in' => 'Acceder', + 'log_out' => 'Cerrar sesión', + 'named' => [ + 'add' => 'Agregar :name', + 'choose' => 'Elegir :name', + 'create' => 'Crear :name', + 'delete' => 'Eliminar :name', + 'duplicate' => 'Duplicar :name', + 'edit' => 'Editar :name', + 'export' => 'Exportar :name', + 'hide' => 'Ocultar :name', + 'import' => 'Importar :name', + 'new' => 'Nuevo :name', + 'restore' => 'Restaurar :name', + 'save' => 'Guardar :name', + 'search' => 'Buscar :name', + 'show' => 'Mostrar :name', + 'update' => 'Actualizar :name', + 'view' => 'Ver :name', + ], + 'new' => 'Nuevo', + 'no' => 'No', + 'open' => 'Abrir', + 'open_website' => 'Abrir en el sitio web', + 'preview' => 'Previsualizar', + 'price' => 'Precio', + 'record' => 'Registro', + 'restore' => 'Restaurar', + 'save' => 'Guardar', + 'save_and_close' => 'Guardar y cerrar', + 'save_and_return' => 'Guardar y volver', + 'search' => 'Buscar', + 'select' => 'Seleccionar', + 'select_all' => 'Seleccionar todo', + 'send' => 'Enviar', + 'settings' => 'Ajustes', + 'show' => 'Mostrar', + 'show_all' => 'Mostrar todo', + 'sign_in' => 'Iniciar sesión', + 'solve' => 'Resolver', + 'start' => 'Comenzar', + 'stop' => 'Detener', + 'submit' => 'Enviar', + 'subscribe' => 'Suscribir', + 'switch' => 'Cambiar', + 'switch_to_role' => 'Cambiar de rol', + 'tag' => 'Etiqueta', + 'tags' => 'Etiquetas', + 'target_link' => [ + 'blank' => 'Abrir en una ventana nueva', + 'parent' => 'Abrir en el marco principal', + 'self' => 'Abrir en la ventana actual', + 'top' => 'Abrir en el marco superior', + ], + 'translate' => 'Traducir', + 'translate_it' => 'Traducirlo', + 'unpack' => 'Desglosar', + 'unsubscribe' => 'Darse de baja', + 'up' => 'Arriba', + 'update' => 'Actualizar', + 'user' => 'Usuario', + 'view' => 'Ver', + 'yes' => 'Sí', +]; diff --git a/lang/es/auth.php b/lang/es/auth.php new file mode 100644 index 0000000..888279b --- /dev/null +++ b/lang/es/auth.php @@ -0,0 +1,9 @@ + 'Estas credenciales no coinciden con nuestros registros.', + 'password' => 'La contraseña es incorrecta.', + 'throttle' => 'Demasiados intentos de acceso. Por favor intente nuevamente en :seconds segundos.', +]; diff --git a/lang/es/http-statuses.php b/lang/es/http-statuses.php new file mode 100644 index 0000000..0548dab --- /dev/null +++ b/lang/es/http-statuses.php @@ -0,0 +1,84 @@ + 'Error desconocido', + '100' => 'Continuar', + '101' => 'Protocolos de conmutación', + '102' => 'Procesando', + '200' => 'DE ACUERDO', + '201' => 'Creado', + '202' => 'Aceptado', + '203' => 'Información no autorizada', + '204' => 'Sin contenido', + '205' => 'Restablecer contenido', + '206' => 'Contenido parcial', + '207' => 'Multiestado', + '208' => 'Ya Reportado', + '226' => 'IM usado', + '300' => 'Múltiples opciones', + '301' => 'Movido permanentemente', + '302' => 'Encontrado', + '303' => 'Ver otros', + '304' => 'No modificado', + '305' => 'Usa proxy', + '307' => 'Redirección temporal', + '308' => 'Redirección permanente', + '400' => 'Solicitud incorrecta', + '401' => 'No autorizado', + '402' => 'Pago requerido', + '403' => 'Prohibido', + '404' => 'No encontrado', + '405' => 'Método no permitido', + '406' => 'Inaceptable', + '407' => 'Se requiere autenticación proxy', + '408' => 'Solicitud de tiempo de espera', + '409' => 'Conflicto', + '410' => 'Recurso no disponible', + '411' => 'Longitud requerida', + '412' => 'Error de condición previa', + '413' => 'Solicitud demasiado grande', + '414' => 'URI demasiado largo', + '415' => 'Tipo de medio no admitido', + '416' => 'Rango no satisfactorio', + '417' => 'Expectativa fallida', + '418' => 'Soy una tetera', + '419' => 'La sesión ha expirado', + '421' => 'Solicitud mal dirigida', + '422' => 'Entidad no procesable', + '423' => 'Bloqueado', + '424' => 'Dependencia fallida', + '425' => 'Demasiado temprano', + '426' => 'Se requiere actualización', + '428' => 'Precondición requerida', + '429' => 'Demasiadas solicitudes', + '431' => 'Campos de encabezado de solicitud demasiado grandes', + '444' => 'Conexión cerrada sin respuesta', + '449' => 'Reintentar con', + '451' => 'No disponible por razones legales', + '499' => 'Solicitud cerrada del cliente', + '500' => 'Error interno del servidor', + '501' => 'No se ha implementado', + '502' => 'Mala puerta de enlace', + '503' => 'Modo de mantenimiento', + '504' => 'Tiempo de espera de puerta de enlace', + '505' => 'Versión HTTP no compatible', + '506' => 'Variante También Negocia', + '507' => 'Espacio insuficiente', + '508' => 'Bucle detectado', + '509' => 'Límite de ancho de banda excedido', + '510' => 'no extendido', + '511' => 'Se requiere autenticación de red', + '520' => 'Error desconocido', + '521' => 'El servidor web está caído', + '522' => 'Tiempo de conexión agotado', + '523' => 'El origen es inalcanzable', + '524' => 'Se produjo un tiempo de espera', + '525' => 'Protocolo de enlace SSL fallido', + '526' => 'Certificado SSL no válido', + '527' => 'Error de cañón de riel', + '598' => 'Error de tiempo de espera de lectura de red', + '599' => 'Error de tiempo de espera de conexión de red', + 'unknownError' => 'Error desconocido', +]; diff --git a/lang/es/messages.php b/lang/es/messages.php new file mode 100644 index 0000000..e6e1c88 --- /dev/null +++ b/lang/es/messages.php @@ -0,0 +1,13 @@ + 'Credenciales inválidas.', + 'account_deactivated' => 'Esta cuenta está desactivada.', + 'account_pending_approval' => 'Tu cuenta está pendiente de aprobación por un administrador.', + 'registration_pending' => 'Cuenta creada. Un administrador tiene que aprobarla antes de que puedas ingresar.', + 'cannot_remove_own_admin_role' => 'No podés quitarte el rol de admin a vos mismo.', + 'cannot_delete_self' => 'No podés borrar tu propia cuenta.', + 'cannot_delete_last_admin' => 'No podés borrar al último administrador.', + 'self_service_requires_user' => 'Este recurso es self-service: necesita un token con usuario asociado (no funciona con client_credentials).', + 'user_not_trashed' => 'Este usuario no está en la papelera.', +]; diff --git a/lang/es/pagination.php b/lang/es/pagination.php new file mode 100644 index 0000000..03816a3 --- /dev/null +++ b/lang/es/pagination.php @@ -0,0 +1,8 @@ + 'Siguiente »', + 'previous' => '« Anterior', +]; diff --git a/lang/es/passwords.php b/lang/es/passwords.php new file mode 100644 index 0000000..75b5e89 --- /dev/null +++ b/lang/es/passwords.php @@ -0,0 +1,11 @@ + 'Su contraseña ha sido restablecida.', + 'sent' => 'Le hemos enviado por correo electrónico el enlace para restablecer su contraseña.', + 'throttled' => 'Por favor espere antes de intentar de nuevo.', + 'token' => 'El token de restablecimiento de contraseña es inválido.', + 'user' => 'No encontramos ningún usuario con ese correo electrónico.', +]; diff --git a/lang/es/validation.php b/lang/es/validation.php new file mode 100644 index 0000000..99fef7f --- /dev/null +++ b/lang/es/validation.php @@ -0,0 +1,289 @@ + 'El campo :attribute debe ser aceptado.', + 'accepted_if' => 'El campo :attribute debe ser aceptado cuando :other sea :value.', + 'active_url' => 'El campo :attribute debe ser una URL válida.', + 'after' => 'El campo :attribute debe ser una fecha posterior a :date.', + 'after_or_equal' => 'El campo :attribute debe ser una fecha posterior o igual a :date.', + 'alpha' => 'El campo :attribute sólo debe contener letras.', + 'alpha_dash' => 'El campo :attribute sólo debe contener letras, números, guiones y guiones bajos.', + 'alpha_num' => 'El campo :attribute sólo debe contener letras y números.', + 'any_of' => 'El campo :attribute no es válido.', + 'array' => 'El campo :attribute debe ser un conjunto.', + 'ascii' => 'El campo :attribute solo debe contener caracteres alfanuméricos y símbolos de un solo byte.', + 'base64' => 'The :attribute field must be a valid Base64 string.', + 'before' => 'El campo :attribute debe ser una fecha anterior a :date.', + 'before_or_equal' => 'El campo :attribute debe ser una fecha anterior o igual a :date.', + 'between' => [ + 'array' => 'El campo :attribute tiene que tener entre :min - :max elementos.', + 'file' => 'El campo :attribute debe pesar entre :min - :max kilobytes.', + 'numeric' => 'El campo :attribute tiene que estar entre :min - :max.', + 'string' => 'El campo :attribute tiene que tener entre :min - :max caracteres.', + ], + 'boolean' => 'El campo :attribute debe tener un valor verdadero o falso.', + 'can' => 'El campo :attribute contiene un valor no autorizado.', + 'confirmed' => 'La confirmación de :attribute no coincide.', + 'contains' => 'Al campo :attribute le falta un valor obligatorio.', + 'current_password' => 'La contraseña es incorrecta.', + 'date' => 'El campo :attribute debe ser una fecha válida.', + 'date_equals' => 'El campo :attribute debe ser una fecha igual a :date.', + 'date_format' => 'El campo :attribute debe coincidir con el formato :format.', + 'decimal' => 'El campo :attribute debe tener :decimal cifras decimales.', + 'declined' => 'El campo :attribute debe ser rechazado.', + 'declined_if' => 'El campo :attribute debe ser rechazado cuando :other sea :value.', + 'different' => 'El campo :attribute y :other deben ser diferentes.', + 'digits' => 'El campo :attribute debe tener :digits dígitos.', + 'digits_between' => 'El campo :attribute debe tener entre :min y :max dígitos.', + 'dimensions' => 'El campo :attribute tiene dimensiones de imagen no válidas.', + 'distinct' => 'El campo :attribute contiene un valor duplicado.', + 'doesnt_contain' => 'El campo :attribute no debe contener ninguno de los siguientes valores: :values.', + 'doesnt_end_with' => 'El campo :attribute no debe finalizar con uno de los siguientes: :values.', + 'doesnt_start_with' => 'El campo :attribute no debe comenzar con uno de los siguientes: :values.', + 'email' => 'El campo :attribute no es un correo válido.', + 'encoding' => 'El campo :attribute debe estar codificado en :encoding.', + 'ends_with' => 'El campo :attribute debe finalizar con uno de los siguientes valores: :values', + 'enum' => 'El campo :attribute no está en la lista de valores permitidos.', + 'exists' => 'El campo :attribute no existe.', + 'extensions' => 'El campo :attribute debe tener una de las siguientes extensiones: :values.', + 'file' => 'El campo :attribute debe ser un archivo.', + 'filled' => 'El campo :attribute es obligatorio.', + 'gt' => [ + 'array' => 'El campo :attribute debe tener más de :value elementos.', + 'file' => 'El campo :attribute debe tener más de :value kilobytes.', + 'numeric' => 'El campo :attribute debe ser mayor que :value.', + 'string' => 'El campo :attribute debe tener más de :value caracteres.', + ], + 'gte' => [ + 'array' => 'El campo :attribute debe tener como mínimo :value elementos.', + 'file' => 'El campo :attribute debe tener como mínimo :value kilobytes.', + 'numeric' => 'El campo :attribute debe ser como mínimo :value.', + 'string' => 'El campo :attribute debe tener como mínimo :value caracteres.', + ], + 'hex_color' => 'El campo :attribute debe tener un color hexadecimal válido.', + 'image' => 'El campo :attribute debe ser una imagen.', + 'in' => 'El campo :attribute no está en la lista de valores permitidos.', + 'in_array' => 'El campo :attribute debe existir en :other.', + 'in_array_keys' => 'El campo :attribute debe contener al menos una de las siguientes claves: :values.', + 'integer' => 'El campo :attribute debe ser un número entero.', + 'ip' => 'El campo :attribute debe ser una dirección IP válida.', + 'ipv4' => 'El campo :attribute debe ser una dirección IPv4 válida.', + 'ipv6' => 'El campo :attribute debe ser una dirección IPv6 válida.', + 'json' => 'El campo :attribute debe ser una cadena JSON válida.', + 'list' => 'El campo :attribute debe ser una lista.', + 'lowercase' => 'El campo :attribute debe estar en minúscula.', + 'lt' => [ + 'array' => 'El campo :attribute debe tener menos de :value elementos.', + 'file' => 'El campo :attribute debe tener menos de :value kilobytes.', + 'numeric' => 'El campo :attribute debe ser menor que :value.', + 'string' => 'El campo :attribute debe tener menos de :value caracteres.', + ], + 'lte' => [ + 'array' => 'El campo :attribute debe tener como máximo :value elementos.', + 'file' => 'El campo :attribute debe tener como máximo :value kilobytes.', + 'numeric' => 'El campo :attribute debe ser como máximo :value.', + 'string' => 'El campo :attribute debe tener como máximo :value caracteres.', + ], + 'mac_address' => 'El campo :attribute debe ser una dirección MAC válida.', + 'max' => [ + 'array' => 'El campo :attribute no debe tener más de :max elementos.', + 'file' => 'El campo :attribute no debe ser mayor que :max kilobytes.', + 'numeric' => 'El campo :attribute no debe ser mayor que :max.', + 'string' => 'El campo :attribute no debe ser mayor que :max caracteres.', + ], + 'max_digits' => 'El campo :attribute no debe tener más de :max dígitos.', + 'mimes' => 'El campo :attribute debe ser un archivo con formato: :values.', + 'mimetypes' => 'El campo :attribute debe ser un archivo con formato: :values.', + 'min' => [ + 'array' => 'El campo :attribute debe tener al menos :min elementos.', + 'file' => 'El tamaño de :attribute debe ser de al menos :min kilobytes.', + 'numeric' => 'El tamaño de :attribute debe ser de al menos :min.', + 'string' => 'El campo :attribute debe contener al menos :min caracteres.', + ], + 'min_digits' => 'El campo :attribute debe tener al menos :min dígitos.', + 'missing' => 'El campo :attribute no debe estar presente.', + 'missing_if' => 'El campo :attribute no debe estar presente cuando :other sea :value.', + 'missing_unless' => 'El campo :attribute no debe estar presente a menos que :other sea :value.', + 'missing_with' => 'El campo :attribute no debe estar presente si alguno de los campos :values está presente.', + 'missing_with_all' => 'El campo :attribute no debe estar presente cuando los campos :values estén presentes.', + 'multiple_of' => 'El campo :attribute debe ser múltiplo de :value', + 'not_in' => 'El campo :attribute no debe estar en la lista.', + 'not_regex' => 'El formato del campo :attribute no es válido.', + 'numeric' => 'El campo :attribute debe ser numérico.', + 'password' => [ + 'letters' => 'La :attribute debe contener al menos una letra.', + 'mixed' => 'La :attribute debe contener al menos una letra mayúscula y una minúscula.', + 'numbers' => 'La :attribute debe contener al menos un número.', + 'symbols' => 'La :attribute debe contener al menos un símbolo.', + 'uncompromised' => 'La :attribute proporcionada se ha visto comprometida en una filtración de datos (data leak). Elija una :attribute diferente.', + ], + 'present' => 'El campo :attribute debe estar presente.', + 'present_if' => 'El campo :attribute debe estar presente cuando :other es :value.', + 'present_unless' => 'El campo :attribute debe estar presente a menos que :other sea :value.', + 'present_with' => 'El campo :attribute debe estar presente cuando :values esté presente.', + 'present_with_all' => 'El campo :attribute debe estar presente cuando :values estén presentes.', + 'prohibited' => 'El campo :attribute está prohibido.', + 'prohibited_if' => 'El campo :attribute está prohibido cuando :other es :value.', + 'prohibited_if_accepted' => 'El campo :attribute está prohibido cuando se acepta :other.', + 'prohibited_if_declined' => 'El campo :attribute está prohibido cuando se rechaza :other.', + 'prohibited_unless' => 'El campo :attribute está prohibido a menos que :other sea :values.', + 'prohibits' => 'El campo :attribute prohibe que :other esté presente.', + 'regex' => 'El formato del campo :attribute no es válido.', + 'required' => 'El campo :attribute es obligatorio.', + 'required_array_keys' => 'El campo :attribute debe contener entradas para: :values.', + 'required_if' => 'El campo :attribute es obligatorio cuando :other es :value.', + 'required_if_accepted' => 'El campo :attribute es obligatorio si :other es aceptado.', + 'required_if_declined' => 'El campo :attribute es obligatorio si :other es rechazado.', + 'required_unless' => 'El campo :attribute es obligatorio a menos que :other esté en :values.', + 'required_with' => 'El campo :attribute es obligatorio cuando :values está presente.', + 'required_with_all' => 'El campo :attribute es obligatorio cuando :values están presentes.', + 'required_without' => 'El campo :attribute es obligatorio cuando :values no está presente.', + 'required_without_all' => 'El campo :attribute es obligatorio cuando ninguno de :values está presente.', + 'same' => 'Los campos :attribute y :other deben coincidir.', + 'size' => [ + 'array' => 'El campo :attribute debe contener :size elementos.', + 'file' => 'El tamaño de :attribute debe ser :size kilobytes.', + 'numeric' => 'El tamaño de :attribute debe ser :size.', + 'string' => 'El campo :attribute debe contener :size caracteres.', + ], + 'starts_with' => 'El campo :attribute debe comenzar con uno de los siguientes valores: :values', + 'string' => 'El campo :attribute debe ser una cadena de caracteres.', + 'timezone' => 'El campo :attribute debe ser una zona horaria válida.', + 'ulid' => 'El campo :attribute debe ser un ULID válido.', + 'unique' => 'El campo :attribute ya ha sido registrado.', + 'uploaded' => 'Subir :attribute ha fallado.', + 'uppercase' => 'El campo :attribute debe estar en mayúscula.', + 'url' => 'El campo :attribute debe ser una URL válida.', + 'uuid' => 'El campo :attribute debe ser un UUID válido.', + 'attributes' => [ + 'address' => 'dirección', + 'affiliate_url' => 'URL de afiliado', + 'age' => 'edad', + 'amount' => 'cantidad', + 'announcement' => 'anuncio', + 'area' => 'área', + 'audience_prize' => 'premio del público', + 'audience_winner' => 'ganador del público', + 'available' => 'disponible', + 'birthday' => 'cumpleaños', + 'body' => 'contenido', + 'city' => 'ciudad', + 'color' => 'color', + 'company' => 'compañía', + 'compilation' => 'compilación', + 'concept' => 'concepto', + 'conditions' => 'condiciones', + 'content' => 'contenido', + 'contest' => 'concurso', + 'country' => 'país', + 'cover' => 'portada', + 'created_at' => 'creado el', + 'creator' => 'creador', + 'currency' => 'moneda', + 'current_password' => 'contraseña actual', + 'customer' => 'cliente', + 'date' => 'fecha', + 'date_of_birth' => 'fecha de nacimiento', + 'dates' => 'fechas', + 'day' => 'día', + 'deleted_at' => 'eliminado el', + 'description' => 'descripción', + 'display_type' => 'tipo de visualización', + 'district' => 'distrito', + 'duration' => 'duración', + 'email' => 'correo electrónico', + 'excerpt' => 'extracto', + 'filter' => 'filtro', + 'finished_at' => 'terminado el', + 'first_name' => 'nombre', + 'gender' => 'género', + 'grand_prize' => 'gran Premio', + 'group' => 'grupo', + 'hour' => 'hora', + 'image' => 'imagen', + 'image_desktop' => 'imagen de escritorio', + 'image_main' => 'imagen principal', + 'image_mobile' => 'imagen móvil', + 'images' => 'imágenes', + 'is_audience_winner' => 'es ganador de audiencia', + 'is_hidden' => 'está oculto', + 'is_subscribed' => 'está suscrito', + 'is_visible' => 'es visible', + 'is_winner' => 'es ganador', + 'items' => 'elementos', + 'key' => 'clave', + 'last_name' => 'apellidos', + 'lesson' => 'lección', + 'line_address_1' => 'línea de dirección 1', + 'line_address_2' => 'línea de dirección 2', + 'login' => 'acceso', + 'message' => 'mensaje', + 'middle_name' => 'segundo nombre', + 'minute' => 'minuto', + 'mobile' => 'móvil', + 'month' => 'mes', + 'name' => 'nombre', + 'national_code' => 'código nacional', + 'number' => 'número', + 'password' => 'contraseña', + 'password_confirmation' => 'confirmación de la contraseña', + 'phone' => 'teléfono', + 'photo' => 'foto', + 'portfolio' => 'portafolio', + 'postal_code' => 'código postal', + 'preview' => 'vista preliminar', + 'price' => 'precio', + 'product_id' => 'ID del producto', + 'product_uid' => 'UID del producto', + 'product_uuid' => 'UUID del producto', + 'promo_code' => 'código promocional', + 'province' => 'provincia', + 'quantity' => 'cantidad', + 'reason' => 'razón', + 'recaptcha_response_field' => 'respuesta del recaptcha', + 'referee' => 'árbitro', + 'referees' => 'árbitros', + 'region' => 'región', + 'reject_reason' => 'motivo de rechazo', + 'remember' => 'recordar', + 'restored_at' => 'restaurado el', + 'result_text_under_image' => 'texto bajo la imagen', + 'role' => 'rol', + 'rule' => 'regla', + 'rules' => 'reglas', + 'second' => 'segundo', + 'sex' => 'sexo', + 'shipment' => 'envío', + 'short_text' => 'texto corto', + 'size' => 'tamaño', + 'skills' => 'habilidades', + 'slug' => 'slug', + 'specialization' => 'especialización', + 'started_at' => 'comenzado el', + 'state' => 'estado', + 'status' => 'estado', + 'street' => 'calle', + 'student' => 'estudiante', + 'subject' => 'asunto', + 'tag' => 'etiqueta', + 'tags' => 'etiquetas', + 'teacher' => 'profesor', + 'terms' => 'términos', + 'test_description' => 'descripción de prueba', + 'test_locale' => 'idioma de prueba', + 'test_name' => 'nombre de prueba', + 'text' => 'texto', + 'time' => 'hora', + 'title' => 'título', + 'type' => 'tipo', + 'updated_at' => 'actualizado el', + 'user' => 'usuario', + 'username' => 'usuario', + 'value' => 'valor', + 'winner' => 'ganador', + 'work' => 'trabajo', + 'year' => 'año', + ], +]; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9e50a9f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2944 @@ +{ + "name": "oauth", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "axios": "^1.18.1", + "pinia": "^4.0.2", + "vue": "^3.5.40", + "vue-i18n": "^11.4.7", + "vue-router": "^5.2.0" + }, + "devDependencies": { + "@types/node": "^26.1.1", + "@vitejs/plugin-vue": "^6.0.8", + "@vue/tsconfig": "^0.9.1", + "concurrently": "^9.0.1", + "laravel-vite-plugin": "^3.1", + "sass": "^1.102.0", + "typescript": "^6.0.3", + "vite": "^8.0.0", + "vue-tsc": "^3.3.8" + } + }, + "node_modules/@babel/generator": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0.tgz", + "integrity": "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^8.0.0", + "@babel/types": "^8.0.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "@types/jsesc": "^2.5.0", + "jsesc": "^3.0.2" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/generator/node_modules/@babel/helper-string-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz", + "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/generator/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz", + "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==", + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/generator/node_modules/@babel/parser": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.4.tgz", + "integrity": "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==", + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/generator/node_modules/@babel/types": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz", + "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.0", + "@babel/helper-validator-identifier": "^8.0.4" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@intlify/core-base": { + "version": "11.4.7", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.4.7.tgz", + "integrity": "sha512-MSB/sBKwEWJTILvQIhg2rnIcwPpLayo3wGwvVA+dJTNeUBD9GoqQgAaSOLdI9iOPDHCm9YoVnLqpfzza98MpkQ==", + "license": "MIT", + "dependencies": { + "@intlify/devtools-types": "11.4.7", + "@intlify/message-compiler": "11.4.7", + "@intlify/shared": "11.4.7" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/devtools-types": { + "version": "11.4.7", + "resolved": "https://registry.npmjs.org/@intlify/devtools-types/-/devtools-types-11.4.7.tgz", + "integrity": "sha512-GSz+J+hqH+AEpAHIYya6fSufS30OaMnG39HiZX7DmGKi3+aaLvassCfsXENEc4Wr4m68q2YP0QdMdB3D9UeAXg==", + "license": "MIT", + "dependencies": { + "@intlify/core-base": "11.4.7", + "@intlify/shared": "11.4.7" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/message-compiler": { + "version": "11.4.7", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.4.7.tgz", + "integrity": "sha512-bHxmh7n94N4N1evADeb7XTkc3jTw6Ki5biMFZVSX6Jmk+iehy8/maeH2XUsBI27rtKIK+Hzc6QnVAKggUwylKw==", + "license": "MIT", + "dependencies": { + "@intlify/shared": "11.4.7", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/shared": { + "version": "11.4.7", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.4.7.tgz", + "integrity": "sha512-OtjPZan3No2OZZFnMUiCVsXC6+j+XRwEywaFDk0AoayAbLuPesyDloXhJZLl9JUl5vHZeQUkYSbEA8VX+CWMjg==", + "license": "MIT", + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "devOptional": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz", + "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.6.0", + "@parcel/watcher-darwin-arm64": "2.6.0", + "@parcel/watcher-darwin-x64": "2.6.0", + "@parcel/watcher-freebsd-x64": "2.6.0", + "@parcel/watcher-linux-arm-glibc": "2.6.0", + "@parcel/watcher-linux-arm-musl": "2.6.0", + "@parcel/watcher-linux-arm64-glibc": "2.6.0", + "@parcel/watcher-linux-arm64-musl": "2.6.0", + "@parcel/watcher-linux-x64-glibc": "2.6.0", + "@parcel/watcher-linux-x64-musl": "2.6.0", + "@parcel/watcher-win32-arm64": "2.6.0", + "@parcel/watcher-win32-x64": "2.6.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz", + "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz", + "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz", + "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz", + "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz", + "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz", + "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz", + "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz", + "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz", + "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz", + "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz", + "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz", + "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/jsesc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.1.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", + "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.8.tgz", + "integrity": "sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.28" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue-macros/common": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-3.1.4.tgz", + "integrity": "sha512-/5Fv+6DgIcM9ajY05ZmKBv+LMX1M9A0X+IUwDRVdt67ciw8OV9bvG2r34p3RiEadlsQybjhKPRKNXDC8Bp23cw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-sfc": "^3.5.22", + "ast-kit": "^2.1.2", + "local-pkg": "^1.1.2", + "magic-string-ast": "^1.0.2", + "unplugin-utils": "^0.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/vue-macros" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.2.25" + }, + "peerDependenciesMeta": { + "vue": { + "optional": true + } + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.40.tgz", + "integrity": "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.40", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.40.tgz", + "integrity": "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.40.tgz", + "integrity": "sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.40", + "@vue/compiler-dom": "3.5.40", + "@vue/compiler-ssr": "3.5.40", + "@vue/shared": "3.5.40", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.19", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.40.tgz", + "integrity": "sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/devtools-api": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-8.1.5.tgz", + "integrity": "sha512-YJipMVAKe5wT5CWf5kTYCaNV7NMNjFVxJkIkJaJ4W/nCxEBzlZzrOsYKeCymdCrFZmBS/+wTWFoUs3Jf/Q6XSQ==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^8.1.5" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-8.1.5.tgz", + "integrity": "sha512-FcSAxsi4eWuXLCB7Rv9lj0aIVHHPNVQ2BazGf4RJTc2JCqb4BQg0hk87ZFhminCfl+mD5OUI0rX2cgyu4kJOGA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^8.1.5", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "perfect-debounce": "^2.0.0" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-8.1.5.tgz", + "integrity": "sha512-mhT4zcPFhF+Xk1O4BfhhrbXzpmfqY03fS6xGpcllbQG7lDjhQf8pQHcTIhqQIYx1hfwtHmk/6jM96ele0UxPqQ==", + "license": "MIT" + }, + "node_modules/@vue/language-core": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.8.tgz", + "integrity": "sha512-ieGT8jJdhhy0mGzStZhsg/qPw5bQZJg5yF+3+XU6saf4sM7yo9ZXy3h+nCwrm2+b4qS/SypkNdR2jAF3uei9tA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.2.1", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.4" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.40.tgz", + "integrity": "sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.40.tgz", + "integrity": "sha512-KAZLweuZ6uUJPK1PMSQPgBU5gCjgrrfjUhSglmU9NhH+Zjepa8cnwSydPWDWHDwOgY4g3VcZ+PljbiHlURNCbw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.40.tgz", + "integrity": "sha512-ZfrX8ssZQds900L9pr8AuK05ddnMsR4MPMZr8cPN9GoqoPWcXLhjvvbIA2SMv+7a97sJ1vv9pj/zxK0Cq/eEFQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.40", + "@vue/runtime-core": "3.5.40", + "@vue/shared": "3.5.40", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.40.tgz", + "integrity": "sha512-XNJym9WpevhTVt1HuwOrCRJ5Q+9z4BjTMrDtjTrvx74SmUll8spNTw6whWJa9mEkO4PKn5TihI/bm/8ds2QVJw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.40", + "@vue/runtime-dom": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.40.tgz", + "integrity": "sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==", + "license": "MIT" + }, + "node_modules/@vue/tsconfig": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.9.1.tgz", + "integrity": "sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">= 5.8", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/alien-signals": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz", + "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ast-kit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.2.0.tgz", + "integrity": "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "pathe": "^2.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/ast-walker-scope": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.9.0.tgz", + "integrity": "sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.2", + "@babel/types": "^7.29.0", + "ast-kit": "^2.2.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concurrently": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.4.tgz", + "integrity": "sha512-TZ0CEhyzvFjgtAvHTusDMgj7wNdihCh7LLLrzdUOXIhdlnL2JBBGA9eJxR24rtqgmdjh3OA3hrN1rCHj6HM8qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.9.0", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/exsolve": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.0.tgz", + "integrity": "sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/immutable": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", + "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/laravel-vite-plugin": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-3.1.3.tgz", + "integrity": "sha512-cI5Anw4QHY+UzvZczFaj+j8NhwT2FtyEN8aqS/hOdt6DpEFBsn6x3GENxALem3cc+TsGvd9MacneimEvShvKMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "tinyglobby": "^0.2.12", + "vite-plugin-full-reload": "^1.1.0" + }, + "bin": { + "clean-orphaned-assets": "bin/clean.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "fontaine": "^0.8.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "fontaine": { + "optional": true + } + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "devOptional": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/local-pkg": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz", + "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==", + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magic-string-ast": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/magic-string-ast/-/magic-string-ast-1.0.3.tgz", + "integrity": "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==", + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.19" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/nostics": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/nostics/-/nostics-1.2.0.tgz", + "integrity": "sha512-FGqEfhQjrvo1lL8KFifdTQiNwwQHJxC1jtYE1Rc54qF/jxONUNL+kC9gS1krX8Q65PgrQ5fCqH/I4NhWBvdSqg==", + "license": "MIT" + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-4.0.2.tgz", + "integrity": "sha512-yKVVA7bSj5oRZFp/Ab9wLlmyb5gPUYEiIm4ryiWTe/xe7PtkRdMVOp1X1ggvq0c6Uj7Q0Du1HnV2mtAwM0Ks1g==", + "license": "MIT", + "dependencies": { + "nostics": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@vue/devtools-api": "^8.1.5", + "typescript": ">=5.6.0", + "vue": "^3.5.11" + }, + "peerDependenciesMeta": { + "@vue/devtools-api": { + "optional": false + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/pkg-types": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "license": "MIT", + "dependencies": { + "confbox": "^0.2.4", + "exsolve": "^1.0.8", + "pathe": "^2.0.3" + } + }, + "node_modules/postcss": { + "version": "8.5.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rolldown": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.139.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/sass": { + "version": "1.102.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.102.0.tgz", + "integrity": "sha512-NSOyTnaQF7rTAEOtI2fwb386vL+akyiQLBZu8Na7hXCb+umJy0GAqlcMIaqACZ6Z1VgTBS4K9PG6B3IdjHGJsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^5.0.0", + "immutable": "^5.1.5", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=20.19.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "license": "MIT" + }, + "node_modules/shell-quote": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", + "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unplugin": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.3.0.tgz", + "integrity": "sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "picomatch": "^4.0.4", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@farmfe/core": "*", + "@rspack/core": "*", + "bun-types-no-globals": "*", + "esbuild": "*", + "rolldown": "*", + "rollup": "*", + "unloader": "*", + "vite": "*", + "webpack": "*" + }, + "peerDependenciesMeta": { + "@farmfe/core": { + "optional": true + }, + "@rspack/core": { + "optional": true + }, + "bun-types-no-globals": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "rolldown": { + "optional": true + }, + "rollup": { + "optional": true + }, + "unloader": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/unplugin-utils": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.2.tgz", + "integrity": "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==", + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/vite": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", + "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.17", + "rolldown": "~1.1.5", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-full-reload": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-full-reload/-/vite-plugin-full-reload-1.2.0.tgz", + "integrity": "sha512-kz18NW79x0IHbxRSHm0jttP4zoO9P9gXh+n6UTwlNKnviTTEpOlum6oS9SmecrTtSr+muHEn5TUuC75UovQzcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "picomatch": "^2.3.1" + } + }, + "node_modules/vite-plugin-full-reload/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.40.tgz", + "integrity": "sha512-+8PJ4SJXdn/cHGImF4CKdxlWHIN5Dkt7DoufRREM6h6uVCx2m7QxgcEQmmzyOK8A9mcafg7sFbJFYsdFVubTig==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.40", + "@vue/compiler-sfc": "3.5.40", + "@vue/runtime-dom": "3.5.40", + "@vue/server-renderer": "3.5.40", + "@vue/shared": "3.5.40" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-i18n": { + "version": "11.4.7", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.4.7.tgz", + "integrity": "sha512-j6RyshdPPzqLiMAUpnpvZGFPM+rRoWi14Sl5yTsquvoW0/56DWyvhAj2o9TO2YXGvb6teg8T0xrYO9jR3urvdw==", + "license": "MIT", + "dependencies": { + "@intlify/core-base": "11.4.7", + "@intlify/devtools-types": "11.4.7", + "@intlify/shared": "11.4.7", + "@vue/devtools-api": "^6.5.0" + }, + "engines": { + "node": ">= 22" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vue-i18n/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/vue-router": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.2.0.tgz", + "integrity": "sha512-QAC5i0LEb1GLG0LXDQmHu8L7FX12j0KwU/JTKmLQUJMrn04gQdKP6Du+p0QwpHb3iy71vBlqnHQ8WAfOSAWhqw==", + "license": "MIT", + "dependencies": { + "@babel/generator": "^8.0.0", + "@vue-macros/common": "^3.1.3", + "@vue/devtools-api": "^8.1.5", + "ast-walker-scope": "^0.9.0", + "chokidar": "^5.0.0", + "json5": "^2.2.3", + "local-pkg": "^1.2.1", + "magic-string": "^0.30.21", + "mlly": "^1.8.2", + "muggle-string": "^0.4.1", + "nostics": "^1.1.4", + "pathe": "^2.0.3", + "picomatch": "^4.0.5", + "scule": "^1.3.0", + "tinyglobby": "^0.2.17", + "unplugin": "^3.3.0", + "unplugin-utils": "^0.3.2", + "yaml": "^2.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@pinia/colada": ">=0.21.2", + "@vue/compiler-sfc": "^3.5.34 || ^4.0.0", + "pinia": "^3.0.4 || ^4.0.2", + "vite": "^7.3.0 || ^8.0.0", + "vue": "^3.5.34 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@pinia/colada": { + "optional": true + }, + "@vue/compiler-sfc": { + "optional": true + }, + "pinia": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vue-tsc": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.8.tgz", + "integrity": "sha512-xXmYlVQpcwJDWyGlqbHrGVOl1h3UOsASymRibrHc+iy9j/UNnOrOn4u+fntHz4D6Cs74RtapeqVV6CzJeg+UlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.28", + "@vue/language-core": "3.3.8" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0b5cbf0 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://www.schemastore.org/package.json", + "private": true, + "type": "module", + "scripts": { + "build": "vite build", + "dev": "vite", + "type-check": "vue-tsc --noEmit" + }, + "devDependencies": { + "@types/node": "^26.1.1", + "@vitejs/plugin-vue": "^6.0.8", + "@vue/tsconfig": "^0.9.1", + "concurrently": "^9.0.1", + "laravel-vite-plugin": "^3.1", + "sass": "^1.102.0", + "typescript": "^6.0.3", + "vite": "^8.0.0", + "vue-tsc": "^3.3.8" + }, + "dependencies": { + "axios": "^1.18.1", + "pinia": "^4.0.2", + "vue": "^3.5.40", + "vue-i18n": "^11.4.7", + "vue-router": "^5.2.0" + } +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..e7f0a48 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,36 @@ + + + + + tests/Unit + + + tests/Feature + + + + + app + + + + + + + + + + + + + + + + + + + diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..b574a59 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,25 @@ + + + Options -MultiViews -Indexes + + + RewriteEngine On + + # Handle Authorization Header + RewriteCond %{HTTP:Authorization} . + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + + # Handle X-XSRF-Token Header + RewriteCond %{HTTP:x-xsrf-token} . + RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}] + + # 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/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..ee8f07e --- /dev/null +++ b/public/index.php @@ -0,0 +1,20 @@ +handleRequest(Request::capture()); 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/js/App.vue b/resources/js/App.vue new file mode 100644 index 0000000..97c432c --- /dev/null +++ b/resources/js/App.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/resources/js/app.ts b/resources/js/app.ts new file mode 100644 index 0000000..41c71d6 --- /dev/null +++ b/resources/js/app.ts @@ -0,0 +1,13 @@ +import './bootstrap'; + +import { createApp } from 'vue'; +import { createPinia } from 'pinia'; +import App from './App.vue'; +import router from './router'; +import i18n from './i18n'; + +createApp(App) + .use(createPinia()) + .use(router) + .use(i18n) + .mount('#app'); diff --git a/resources/js/bootstrap.ts b/resources/js/bootstrap.ts new file mode 100644 index 0000000..8e9d770 --- /dev/null +++ b/resources/js/bootstrap.ts @@ -0,0 +1,13 @@ +import axios from 'axios'; + +declare global { + interface Window { + axios: typeof axios; + } +} + +window.axios = axios; + +window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; +window.axios.defaults.withCredentials = true; +window.axios.defaults.withXSRFToken = true; diff --git a/resources/js/components/charts/BarList.vue b/resources/js/components/charts/BarList.vue new file mode 100644 index 0000000..9a9a6e7 --- /dev/null +++ b/resources/js/components/charts/BarList.vue @@ -0,0 +1,45 @@ + + + diff --git a/resources/js/components/charts/DailyLoginsChart.vue b/resources/js/components/charts/DailyLoginsChart.vue new file mode 100644 index 0000000..f6d5003 --- /dev/null +++ b/resources/js/components/charts/DailyLoginsChart.vue @@ -0,0 +1,328 @@ + + + + + diff --git a/resources/js/components/charts/StatTile.vue b/resources/js/components/charts/StatTile.vue new file mode 100644 index 0000000..ad2ee6c --- /dev/null +++ b/resources/js/components/charts/StatTile.vue @@ -0,0 +1,15 @@ + + + diff --git a/resources/js/composables/useTheme.ts b/resources/js/composables/useTheme.ts new file mode 100644 index 0000000..8b41a3c --- /dev/null +++ b/resources/js/composables/useTheme.ts @@ -0,0 +1,16 @@ +import { ref } from 'vue'; + +// El estado inicial ya lo aplicó el script inline en app.blade.php (evita el +// flash); acá solo reflejamos esa clase para que el ícono del toggle arranque +// sincronizado. +const isDark = ref(document.documentElement.classList.contains('dark')); + +function toggleTheme(): void { + isDark.value = !isDark.value; + document.documentElement.classList.toggle('dark', isDark.value); + localStorage.setItem('theme', isDark.value ? 'dark' : 'light'); +} + +export function useTheme() { + return { isDark, toggleTheme }; +} diff --git a/resources/js/i18n/ca.ts b/resources/js/i18n/ca.ts new file mode 100644 index 0000000..ffb955b --- /dev/null +++ b/resources/js/i18n/ca.ts @@ -0,0 +1,209 @@ +import type { MessageSchema } from './es'; + +const ca: MessageSchema = { + nav: { + dashboard: 'Tauler', + clients: 'Clients OAuth', + tokens: 'Personal Access Tokens', + authorizedApps: 'Apps autoritzades', + users: 'Usuaris', + analytics: 'Analítiques', + apiDocs: 'API Docs', + logout: 'Sortir', + }, + common: { + loading: 'Carregant…', + creating: 'Creant…', + create: 'Crear', + save: 'Desar', + cancel: 'Cancel·lar', + close: 'Tancar', + edit: 'Editar', + delete: 'Esborrar', + none: '(cap)', + }, + login: { + title: 'Entrar', + subtitle: "Servidor OAuth2 — tauler d'administració.", + email: 'Email', + password: 'Contrasenya', + submit: 'Entrar', + submitting: 'Entrant…', + noAccount: 'No tens compte?', + signUp: "Registra't", + invalidCredentials: 'Credencials no vàlides.', + }, + register: { + title: 'Crear compte', + name: 'Nom', + email: 'Email', + password: 'Contrasenya', + passwordConfirm: 'Confirmar contrasenya', + submit: 'Crear compte', + submitFirstUser: 'Crear compte d\'administrador', + submitting: 'Creant…', + haveAccount: 'Ja tens compte?', + signIn: 'Entra', + firstUserBanner: 'Encara no hi ha cap usuari. Aquest compte quedarà com a administrador, amb accés immediat (sense aprovació).', + pendingTitle: 'Compte creat', + pendingBody: "Un administrador ha d'aprovar el teu compte abans que puguis entrar. És possible que t'avisin fora de l'app quan estigui llest.", + backToLogin: 'Tornar a entrar', + }, + dashboard: { + greeting: 'Hola, {name}', + subtitle: "Aquest és el tauler d'administració del servidor OAuth2.", + clientsTitle: 'Clients OAuth', + clientsDesc: 'Registra apps (Authorization Code+PKCE, Client Credentials o Password Grant).', + tokensTitle: 'Personal Access Tokens', + tokensDesc: "Genera tokens d'accés directe a l'API, com API keys.", + appsTitle: 'Apps autoritzades', + appsDesc: 'Apps de tercers a les quals has donat accés al teu compte.', + usersTitle: 'Usuaris', + usersDesc: 'Alta, edició de rol/estat i baixa de comptes (només admins).', + analyticsTitle: 'Analítiques', + analyticsDesc: 'Sessions actives, logins per app i usuaris més actius (només admins).', + apiTitle: 'API REST', + apiDesc: 'Aquest servidor exposa el recurs {resource} (scopes {read}/{write}) perquè aplicacions de tercers gestionin usuaris via OAuth2. Requereix un access token de Passport. Documentació interactiva a {link}.', + }, + clients: { + title: 'Clients OAuth', + subtitle: 'Apps registrades que poden demanar tokens a aquest servidor.', + secretWarning: 'Desa aquest client secret ara, no el podràs tornar a veure:', + clientIdLabel: 'client_id:', + clientSecretLabel: 'client_secret:', + newClient: 'Nou client', + name: 'Nom', + grantType: 'Grant type', + grantAuthCode: 'Authorization Code + PKCE', + grantClientCredentials: 'Client Credentials', + grantPassword: 'Password Grant', + redirectUris: 'Redirect URIs (separades per coma)', + confidentialLabel: "Client confidencial (amb secret). Desmarca-ho per a un client públic que només faci servir PKCE.", + create: 'Crear client', + empty: 'Encara no has creat cap client.', + revoke: 'Revocar', + revokeConfirm: 'Vols revocar el client "{name}"? Els seus tokens deixaran de funcionar.', + clientIdShort: 'client_id', + redirectLabel: 'redirect', + }, + tokens: { + title: 'Personal Access Tokens', + subtitle: "Tokens d'accés directe a l'API, sense passar pel flux d'authorize (tipus API key).", + tokenWarning: 'Desa aquest token ara, no el podràs tornar a veure:', + newToken: 'Nou token', + name: 'Nom', + namePlaceholder: 'El meu script', + scopes: 'Scopes', + create: 'Crear token', + empty: 'Encara no has creat cap token.', + revoke: 'Revocar', + revokeConfirm: 'Vols revocar el token "{name}"?', + scopesLabel: 'scopes', + }, + authorizedApps: { + title: 'Apps autoritzades', + subtitle: 'Aplicacions de tercers a les quals has donat accés al teu compte.', + empty: 'Encara no has autoritzat cap app.', + revoke: 'Revocar accés', + revokeConfirm: "Vols revocar l'accés de \"{name}\"?", + scopesLabel: 'scopes', + }, + authorize: { + title: 'Autoritzar aplicació', + wantsAccess: '{client} vol accedir al teu compte ({email}) amb els següents permisos:', + noScopes: "No sol·licita permisos addicionals (accés bàsic).", + approve: 'Autoritzar', + cancel: 'Cancel·lar', + notFoundTitle: 'Sol·licitud no trobada', + notFoundBody: "Aquesta pantalla només funciona quan hi arribes redirigit per una aplicació client via {path}. Torna-ho a provar des d'aquella app.", + goDashboard: 'Anar al tauler', + }, + adminUsers: { + title: 'Usuaris', + subtitle: 'Alta, edició de rol/estat i baixa de comptes.', + newUser: 'Nou usuari', + name: 'Nom', + email: 'Email', + password: 'Contrasenya', + role: 'Rol', + roleUser: 'Usuari', + roleAdmin: 'Admin', + create: 'Crear usuari', + colName: 'Nom', + colEmail: 'Email', + colRole: 'Rol', + colStatus: 'Estat', + active: 'Actiu', + inactive: 'Desactivat', + noRole: '(sense rol)', + edit: 'Editar', + delete: 'Esborrar', + deleteConfirm: 'Vols esborrar l\'usuari "{name}"? Anirà a la paperera i es pot restaurar.', + deleteFailed: "No s'ha pogut esborrar l'usuari.", + newPassword: 'Nova contrasenya', + noChange: '(sense canvis)', + save: 'Desar', + cancel: 'Cancel·lar', + appRolesTitle: 'Rols per app', + appRolesDesc: 'El rol global de dalt mana en aquest panell. Aquí es defineix quin rol té {name} dins de cada app quan aquella app demana el seu usuari per API.', + noAppRoles: 'Encara no hi ha rols assignats en cap app.', + chooseApp: 'Tria una app…', + assign: 'Assignar', + assignFailed: "No s'ha pogut assignar el rol.", + trashTitle: 'Paperera', + viewTrash: 'Veure paperera', + viewActive: 'Veure actius', + trashEmpty: 'No hi ha usuaris esborrats.', + deletedAt: 'Esborrat el {date}', + restore: 'Restaurar', + restoreFailed: "No s'ha pogut restaurar l'usuari.", + forceDelete: 'Esborrar definitivament', + forceDeleteConfirm: 'Vols esborrar definitivament "{name}"? Aquesta acció no es pot desfer.', + forceDeleteFailed: "No s'ha pogut esborrar definitivament.", + pendingTitle: 'Pendents', + viewPending: 'Pendents', + pendingEmpty: "No hi ha registres pendents d'aprovació.", + registeredAt: 'Es va registrar el {date}', + approve: 'Aprovar', + approveFailed: "No s'ha pogut aprovar l'usuari.", + reject: 'Rebutjar', + rejectConfirm: 'Vols rebutjar el registre de "{name}"? Anirà a la paperera i es pot restaurar després.', + rejectFailed: "No s'ha pogut rebutjar el registre.", + }, + analytics: { + title: 'Analítiques', + subtitle: "Sessions, logins per app i activitat d'usuaris.", + last7: 'Últims 7 dies', + last30: 'Últims 30 dies', + last90: 'Últims 90 dies', + totalUsers: 'Usuaris totals', + activeSessions: 'Sessions actives', + activeSessionsHint: 'tokens OAuth vigents', + loginsToday: 'Logins avui', + logins7d: 'Logins últims 7 dies', + loginsByDay: 'Logins per dia', + appsOAuth: 'Apps (OAuth)', + panelAdmin: 'Tauler admin', + loginsByApp: 'Logins per app', + topUsers: 'Usuaris més actius', + noAppLogins: 'Encara no hi ha logins d\'apps en aquest període.', + noUserActivity: 'Encara no hi ha activitat en aquest període.', + recentActivity: 'Activitat recent', + hide: 'Amagar', + viewAll: 'Veure-ho tot', + colUser: 'Usuari', + colApp: 'App', + colType: 'Tipus', + colIp: 'IP', + colWhen: 'Quan', + serviceNoUser: '(servei, sense usuari)', + deletedApp: '(app esborrada)', + recentCount: '{count} esdeveniments recents — clica "Veure-ho tot" per a la taula completa.', + chartAriaLabel: 'Logins per dia, apilat per tipus', + tooltipAppLabel: 'Apps (OAuth):', + tooltipSessionLabel: 'Tauler admin:', + barAriaLabel: "{date}: {oauth} logins d'apps, {session} al tauler admin", + }, +}; + +export default ca; diff --git a/resources/js/i18n/en.ts b/resources/js/i18n/en.ts new file mode 100644 index 0000000..31239ff --- /dev/null +++ b/resources/js/i18n/en.ts @@ -0,0 +1,209 @@ +import type { MessageSchema } from './es'; + +const en: MessageSchema = { + nav: { + dashboard: 'Dashboard', + clients: 'OAuth Clients', + tokens: 'Personal Access Tokens', + authorizedApps: 'Authorized Apps', + users: 'Users', + analytics: 'Analytics', + apiDocs: 'API Docs', + logout: 'Sign out', + }, + common: { + loading: 'Loading…', + creating: 'Creating…', + create: 'Create', + save: 'Save', + cancel: 'Cancel', + close: 'Close', + edit: 'Edit', + delete: 'Delete', + none: '(none)', + }, + login: { + title: 'Sign in', + subtitle: 'OAuth2 server — admin panel.', + email: 'Email', + password: 'Password', + submit: 'Sign in', + submitting: 'Signing in…', + noAccount: "Don't have an account?", + signUp: 'Sign up', + invalidCredentials: 'Invalid credentials.', + }, + register: { + title: 'Create account', + name: 'Name', + email: 'Email', + password: 'Password', + passwordConfirm: 'Confirm password', + submit: 'Create account', + submitFirstUser: 'Create admin account', + submitting: 'Creating…', + haveAccount: 'Already have an account?', + signIn: 'Sign in', + firstUserBanner: "There are no users yet. This account will be the administrator, with immediate access (no approval needed).", + pendingTitle: 'Account created', + pendingBody: 'An administrator has to approve your account before you can sign in. You may be notified outside the app once it is ready.', + backToLogin: 'Back to sign in', + }, + dashboard: { + greeting: 'Hi, {name}', + subtitle: "This is the OAuth2 server's admin panel.", + clientsTitle: 'OAuth Clients', + clientsDesc: 'Register apps (Authorization Code+PKCE, Client Credentials or Password Grant).', + tokensTitle: 'Personal Access Tokens', + tokensDesc: 'Generate tokens for direct API access, like API keys.', + appsTitle: 'Authorized Apps', + appsDesc: "Third-party apps you've granted access to your account.", + usersTitle: 'Users', + usersDesc: 'Create, edit role/status and delete accounts (admins only).', + analyticsTitle: 'Analytics', + analyticsDesc: 'Active sessions, logins by app and most active users (admins only).', + apiTitle: 'REST API', + apiDesc: 'This server exposes the {resource} resource (scopes {read}/{write}) so third-party apps can manage users via OAuth2. Requires a Passport access token. Interactive docs at {link}.', + }, + clients: { + title: 'OAuth Clients', + subtitle: 'Registered apps that can request tokens from this server.', + secretWarning: "Save this client secret now, you won't be able to see it again:", + clientIdLabel: 'client_id:', + clientSecretLabel: 'client_secret:', + newClient: 'New client', + name: 'Name', + grantType: 'Grant type', + grantAuthCode: 'Authorization Code + PKCE', + grantClientCredentials: 'Client Credentials', + grantPassword: 'Password Grant', + redirectUris: 'Redirect URIs (comma-separated)', + confidentialLabel: 'Confidential client (with secret). Uncheck for a public client that only uses PKCE.', + create: 'Create client', + empty: "You haven't created any clients yet.", + revoke: 'Revoke', + revokeConfirm: 'Revoke client "{name}"? Its tokens will stop working.', + clientIdShort: 'client_id', + redirectLabel: 'redirect', + }, + tokens: { + title: 'Personal Access Tokens', + subtitle: 'Direct API access tokens, without going through the authorize flow (like an API key).', + tokenWarning: "Save this token now, you won't be able to see it again:", + newToken: 'New token', + name: 'Name', + namePlaceholder: 'My script', + scopes: 'Scopes', + create: 'Create token', + empty: "You haven't created any tokens yet.", + revoke: 'Revoke', + revokeConfirm: 'Revoke token "{name}"?', + scopesLabel: 'scopes', + }, + authorizedApps: { + title: 'Authorized Apps', + subtitle: "Third-party apps you've granted access to your account.", + empty: "You haven't authorized any app yet.", + revoke: 'Revoke access', + revokeConfirm: 'Revoke access for "{name}"?', + scopesLabel: 'scopes', + }, + authorize: { + title: 'Authorize application', + wantsAccess: '{client} wants to access your account ({email}) with the following permissions:', + noScopes: 'No additional permissions requested (basic access).', + approve: 'Authorize', + cancel: 'Cancel', + notFoundTitle: 'Request not found', + notFoundBody: 'This screen only works when you arrive here redirected by a client application via {path}. Try again from that app.', + goDashboard: 'Go to dashboard', + }, + adminUsers: { + title: 'Users', + subtitle: 'Create, edit role/status and delete accounts.', + newUser: 'New user', + name: 'Name', + email: 'Email', + password: 'Password', + role: 'Role', + roleUser: 'User', + roleAdmin: 'Admin', + create: 'Create user', + colName: 'Name', + colEmail: 'Email', + colRole: 'Role', + colStatus: 'Status', + active: 'Active', + inactive: 'Deactivated', + noRole: '(no role)', + edit: 'Edit', + delete: 'Delete', + deleteConfirm: "Delete user \"{name}\"? They'll go to the trash and can be restored.", + deleteFailed: 'Could not delete the user.', + newPassword: 'New password', + noChange: '(no change)', + save: 'Save', + cancel: 'Cancel', + appRolesTitle: 'Roles per app', + appRolesDesc: 'The global role above rules this panel. Here you set what role {name} has inside each app when that app asks for their user via API.', + noAppRoles: 'No roles assigned in any app yet.', + chooseApp: 'Choose app…', + assign: 'Assign', + assignFailed: 'Could not assign the role.', + trashTitle: 'Trash', + viewTrash: 'View trash', + viewActive: 'View active', + trashEmpty: 'No deleted users.', + deletedAt: 'Deleted on {date}', + restore: 'Restore', + restoreFailed: 'Could not restore the user.', + forceDelete: 'Delete permanently', + forceDeleteConfirm: 'Permanently delete "{name}"? This action cannot be undone.', + forceDeleteFailed: 'Could not permanently delete the user.', + pendingTitle: 'Pending', + viewPending: 'Pending', + pendingEmpty: 'No registrations pending approval.', + registeredAt: 'Registered on {date}', + approve: 'Approve', + approveFailed: 'Could not approve the user.', + reject: 'Reject', + rejectConfirm: 'Reject the registration for "{name}"? It will go to the trash and can be restored later.', + rejectFailed: 'Could not reject the registration.', + }, + analytics: { + title: 'Analytics', + subtitle: 'Sessions, logins by app and user activity.', + last7: 'Last 7 days', + last30: 'Last 30 days', + last90: 'Last 90 days', + totalUsers: 'Total users', + activeSessions: 'Active sessions', + activeSessionsHint: 'valid OAuth tokens', + loginsToday: 'Logins today', + logins7d: 'Logins last 7 days', + loginsByDay: 'Logins by day', + appsOAuth: 'Apps (OAuth)', + panelAdmin: 'Admin panel', + loginsByApp: 'Logins by app', + topUsers: 'Most active users', + noAppLogins: 'No app logins in this period yet.', + noUserActivity: 'No activity in this period yet.', + recentActivity: 'Recent activity', + hide: 'Hide', + viewAll: 'View all', + colUser: 'User', + colApp: 'App', + colType: 'Type', + colIp: 'IP', + colWhen: 'When', + serviceNoUser: '(service, no user)', + deletedApp: '(deleted app)', + recentCount: '{count} recent events — click "View all" for the full table.', + chartAriaLabel: 'Logins by day, stacked by type', + tooltipAppLabel: 'Apps (OAuth):', + tooltipSessionLabel: 'Admin panel:', + barAriaLabel: '{date}: {oauth} app logins, {session} to admin panel', + }, +}; + +export default en; diff --git a/resources/js/i18n/es.ts b/resources/js/i18n/es.ts new file mode 100644 index 0000000..ac69970 --- /dev/null +++ b/resources/js/i18n/es.ts @@ -0,0 +1,208 @@ +const es = { + nav: { + dashboard: 'Dashboard', + clients: 'Clientes OAuth', + tokens: 'Personal Access Tokens', + authorizedApps: 'Apps autorizadas', + users: 'Usuarios', + analytics: 'Analíticas', + apiDocs: 'API Docs', + logout: 'Salir', + }, + common: { + loading: 'Cargando…', + creating: 'Creando…', + create: 'Crear', + save: 'Guardar', + cancel: 'Cancelar', + close: 'Cerrar', + edit: 'Editar', + delete: 'Borrar', + none: '(ninguno)', + }, + login: { + title: 'Ingresar', + subtitle: 'Servidor OAuth2 — panel de administración.', + email: 'Email', + password: 'Contraseña', + submit: 'Ingresar', + submitting: 'Ingresando…', + noAccount: '¿No tenés cuenta?', + signUp: 'Registrate', + invalidCredentials: 'Credenciales inválidas.', + }, + register: { + title: 'Crear cuenta', + name: 'Nombre', + email: 'Email', + password: 'Contraseña', + passwordConfirm: 'Confirmar contraseña', + submit: 'Crear cuenta', + submitFirstUser: 'Crear cuenta de administrador', + submitting: 'Creando…', + haveAccount: '¿Ya tenés cuenta?', + signIn: 'Ingresá', + firstUserBanner: 'Todavía no hay ningún usuario. Esta cuenta va a quedar como administradora, con acceso inmediato (sin aprobación).', + pendingTitle: 'Cuenta creada', + pendingBody: 'Un administrador tiene que aprobar tu cuenta antes de que puedas ingresar. Te va a poder avisar por fuera de la app cuando esté lista.', + backToLogin: 'Volver a ingresar', + }, + dashboard: { + greeting: 'Hola, {name}', + subtitle: 'Este es el panel de administración del servidor OAuth2.', + clientsTitle: 'Clientes OAuth', + clientsDesc: 'Registrá apps (Authorization Code+PKCE, Client Credentials o Password Grant).', + tokensTitle: 'Personal Access Tokens', + tokensDesc: 'Generá tokens de acceso directo a la API, como API keys.', + appsTitle: 'Apps autorizadas', + appsDesc: 'Apps de terceros a las que le diste acceso a tu cuenta.', + usersTitle: 'Usuarios', + usersDesc: 'Alta, edición de rol/estado y baja de cuentas (solo admins).', + analyticsTitle: 'Analíticas', + analyticsDesc: 'Sesiones activas, logins por app y usuarios más activos (solo admins).', + apiTitle: 'API REST', + apiDesc: 'Este servidor expone el recurso {resource} (scopes {read}/{write}) para que aplicaciones de terceros gestionen usuarios vía OAuth2. Requiere un access token de Passport. Documentación interactiva en {link}.', + }, + clients: { + title: 'Clientes OAuth', + subtitle: 'Apps registradas que pueden pedir tokens a este servidor.', + secretWarning: 'Guardá este client secret ahora, no se puede volver a ver:', + clientIdLabel: 'client_id:', + clientSecretLabel: 'client_secret:', + newClient: 'Nuevo cliente', + name: 'Nombre', + grantType: 'Grant type', + grantAuthCode: 'Authorization Code + PKCE', + grantClientCredentials: 'Client Credentials', + grantPassword: 'Password Grant', + redirectUris: 'Redirect URIs (separadas por coma)', + confidentialLabel: 'Cliente confidencial (con secret). Desmarcá para un cliente público que solo use PKCE.', + create: 'Crear cliente', + empty: 'Todavía no creaste ningún cliente.', + revoke: 'Revocar', + revokeConfirm: '¿Revocar el cliente "{name}"? Sus tokens dejarán de funcionar.', + clientIdShort: 'client_id', + redirectLabel: 'redirect', + }, + tokens: { + title: 'Personal Access Tokens', + subtitle: 'Tokens de acceso directo a la API, sin pasar por el flujo de authorize (tipo API key).', + tokenWarning: 'Guardá este token ahora, no se puede volver a ver:', + newToken: 'Nuevo token', + name: 'Nombre', + namePlaceholder: 'Mi script', + scopes: 'Scopes', + create: 'Crear token', + empty: 'Todavía no creaste ningún token.', + revoke: 'Revocar', + revokeConfirm: '¿Revocar el token "{name}"?', + scopesLabel: 'scopes', + }, + authorizedApps: { + title: 'Apps autorizadas', + subtitle: 'Aplicaciones de terceros a las que le diste acceso a tu cuenta.', + empty: 'No autorizaste ninguna app todavía.', + revoke: 'Revocar acceso', + revokeConfirm: '¿Revocar el acceso de "{name}"?', + scopesLabel: 'scopes', + }, + authorize: { + title: 'Autorizar aplicación', + wantsAccess: '{client} quiere acceder a tu cuenta ({email}) con los siguientes permisos:', + noScopes: 'No solicita permisos adicionales (acceso básico).', + approve: 'Autorizar', + cancel: 'Cancelar', + notFoundTitle: 'Solicitud no encontrada', + notFoundBody: 'Esta pantalla solo funciona cuando llegás acá redirigido por una aplicación cliente vía {path}. Volvé a intentar desde esa app.', + goDashboard: 'Ir al dashboard', + }, + adminUsers: { + title: 'Usuarios', + subtitle: 'Alta, edición de rol/estado y baja de cuentas.', + newUser: 'Nuevo usuario', + name: 'Nombre', + email: 'Email', + password: 'Contraseña', + role: 'Rol', + roleUser: 'Usuario', + roleAdmin: 'Admin', + create: 'Crear usuario', + colName: 'Nombre', + colEmail: 'Email', + colRole: 'Rol', + colStatus: 'Estado', + active: 'Activo', + inactive: 'Desactivado', + noRole: '(sin rol)', + edit: 'Editar', + delete: 'Borrar', + deleteConfirm: '¿Borrar al usuario "{name}"? Va a la papelera y se puede restaurar.', + deleteFailed: 'No se pudo borrar el usuario.', + newPassword: 'Nueva contraseña', + noChange: '(sin cambios)', + save: 'Guardar', + cancel: 'Cancelar', + appRolesTitle: 'Roles por app', + appRolesDesc: 'El rol global de arriba manda en este panel. Acá se define qué rol tiene {name} dentro de cada app cuando esa app pide su usuario por API.', + noAppRoles: 'Sin roles asignados en ninguna app todavía.', + chooseApp: 'Elegir app…', + assign: 'Asignar', + assignFailed: 'No se pudo asignar el rol.', + trashTitle: 'Papelera', + viewTrash: 'Ver papelera', + viewActive: 'Ver activos', + trashEmpty: 'No hay usuarios borrados.', + deletedAt: 'Borrado el {date}', + restore: 'Restaurar', + restoreFailed: 'No se pudo restaurar el usuario.', + forceDelete: 'Borrar definitivamente', + forceDeleteConfirm: '¿Borrar definitivamente a "{name}"? Esta acción no se puede deshacer.', + forceDeleteFailed: 'No se pudo borrar definitivamente.', + pendingTitle: 'Pendientes', + viewPending: 'Pendientes', + pendingEmpty: 'No hay registros pendientes de aprobación.', + registeredAt: 'Se registró el {date}', + approve: 'Aprobar', + approveFailed: 'No se pudo aprobar el usuario.', + reject: 'Rechazar', + rejectConfirm: '¿Rechazar el registro de "{name}"? Va a la papelera y se puede restaurar después.', + rejectFailed: 'No se pudo rechazar el registro.', + }, + analytics: { + title: 'Analíticas', + subtitle: 'Sesiones, logins por app y actividad de usuarios.', + last7: 'Últimos 7 días', + last30: 'Últimos 30 días', + last90: 'Últimos 90 días', + totalUsers: 'Usuarios totales', + activeSessions: 'Sesiones activas', + activeSessionsHint: 'tokens OAuth vigentes', + loginsToday: 'Logins hoy', + logins7d: 'Logins últimos 7 días', + loginsByDay: 'Logins por día', + appsOAuth: 'Apps (OAuth)', + panelAdmin: 'Panel admin', + loginsByApp: 'Logins por app', + topUsers: 'Usuarios más activos', + noAppLogins: 'Todavía no hay logins de apps en este período.', + noUserActivity: 'Todavía no hay actividad en este período.', + recentActivity: 'Actividad reciente', + hide: 'Ocultar', + viewAll: 'Ver todo', + colUser: 'Usuario', + colApp: 'App', + colType: 'Tipo', + colIp: 'IP', + colWhen: 'Cuándo', + serviceNoUser: '(servicio, sin usuario)', + deletedApp: '(app borrada)', + recentCount: '{count} eventos recientes — click en "Ver todo" para la tabla completa.', + chartAriaLabel: 'Logins por día, apilado por tipo', + tooltipAppLabel: 'Apps (OAuth):', + tooltipSessionLabel: 'Panel admin:', + barAriaLabel: '{date}: {oauth} logins de apps, {session} al panel admin', + }, +}; + +export default es; +export type MessageSchema = typeof es; diff --git a/resources/js/i18n/index.ts b/resources/js/i18n/index.ts new file mode 100644 index 0000000..f2ec631 --- /dev/null +++ b/resources/js/i18n/index.ts @@ -0,0 +1,20 @@ +import { createI18n } from 'vue-i18n'; +import ca from './ca'; +import en from './en'; +import es, { type MessageSchema } from './es'; + +const SUPPORTED = ['es', 'en', 'ca'] as const; +type Locale = (typeof SUPPORTED)[number]; + +function isSupported(locale: string): locale is Locale { + return (SUPPORTED as readonly string[]).includes(locale); +} + +const i18n = createI18n<[MessageSchema], Locale>({ + legacy: false, + locale: isSupported(window.__LOCALE__) ? window.__LOCALE__ : 'es', + fallbackLocale: 'es', + messages: { en, es, ca }, +}); + +export default i18n; diff --git a/resources/js/pages/Authorize.vue b/resources/js/pages/Authorize.vue new file mode 100644 index 0000000..fb14093 --- /dev/null +++ b/resources/js/pages/Authorize.vue @@ -0,0 +1,70 @@ + + + diff --git a/resources/js/pages/AuthorizedApps.vue b/resources/js/pages/AuthorizedApps.vue new file mode 100644 index 0000000..fd75061 --- /dev/null +++ b/resources/js/pages/AuthorizedApps.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/resources/js/pages/Clients.vue b/resources/js/pages/Clients.vue new file mode 100644 index 0000000..addfec7 --- /dev/null +++ b/resources/js/pages/Clients.vue @@ -0,0 +1,184 @@ + + + + + diff --git a/resources/js/pages/Dashboard.vue b/resources/js/pages/Dashboard.vue new file mode 100644 index 0000000..3341e4b --- /dev/null +++ b/resources/js/pages/Dashboard.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/resources/js/pages/Login.vue b/resources/js/pages/Login.vue new file mode 100644 index 0000000..0a35d46 --- /dev/null +++ b/resources/js/pages/Login.vue @@ -0,0 +1,69 @@ + + + diff --git a/resources/js/pages/Register.vue b/resources/js/pages/Register.vue new file mode 100644 index 0000000..9bc153a --- /dev/null +++ b/resources/js/pages/Register.vue @@ -0,0 +1,103 @@ + + + diff --git a/resources/js/pages/Tokens.vue b/resources/js/pages/Tokens.vue new file mode 100644 index 0000000..0a2a177 --- /dev/null +++ b/resources/js/pages/Tokens.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/resources/js/pages/admin/Analytics.vue b/resources/js/pages/admin/Analytics.vue new file mode 100644 index 0000000..1189dff --- /dev/null +++ b/resources/js/pages/admin/Analytics.vue @@ -0,0 +1,179 @@ + + + + + diff --git a/resources/js/pages/admin/Users.vue b/resources/js/pages/admin/Users.vue new file mode 100644 index 0000000..881c654 --- /dev/null +++ b/resources/js/pages/admin/Users.vue @@ -0,0 +1,567 @@ + + + + + diff --git a/resources/js/router/index.ts b/resources/js/router/index.ts new file mode 100644 index 0000000..6a05586 --- /dev/null +++ b/resources/js/router/index.ts @@ -0,0 +1,96 @@ +import { createRouter, createWebHistory } from 'vue-router'; +import { useAuthStore } from '../stores/auth'; + +declare module 'vue-router' { + interface RouteMeta { + requiresAuth?: boolean; + requiresAdmin?: boolean; + guestOnly?: boolean; + } +} + +const routes = [ + { + path: '/', + name: 'dashboard', + component: () => import('../pages/Dashboard.vue'), + meta: { requiresAuth: true }, + }, + { + path: '/login', + name: 'login', + component: () => import('../pages/Login.vue'), + meta: { guestOnly: true }, + }, + { + path: '/register', + name: 'register', + component: () => import('../pages/Register.vue'), + meta: { guestOnly: true }, + }, + { + path: '/clients', + name: 'clients', + component: () => import('../pages/Clients.vue'), + meta: { requiresAuth: true }, + }, + { + path: '/tokens', + name: 'tokens', + component: () => import('../pages/Tokens.vue'), + meta: { requiresAuth: true }, + }, + { + path: '/authorized-apps', + name: 'authorized-apps', + component: () => import('../pages/AuthorizedApps.vue'), + meta: { requiresAuth: true }, + }, + { + path: '/oauth/authorize', + name: 'oauth-authorize', + component: () => import('../pages/Authorize.vue'), + meta: { requiresAuth: true }, + }, + { + path: '/admin/users', + name: 'admin-users', + component: () => import('../pages/admin/Users.vue'), + meta: { requiresAuth: true, requiresAdmin: true }, + }, + { + path: '/admin/analytics', + name: 'admin-analytics', + component: () => import('../pages/admin/Analytics.vue'), + meta: { requiresAuth: true, requiresAdmin: true }, + }, +]; + +const router = createRouter({ + history: createWebHistory(), + routes, +}); + +router.beforeEach(async (to) => { + const auth = useAuthStore(); + + if (!auth.checked) { + await auth.fetchUser(); + } + + if (to.meta.requiresAuth && !auth.isAuthenticated) { + return { name: 'login', query: { redirect: to.fullPath } }; + } + + if (to.meta.requiresAdmin && !auth.isAdmin) { + return { name: 'dashboard' }; + } + + if (to.meta.guestOnly && auth.isAuthenticated) { + return { name: 'dashboard' }; + } + + return true; +}); + +export default router; diff --git a/resources/js/stores/auth.ts b/resources/js/stores/auth.ts new file mode 100644 index 0000000..82abf70 --- /dev/null +++ b/resources/js/stores/auth.ts @@ -0,0 +1,91 @@ +import { defineStore } from 'pinia'; +import axios from 'axios'; +import type { User } from '../types/models'; + +interface AuthState { + user: User | null; + checked: boolean; +} + +interface Credentials { + email: string; + password: string; + remember?: boolean; +} + +interface RegisterPayload { + name: string; + email: string; + password: string; + password_confirmation: string; +} + +interface RegisterResult { + bootstrap: boolean; + message?: string; +} + +export const useAuthStore = defineStore('auth', { + state: (): AuthState => ({ + user: null, + checked: false, + }), + + getters: { + isAuthenticated: (state): boolean => state.user !== null, + isAdmin: (state): boolean => Boolean(state.user?.roles?.includes('admin')), + }, + + actions: { + async fetchUser(): Promise { + try { + const { data } = await axios.get<{ user: User }>('/web-api/me'); + this.user = data.user; + } catch { + this.user = null; + } finally { + this.checked = true; + } + }, + + async login(payload: Credentials): Promise { + const { data } = await axios.post<{ user: User; intended: string | null }>('/web-api/login', payload); + this.user = data.user; + this.checked = true; + + return data.intended ?? null; + }, + + // Si todavía no hay ningún usuario en el sistema, el registro loguea de + // una como admin (bootstrap). Si ya hay usuarios, la cuenta queda + // pendiente hasta que un admin la aprueba (ver AuthController::register). + async register(payload: RegisterPayload): Promise { + const { data } = await axios.post<{ + bootstrap?: true; + user?: User; + pending?: true; + message?: string; + }>('/web-api/register', payload); + + if (data.bootstrap && data.user) { + this.user = data.user; + this.checked = true; + + return { bootstrap: true }; + } + + return { bootstrap: false, message: data.message }; + }, + + async setupIsEmpty(): Promise { + const { data } = await axios.get<{ empty: boolean }>('/web-api/setup-status'); + + return data.empty; + }, + + async logout(): Promise { + await axios.post('/web-api/logout'); + this.user = null; + }, + }, +}); diff --git a/resources/js/types/models.ts b/resources/js/types/models.ts new file mode 100644 index 0000000..5c2e55e --- /dev/null +++ b/resources/js/types/models.ts @@ -0,0 +1,90 @@ +export interface User { + id: number; + name: string; + email: string; + is_active: boolean; + roles: string[]; + permissions?: string[]; + created_at: string; + deleted_at?: string | null; + approved_at?: string | null; +} + +export interface OAuthClient { + id: string; + owner_id: number; + owner_type: string; + name: string; + secret: string | null; + provider: string | null; + redirect_uris: string[]; + grant_types: string[]; + revoked: boolean; + created_at: string; + updated_at: string; +} + +export interface PersonalAccessToken { + id: string; + name: string; + scopes: string[]; + created_at: string; + expires_at: string | null; +} + +export interface AuthorizedAppToken { + id: string; + user_id: number; + client_id: string; + name: string | null; + scopes: string[]; + revoked: boolean; + created_at: string; + updated_at: string; + expires_at: string | null; + client: { id: string; name: string }; +} + +export interface Scope { + id: string; + description: string; +} + +export interface OAuthApp { + id: string; + name: string; +} + +export interface AppRoleGroup { + client_id: string; + client_name: string; + roles: string[]; +} + +export interface AnalyticsSummary { + kpis: { + total_users: number; + active_sessions: number; + logins_today: number; + logins_7d: number; + }; + logins_by_day: Array<{ date: string; session: number; oauth: number }>; + logins_by_app: Array<{ client_name: string; count: number }>; + top_users: Array<{ name: string; email: string; count: number }>; + recent: Array<{ + user_name: string | null; + user_email: string | null; + client_name: string; + type: 'session' | 'oauth'; + ip_address: string | null; + created_at: string; + }>; +} + +export interface OAuthAuthorizePayload { + authToken: string; + csrfToken: string; + client: { id: string; name: string }; + scopes: Scope[]; + user: { name: string; email: string }; +} diff --git a/resources/js/vite-env.d.ts b/resources/js/vite-env.d.ts new file mode 100644 index 0000000..9fc7cd7 --- /dev/null +++ b/resources/js/vite-env.d.ts @@ -0,0 +1,13 @@ +/// + +import type { OAuthAuthorizePayload } from './types/models'; + +declare global { + interface Window { + __LOCALE__: 'es' | 'en' | 'ca'; + __SESSION_LOCALE__: string; + __OAUTH_AUTHORIZE__?: OAuthAuthorizePayload; + } +} + +export {}; diff --git a/resources/scss/abstracts/_mixins.scss b/resources/scss/abstracts/_mixins.scss new file mode 100644 index 0000000..4a9c829 --- /dev/null +++ b/resources/scss/abstracts/_mixins.scss @@ -0,0 +1,35 @@ +// Equivalente al prefijo `dark:` de Tailwind: el toggle de tema pone la clase +// `.dark` en (ver resources/js/composables/useTheme.ts), así que +// cualquier regla dentro de este mixin solo aplica bajo esa clase ancestro. +// Uso en los partials globales de components/ (no pasan por el compilador +// "scoped" de Vue, así que acá ".dark &" alcanza). Los dos componentes que +// SÍ usan + @if(config('l5-swagger.defaults.ui.display.dark_mode')) + + @endif + + + +
+ + + + + + diff --git a/routes/api.php b/routes/api.php new file mode 100644 index 0000000..5fdfa1e --- /dev/null +++ b/routes/api.php @@ -0,0 +1,44 @@ +middleware('auth:api'); + +// Directorio de usuarios (admin) — self-service para uno mismo, permiso users.* para el resto. +// "/users/trashed" tiene que registrarse ANTES de la apiResource: si no, Laravel +// intenta bindear "trashed" como {user} de la ruta show y nunca llega acá. +Route::get('/users/trashed', [UserController::class, 'trashed']); +Route::get('/users/pending', [UserController::class, 'pending']); +Route::post('/users/{id}/restore', [UserController::class, 'restore'])->whereNumber('id'); +Route::delete('/users/{id}/force', [UserController::class, 'forceDelete'])->whereNumber('id'); +Route::apiResource('users', UserController::class); +Route::post('/users/{user}/approve', [UserController::class, 'approve']); +Route::get('/users/{user}/app-roles', [UserController::class, 'appRoles']); +Route::post('/users/{user}/app-roles', [UserController::class, 'assignAppRole']); +Route::delete('/users/{user}/app-roles/{clientId}/{role}', [UserController::class, 'revokeAppRole']); + +// Apps registradas en el sistema (admin, para el selector de roles-por-app). +Route::get('/apps', [AppController::class, 'index']); + +// Analíticas de uso: sesiones, logins por app, actividad reciente. +Route::get('/analytics/summary', [AnalyticsController::class, 'summary']); + +// Cuenta propia: clientes OAuth, personal access tokens y apps autorizadas. +// Todo esto es lo mismo que ofrece la SPA Vue por sesión (routes/web.php), pero +// vía Bearer token — para que cualquier cliente (Angular, Flutter, etc.) pueda +// construir su propio panel sin depender de la sesión de Laravel. +Route::apiResource('clients', ClientController::class)->except(['show'])->names('api.clients'); + +Route::get('/personal-access-tokens', [PersonalAccessTokenController::class, 'index']); +Route::post('/personal-access-tokens', [PersonalAccessTokenController::class, 'store']); +Route::delete('/personal-access-tokens/{tokenId}', [PersonalAccessTokenController::class, 'destroy']); + +Route::get('/authorized-apps', [AuthorizedAppController::class, 'index']); +Route::delete('/authorized-apps/{tokenId}', [AuthorizedAppController::class, 'destroy']); diff --git a/routes/console.php b/routes/console.php new file mode 100644 index 0000000..3c9adf1 --- /dev/null +++ b/routes/console.php @@ -0,0 +1,8 @@ +comment(Inspiring::quote()); +})->purpose('Display an inspiring quote'); diff --git a/routes/web.php b/routes/web.php new file mode 100644 index 0000000..42e7fd4 --- /dev/null +++ b/routes/web.php @@ -0,0 +1,74 @@ +validate(['locale' => 'required|in:es,en,ca']); + session(['locale' => request('locale')]); + + return response()->noContent(); +}); + +// Endpoints de sesión (guard "web") que consume la SPA para loguearse y +// administrar el servidor OAuth2 (clientes, personal access tokens, apps autorizadas). +Route::prefix('web-api')->group(function () { + Route::get('/setup-status', [AuthController::class, 'setupStatus']); + Route::post('/register', [AuthController::class, 'register']); + Route::post('/login', [AuthController::class, 'login']); + + Route::middleware('auth')->group(function () { + Route::post('/logout', [AuthController::class, 'logout']); + Route::get('/me', [AuthController::class, 'me']); + Route::get('/scopes', fn () => response()->json(Passport::scopes())); + + Route::apiResource('clients', ClientController::class)->except(['show']); + + Route::get('/personal-access-tokens', [PersonalAccessTokenController::class, 'index']); + Route::post('/personal-access-tokens', [PersonalAccessTokenController::class, 'store']); + Route::delete('/personal-access-tokens/{tokenId}', [PersonalAccessTokenController::class, 'destroy']); + + Route::get('/authorized-apps', [AuthorizedAppController::class, 'index']); + Route::delete('/authorized-apps/{tokenId}', [AuthorizedAppController::class, 'destroy']); + + // Panel de administración de usuarios (solo para roles/permisos con "users.*"). + Route::prefix('admin')->group(function () { + Route::get('/users', [UserController::class, 'index'])->middleware('can:users.view'); + Route::get('/users/trashed', [UserController::class, 'trashed'])->middleware('can:users.view'); + Route::get('/users/pending', [UserController::class, 'pending'])->middleware('can:users.view'); + Route::post('/users', [UserController::class, 'store'])->middleware('can:users.create'); + Route::put('/users/{user}', [UserController::class, 'update'])->middleware('can:users.update'); + Route::delete('/users/{user}', [UserController::class, 'destroy'])->middleware('can:users.delete'); + Route::post('/users/{id}/restore', [UserController::class, 'restore'])->middleware('can:users.update')->whereNumber('id'); + Route::delete('/users/{id}/force', [UserController::class, 'forceDelete'])->middleware('can:users.delete')->whereNumber('id'); + Route::post('/users/{user}/approve', [UserController::class, 'approve'])->middleware('can:users.update'); + + // Roles por app (multi-tenant): a qué app y con qué rol puede entrar cada usuario. + Route::get('/apps', [AppController::class, 'index'])->middleware('can:users.view'); + Route::get('/users/{user}/app-roles', [UserController::class, 'appRoles'])->middleware('can:users.view'); + Route::post('/users/{user}/app-roles', [UserController::class, 'assignAppRole'])->middleware('can:users.update'); + Route::delete('/users/{user}/app-roles/{clientId}/{role}', [UserController::class, 'revokeAppRole'])->middleware('can:users.update'); + + // Analíticas de uso: sesiones, logins por app, actividad reciente. + Route::get('/analytics/summary', [AnalyticsController::class, 'summary'])->middleware('can:users.view'); + }); + }); +}); + +// Laravel/Passport redirige a los invitados no autenticados a route('login'), así que +// necesita una ruta fija (no el wildcard de abajo, que exige un parámetro). +Route::view('/login', 'app')->name('login'); + +// Todo lo demás (home, register, dashboard, /oauth/authorize cuando hay sesión, etc.) +// lo resuelve el shell de la SPA Vue (Vue Router decide qué pantalla mostrar). +Route::view('/', 'app'); +Route::get('/{any}', fn () => view('app')) + ->where('any', '^(?!web-api|api|oauth|storage|login).*$'); diff --git a/storage/api-docs/api-docs.json b/storage/api-docs/api-docs.json new file mode 100644 index 0000000..f6b30fb --- /dev/null +++ b/storage/api-docs/api-docs.json @@ -0,0 +1,1133 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "OAuth2 Identity Server API", + "description": "Servidor de identidad/OAuth2 (Laravel Passport) para que aplicaciones de terceros se autentiquen y gestionen usuarios. Pensada para ser consumida por aplicaciones cliente de terceros (Angular, Flutter, NestJS, etc.) que quieran construir su propio panel sin depender de la sesión de Laravel.", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/api", + "description": "API base URL" + } + ], + "paths": { + "/analytics/summary": { + "get": { + "tags": [ + "Analytics" + ], + "summary": "Métricas de uso: logins por día, por app, usuarios más activos y actividad reciente", + "description": "Requiere el permiso users.view (o un token client_credentials, que actúa como servicio de confianza).", + "operationId": "b34ca6a4b161498d5b34ed3054ddc39d", + "parameters": [ + { + "name": "days", + "in": "query", + "description": "Ventana en días (7-90, default 30)", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Resumen de analíticas" + }, + "403": { + "description": "Sin permiso users.view" + } + }, + "security": [ + { + "oauth2": [ + "users:read" + ] + } + ] + } + }, + "/apps": { + "get": { + "tags": [ + "Users" + ], + "summary": "Listar todas las apps (clientes OAuth) registradas en el sistema", + "description": "Requiere el permiso users.view (o un token client_credentials, que actúa como servicio de confianza). Se usa para armar el selector de \"roles por app\".", + "operationId": "5a879a1d739e5b67999e22de46d3223b", + "responses": { + "200": { + "description": "Listado de apps" + }, + "403": { + "description": "Sin permiso users.view" + } + }, + "security": [ + { + "oauth2": [ + "users:read" + ] + } + ] + } + }, + "/authorized-apps": { + "get": { + "tags": [ + "Account" + ], + "summary": "Listar apps de terceros a las que le diste acceso a tu cuenta", + "operationId": "52cd9fb5a0e5a127774bdd7fa8456851", + "responses": { + "200": { + "description": "Listado de tokens/apps autorizadas" + } + }, + "security": [ + { + "oauth2": [ + "account:read" + ] + } + ] + } + }, + "/authorized-apps/{tokenId}": { + "delete": { + "tags": [ + "Account" + ], + "summary": "Revocar el acceso de una app de terceros", + "operationId": "59b2351fe30ac2a4c717926d094d08c9", + "parameters": [ + { + "name": "tokenId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Acceso revocado" + }, + "404": { + "description": "No existe o no es tuyo" + } + }, + "security": [ + { + "oauth2": [ + "account:write" + ] + } + ] + } + }, + "/clients": { + "get": { + "tags": [ + "Account" + ], + "summary": "Listar tus clientes OAuth (apps que registraste)", + "operationId": "6536f8d22d25758c3e14dbbe8e228af0", + "responses": { + "200": { + "description": "Listado de clientes", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Client" + } + } + } + } + }, + "422": { + "description": "El token no tiene un usuario asociado (ej. client_credentials)" + } + }, + "security": [ + { + "oauth2": [ + "account:read" + ] + } + ] + }, + "post": { + "tags": [ + "Account" + ], + "summary": "Registrar un nuevo cliente OAuth (tu app)", + "operationId": "74a35b85418e09c2489915865dd22445", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "required": [ + "name", + "grant_type" + ], + "properties": { + "name": { + "type": "string" + }, + "grant_type": { + "type": "string", + "enum": [ + "authorization_code", + "client_credentials", + "password" + ] + }, + "redirect_uris": { + "type": "array", + "items": { + "type": "string" + } + }, + "confidential": { + "type": "boolean", + "default": true + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Cliente creado (incluye plain_secret una sola vez)" + }, + "422": { + "description": "Datos inválidos o token sin usuario asociado" + } + }, + "security": [ + { + "oauth2": [ + "account:write" + ] + } + ] + } + }, + "/clients/{client}": { + "put": { + "tags": [ + "Account" + ], + "summary": "Editar uno de tus clientes OAuth", + "operationId": "01a0e98b651c8cda71034d93d40b8b27", + "parameters": [ + { + "name": "client", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Cliente actualizado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Client" + } + } + } + }, + "404": { + "description": "No es tuyo o no existe" + } + }, + "security": [ + { + "oauth2": [ + "account:write" + ] + } + ] + }, + "delete": { + "tags": [ + "Account" + ], + "summary": "Revocar uno de tus clientes OAuth (y sus tokens)", + "operationId": "ed21785e71ab67958cf6216b6cf263a3", + "parameters": [ + { + "name": "client", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Revocado" + }, + "404": { + "description": "No es tuyo o no existe" + } + }, + "security": [ + { + "oauth2": [ + "account:write" + ] + } + ] + } + }, + "/personal-access-tokens": { + "get": { + "tags": [ + "Account" + ], + "summary": "Listar tus Personal Access Tokens", + "operationId": "b94780059af9e6c94456302b3142b3cb", + "responses": { + "200": { + "description": "Listado de tokens" + } + }, + "security": [ + { + "oauth2": [ + "account:read" + ] + } + ] + }, + "post": { + "tags": [ + "Account" + ], + "summary": "Crear un Personal Access Token (tipo API key)", + "operationId": "6e653bdc7fa6bdd2768842f92d575ae7", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "scopes": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Token creado (access_token solo se muestra esta vez)" + } + }, + "security": [ + { + "oauth2": [ + "account:write" + ] + } + ] + } + }, + "/personal-access-tokens/{tokenId}": { + "delete": { + "tags": [ + "Account" + ], + "summary": "Revocar un Personal Access Token", + "operationId": "d72271ccb6b2630a6d33c20516229723", + "parameters": [ + { + "name": "tokenId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Revocado" + }, + "404": { + "description": "No existe o no es tuyo" + } + }, + "security": [ + { + "oauth2": [ + "account:write" + ] + } + ] + } + }, + "/user": { + "get": { + "tags": [ + "Users" + ], + "summary": "Usuario del token actual (whoami) + sus roles global y en la app que llama", + "operationId": "853204b4b2f8a255a3721aaca3407e72", + "responses": { + "200": { + "description": "Usuario autenticado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "401": { + "description": "Token inválido o sin usuario asociado (ej. client_credentials)" + } + }, + "security": [ + { + "oauth2": [] + } + ] + } + }, + "/users": { + "get": { + "tags": [ + "Users" + ], + "summary": "Listar usuarios", + "description": "Incluye usuarios pendientes de aprobación del panel admin (ver campo approved_at) — esa aprobación solo gobierna el acceso al panel de sesión de esta app, no la consulta de usuarios vía API.", + "operationId": "7208c29037df2b8bb02499ba39ed8a78", + "responses": { + "200": { + "description": "Listado de usuarios", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + }, + "403": { + "description": "Sin permiso users.view" + } + }, + "security": [ + { + "oauth2": [ + "users:read" + ] + } + ] + }, + "post": { + "tags": [ + "Users" + ], + "summary": "Crear un usuario", + "operationId": "262eb5ca1fdea354023c0a8870442c1e", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "required": [ + "name", + "email", + "password" + ], + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "password": { + "type": "string", + "format": "password" + }, + "role": { + "type": "string", + "example": "user" + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Usuario creado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "403": { + "description": "Sin permiso users.create" + } + }, + "security": [ + { + "oauth2": [ + "users:write" + ] + } + ] + } + }, + "/users/{user}": { + "get": { + "tags": [ + "Users" + ], + "summary": "Ver un usuario (el propio siempre permitido; otros requieren users.view)", + "operationId": "a8a7a1f979a2abb839b4b320238106fc", + "parameters": [ + { + "name": "user", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Datos del usuario", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "403": { + "description": "Sin permiso users.view" + } + }, + "security": [ + { + "oauth2": [ + "users:read" + ] + } + ] + }, + "put": { + "tags": [ + "Users" + ], + "summary": "Editar un usuario (el propio siempre permitido; rol/estado y otros usuarios requieren users.update)", + "operationId": "4ac3faef1359369686fdd433daad2bee", + "parameters": [ + { + "name": "user", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "password": { + "type": "string", + "format": "password" + }, + "is_active": { + "type": "boolean" + }, + "role": { + "type": "string" + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Usuario actualizado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "403": { + "description": "Sin permiso users.update" + } + }, + "security": [ + { + "oauth2": [ + "users:write" + ] + } + ] + }, + "delete": { + "tags": [ + "Users" + ], + "summary": "Borrar un usuario (requiere users.delete; no se puede autoeliminar)", + "operationId": "371ba09bb6d4a95792dd93040f8812b3", + "parameters": [ + { + "name": "user", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "Usuario borrado" + }, + "403": { + "description": "Sin permiso users.delete" + }, + "422": { + "description": "No se puede autoeliminar ni borrar al último admin" + } + }, + "security": [ + { + "oauth2": [ + "users:write" + ] + } + ] + } + }, + "/users/trashed": { + "get": { + "tags": [ + "Users" + ], + "summary": "Listar usuarios borrados (papelera)", + "operationId": "ac10f52342c5e5c0d77d46764b6e0637", + "responses": { + "200": { + "description": "Listado de usuarios en la papelera", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + }, + "403": { + "description": "Sin permiso users.view" + } + }, + "security": [ + { + "oauth2": [ + "users:read" + ] + } + ] + } + }, + "/users/{id}/restore": { + "post": { + "tags": [ + "Users" + ], + "summary": "Restaurar un usuario borrado", + "operationId": "0ec16b4b1d92c2d61463d74d9c045701", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Usuario restaurado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "403": { + "description": "Sin permiso users.update" + } + }, + "security": [ + { + "oauth2": [ + "users:write" + ] + } + ] + } + }, + "/users/{id}/force": { + "delete": { + "tags": [ + "Users" + ], + "summary": "Borrar un usuario definitivamente (tiene que estar en la papelera)", + "operationId": "093b171b76c7e960668a7c9adf16e9ef", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "204": { + "description": "Borrado definitivamente" + }, + "403": { + "description": "Sin permiso users.delete" + } + }, + "security": [ + { + "oauth2": [ + "users:write" + ] + } + ] + } + }, + "/users/pending": { + "get": { + "tags": [ + "Users" + ], + "summary": "Listar usuarios autorregistrados que todavía no aprobó ningún admin", + "operationId": "373548f0068d0e9154443cb895254faf", + "responses": { + "200": { + "description": "Listado de usuarios pendientes", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + }, + "403": { + "description": "Sin permiso users.view" + } + }, + "security": [ + { + "oauth2": [ + "users:read" + ] + } + ] + } + }, + "/users/{user}/approve": { + "post": { + "tags": [ + "Users" + ], + "summary": "Aprobar un registro pendiente", + "operationId": "c9f59dde1d9a997c3f33c22af5ffc0f0", + "parameters": [ + { + "name": "user", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Usuario aprobado", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "403": { + "description": "Sin permiso users.update" + } + }, + "security": [ + { + "oauth2": [ + "users:write" + ] + } + ] + } + }, + "/users/{user}/app-roles": { + "get": { + "tags": [ + "Users" + ], + "summary": "Roles que el usuario tiene en cada app (el propio siempre permitido; otros requieren users.view)", + "operationId": "9f0336429727034d7b79d1dd1ec210e2", + "parameters": [ + { + "name": "user", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Roles agrupados por app" + } + }, + "security": [ + { + "oauth2": [ + "users:read" + ] + } + ] + }, + "post": { + "tags": [ + "Users" + ], + "summary": "Asignar un rol a un usuario dentro de una app (requiere users.update; sin bypass por self)", + "operationId": "108e24d381e03dcbde787f187bff167f", + "parameters": [ + { + "name": "user", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "required": [ + "client_id", + "role" + ], + "properties": { + "client_id": { + "type": "string", + "format": "uuid" + }, + "role": { + "type": "string", + "example": "admin" + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Rol asignado" + }, + "403": { + "description": "Sin permiso users.update" + } + }, + "security": [ + { + "oauth2": [ + "users:write" + ] + } + ] + } + }, + "/users/{user}/app-roles/{clientId}/{role}": { + "delete": { + "tags": [ + "Users" + ], + "summary": "Quitarle a un usuario un rol dentro de una app (requiere users.update)", + "operationId": "60321b766e2a52bc435d7c93ed35a6dc", + "parameters": [ + { + "name": "user", + "in": "path", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "clientId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "role", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Rol quitado" + }, + "403": { + "description": "Sin permiso users.update" + } + }, + "security": [ + { + "oauth2": [ + "users:write" + ] + } + ] + } + } + }, + "components": { + "schemas": { + "Client": { + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "name": { + "type": "string", + "example": "Mi app Angular" + }, + "grant_types": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "authorization_code", + "refresh_token" + ] + }, + "redirect_uris": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "https://mi-app.com/callback" + ] + }, + "revoked": { + "type": "boolean" + } + }, + "type": "object" + }, + "User": { + "properties": { + "id": { + "type": "integer", + "example": 1 + }, + "name": { + "type": "string", + "example": "Ada Lovelace" + }, + "email": { + "type": "string", + "format": "email", + "example": "ada@example.com" + }, + "is_active": { + "type": "boolean", + "example": true + }, + "roles": { + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "user" + ] + }, + "app_roles": { + "description": "Roles del usuario específicos de la app que llama (el client_id del token usado).", + "type": "array", + "items": { + "type": "string" + }, + "example": [ + "editor" + ] + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "deleted_at": { + "description": "Solo presente para usuarios en la papelera (soft-deleted).", + "type": "string", + "format": "date-time", + "nullable": true + }, + "approved_at": { + "description": "Null = todavía no lo aprobó ningún admin (registro pendiente).", + "type": "string", + "format": "date-time", + "nullable": true + } + }, + "type": "object" + } + }, + "securitySchemes": { + "oauth2": { + "type": "oauth2", + "description": "Servidor de autorización OAuth2 (Laravel Passport). Soporta Authorization Code + PKCE, Client Credentials y Password Grant.", + "flows": { + "authorizationCode": { + "authorizationUrl": "/oauth/authorize", + "tokenUrl": "/oauth/token", + "refreshUrl": "/oauth/token", + "scopes": { + "users:read": "Leer usuarios", + "users:write": "Crear, editar y borrar usuarios", + "account:read": "Leer tus clientes OAuth, tokens y apps autorizadas", + "account:write": "Gestionar tus clientes OAuth, tokens y apps autorizadas" + } + }, + "clientCredentials": { + "tokenUrl": "/oauth/token", + "scopes": { + "users:read": "Leer usuarios", + "users:write": "Crear, editar y borrar usuarios", + "account:read": "Leer tus clientes OAuth, tokens y apps autorizadas", + "account:write": "Gestionar tus clientes OAuth, tokens y apps autorizadas" + } + }, + "password": { + "tokenUrl": "/oauth/token", + "scopes": { + "users:read": "Leer usuarios", + "users:write": "Crear, editar y borrar usuarios", + "account:read": "Leer tus clientes OAuth, tokens y apps autorizadas", + "account:write": "Gestionar tus clientes OAuth, tokens y apps autorizadas" + } + } + } + } + } + }, + "tags": [ + { + "name": "Analytics", + "description": "Analytics" + }, + { + "name": "Users", + "description": "Users" + }, + { + "name": "Account", + "description": "Account" + } + ] +} \ No newline at end of file diff --git a/storage/app/.gitignore b/storage/app/.gitignore new file mode 100644 index 0000000..fedb287 --- /dev/null +++ b/storage/app/.gitignore @@ -0,0 +1,4 @@ +* +!private/ +!public/ +!.gitignore diff --git a/storage/app/private/.gitignore b/storage/app/private/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/app/private/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/app/public/.gitignore b/storage/app/public/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/app/public/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/.gitignore b/storage/framework/.gitignore new file mode 100644 index 0000000..05c4471 --- /dev/null +++ b/storage/framework/.gitignore @@ -0,0 +1,9 @@ +compiled.php +config.php +down +events.scanned.php +maintenance.php +routes.php +routes.scanned.php +schedule-* +services.json diff --git a/storage/framework/cache/.gitignore b/storage/framework/cache/.gitignore new file mode 100644 index 0000000..01e4a6c --- /dev/null +++ b/storage/framework/cache/.gitignore @@ -0,0 +1,3 @@ +* +!data/ +!.gitignore diff --git a/storage/framework/cache/data/.gitignore b/storage/framework/cache/data/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/cache/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/sessions/.gitignore b/storage/framework/sessions/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/sessions/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/testing/.gitignore b/storage/framework/testing/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/testing/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/framework/views/.gitignore b/storage/framework/views/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/framework/views/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/logs/.gitignore b/storage/logs/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php new file mode 100644 index 0000000..8364a84 --- /dev/null +++ b/tests/Feature/ExampleTest.php @@ -0,0 +1,19 @@ +get('/'); + + $response->assertStatus(200); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..fe1ffc2 --- /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..911f220 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "noEmit": true, + "strict": true + }, + "include": ["resources/js/**/*.ts", "resources/js/**/*.vue", "resources/js/**/*.d.ts"], + "exclude": ["node_modules", "public", "vendor"] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..a9dd1d8 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "extends": "@vue/tsconfig/tsconfig.json", + "compilerOptions": { + "composite": true, + "types": ["node"], + "module": "ESNext", + "moduleResolution": "Bundler" + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..0405eee --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,24 @@ +import { defineConfig } from 'vite'; +import laravel from 'laravel-vite-plugin'; +import { bunny } from 'laravel-vite-plugin/fonts'; +import vue from '@vitejs/plugin-vue'; + +export default defineConfig({ + plugins: [ + laravel({ + input: ['resources/scss/app.scss', 'resources/js/app.ts'], + refresh: true, + fonts: [ + bunny('Instrument Sans', { + weights: [400, 500, 600], + }), + ], + }), + vue(), + ], + server: { + watch: { + ignored: ['**/storage/framework/views/**'], + }, + }, +});