commit 0280278978d2ab2870f9da601ef1b56738f71071 Author: Dolo Date: Sat Apr 4 18:01:50 2026 +0200 Initialer Laravel Commit für BetiX diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..a945ef7 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,32 @@ +{ + "permissions": { + "allow": [ + "Bash(xargs grep:*)", + "Bash(grep -r \"Error.*Handler\\\\|Exception.*Handler\" /c/Users/Dolo/Documents/Herd/casino/app --include=*.php)", + "Bash(grep -r \"Email.*Confirm\\\\|Withdraw.*2FA\\\\|cooldown\" /c/Users/Dolo/Documents/Herd/casino/app --include=*.php)", + "Bash(grep -r \"transition\\\\|animation\\\\|fade\" /c/Users/Dolo/Documents/Herd/casino/resources/js/components --include=*.vue)", + "Bash(grep -r \"glassmorphism\\\\|neon\\\\|glass\" /c/Users/Dolo/Documents/Herd/casino/resources --include=*.vue --include=*.css)", + "Bash(grep -r \"dark\\\\|light\\\\|theme\" /c/Users/Dolo/Documents/Herd/casino/resources/js --include=*.ts --include=*.vue)", + "Bash(grep -r \"2FA\\\\|Email.*Verif\" /c/Users/Dolo/Documents/Herd/casino/app/Http/Controllers --include=*.php)", + "Bash(grep -r withdraw /c/Users/Dolo/Documents/Herd/casino/app --include=*.php -i)", + "Bash(grep -r \"class.*Vip\\\\|function.*vip\" /c/Users/Dolo/Documents/Herd/casino/app --include=*.php)", + "Bash(grep -r \"slot\\\\|game\\\\|layout\" /c/Users/Dolo/Documents/Herd/casino/resources/js/components --include=*.vue)", + "Bash(grep -rn lucide C:UsersDoloDocumentsHerdcasinoresources --include=*.ts --include=*.js -l)", + "Bash(grep -n \"AdminLayout\" \"/c/Users/Dolo/Documents/Herd/casino/resources/js/pages/Admin/\"*.vue)", + "Bash(php artisan:*)", + "Bash(/c/Users/Dolo/AppData/Roaming/Herd/config/php/php.ini)", + "Bash(grep -rn \"from ''@/layouts/user/userlayout\" \"/c/Users/Dolo/Documents/Herd/casino/resources/js/pages/Admin/\")", + "Bash(grep -rn support /c/Users/Dolo/Documents/Herd/casino/routes/ --include=*.php)", + "Bash(find /c/Users/Dolo/Documents/Herd/casino/routes -name *.php -exec cat {})", + "Bash(2)", + "Bash(xargs cat:*)", + "Bash(/c/Users/Dolo/AppData/Local/herd-lite/bin/php.bat artisan:*)", + "Bash(/c/Users/Dolo/.config/herd/bin/php84/php.exe artisan:*)", + "Bash(find /c/Users/Dolo/Documents/Herd/casino -type d -name game* -o -name slot* -o -name pages)", + "Bash(grep -r \"originals/launch\\\\|launchOriginal\" /c/Users/Dolo/Documents/Herd/casino --include=*.php --include=*.ts --include=*.vue)", + "Bash(xargs wc:*)", + "Bash(\"/c/Users/Dolo/AppData/Local/Herd/resources/app.asar.unpacked/resources/win/bin/php/php\" artisan:*)", + "Bash(herd php:*)" + ] + } +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a186cd2 --- /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.yaml] +indent_size = 4 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2da6a03 --- /dev/null +++ b/.env.example @@ -0,0 +1,69 @@ +APP_NAME=Laravel +APP_ENV=production +APP_KEY= +APP_DEBUG=false +APP_URL=https://deine-domain.com + +APP_LOCALE=en +APP_FALLBACK_LOCALE=en +APP_FAKER_LOCALE=en_US + +APP_MAINTENANCE_DRIVER=file +# APP_MAINTENANCE_STORE=database + +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 + +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}" + +# --- NOWPayments LIVE Konfiguration --- +NOWPAYMENTS_ENV=live +NOWPAYMENTS_BASE_URL=https://api.nowpayments.io/v1 +NOWPAYMENTS_API_KEY=dein_live_api_key_hier +NOWPAYMENTS_PUBLIC_KEY=dein_oeffentlicher_api_key_hier +NOWPAYMENTS_IPN_SECRET=dein_ipn_secret_hier diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..4da48a8 --- /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 + +CHANGELOG.md export-ignore +README.md export-ignore +.github/workflows/browser-tests.yml export-ignore diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..619061f --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,49 @@ +name: linter + +on: + push: + branches: + - develop + - main + - master + - workos + pull_request: + branches: + - develop + - main + - master + - workos + +permissions: + contents: write + +jobs: + quality: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + + - name: Install Dependencies + run: | + composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist + npm install + + - name: Run Pint + run: composer lint + + - name: Format Frontend + run: npm run format + + - name: Lint Frontend + run: npm run lint + + # - name: Commit Changes + # uses: stefanzweifel/git-auto-commit-action@v7 + # with: + # commit_message: fix code style + # commit_options: '--no-verify' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..b2ded27 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,56 @@ +name: tests + +on: + push: + branches: + - develop + - main + - master + - workos + pull_request: + branches: + - develop + - main + - master + - workos + +jobs: + ci: + runs-on: ubuntu-latest + strategy: + matrix: + php-version: ['8.4', '8.5'] + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + tools: composer:v2 + coverage: xdebug + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install Node Dependencies + run: npm i + + - name: Install Dependencies + run: composer install --no-interaction --prefer-dist --optimize-autoloader + + - name: Copy Environment File + run: cp .env.example .env + + - name: Generate Application Key + run: php artisan key:generate + + - name: Build Assets + run: npm run build + + - name: Tests + run: ./vendor/bin/pest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5504403 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +/.phpunit.cache +/bootstrap/ssr +/node_modules +/public/build +/public/hot +/public/storage +/storage/*.key +/storage/pail +/resources/js/actions +/resources/js/routes +/resources/js/wayfinder +/vendor +.DS_Store +.env +.env.backup +.env.production +.phpactor.json +.phpunit.result.cache +Homestead.json +Homestead.yaml +npm-debug.log +yarn-error.log +/auth.json +/.fleet +/.idea +/.nova +/.vscode +/.zed diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..2a2592c --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +public-hoist-pattern[]=@inertiajs/core diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..6b929ae --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +resources/js/components/ui/* +resources/views/mail/* diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..92805fa --- /dev/null +++ b/.prettierrc @@ -0,0 +1,25 @@ +{ + "semi": true, + "singleQuote": true, + "singleAttributePerLine": false, + "htmlWhitespaceSensitivity": "css", + "printWidth": 80, + "plugins": [ + "prettier-plugin-tailwindcss" + ], + "tailwindFunctions": [ + "clsx", + "cn", + "cva" + ], + "tailwindStylesheet": "resources/css/app.css", + "tabWidth": 4, + "overrides": [ + { + "files": "**/*.yml", + "options": { + "tabWidth": 2 + } + } + ] +} diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php new file mode 100644 index 0000000..1d86616 --- /dev/null +++ b/app/Actions/Fortify/CreateNewUser.php @@ -0,0 +1,85 @@ + $input + */ + public function create(array $input) + { + // Check if registration is enabled via admin site settings + $siteSettings = \App\Models\AppSetting::get('site.settings', []); + if (isset($siteSettings['registration_open']) && $siteSettings['registration_open'] === false) { + throw ValidationException::withMessages([ + 'email' => ['Die Registrierung ist derzeit deaktiviert.'], + ]); + } + + Validator::make($input, [ + 'username' => ['required', 'string', 'max:255', 'alpha_dash', Rule::unique(User::class)], + 'email' => ['required', 'string', 'email', 'max:255', Rule::unique(User::class)], + 'first_name' => ['required', 'string', 'max:255'], + 'last_name' => ['required', 'string', 'max:255'], + 'birthdate' => ['required', 'date', 'before:today'], + 'gender' => ['required', 'string', Rule::in(['male', 'female', 'other'])], + 'phone' => ['required', 'string', 'max:255'], + 'country' => ['required', 'string', 'size:2'], + 'address_line1'=> ['required', 'string', 'max:255'], + 'address_line2'=> ['nullable', 'string', 'max:255'], + 'city' => ['required', 'string', 'max:255'], + 'postal_code' => ['required', 'string', 'max:255'], + 'currency' => ['required', 'string', Rule::in(['EUR', 'USD', 'GBP', 'BTC'])], + 'password' => $this->passwordRules(), + 'is_adult' => ['accepted'], + 'terms_accepted' => ['accepted'], + ])->validate(); + + // Anti-Abuse: block if more than 3 accounts already registered from this IP in 24h + $ip = request()->ip(); + if ($ip) { + $recentCount = User::where('registration_ip', $ip) + ->where('created_at', '>=', now()->subHours(24)) + ->count(); + + if ($recentCount >= 3) { + throw ValidationException::withMessages([ + 'email' => ['Too many accounts registered from this IP address. Please try again later.'], + ]); + } + } + + return User::create([ + 'username' => $input['username'], + 'email' => $input['email'], + 'first_name' => $input['first_name'], + 'last_name' => $input['last_name'], + 'name' => ($input['first_name'] ?? '') . ' ' . ($input['last_name'] ?? ''), + 'birthdate' => $input['birthdate'], + 'gender' => $input['gender'], + 'phone' => $input['phone'], + 'country' => $input['country'], + 'address_line1' => $input['address_line1'], + 'address_line2' => $input['address_line2'] ?? '', + 'city' => $input['city'], + 'postal_code' => $input['postal_code'], + 'currency' => $input['currency'], + 'is_adult' => (bool)$input['is_adult'], + 'password' => Hash::make($input['password']), + 'registration_ip' => $ip, + ]); + } +} diff --git a/app/Actions/Fortify/ResetUserPassword.php b/app/Actions/Fortify/ResetUserPassword.php new file mode 100644 index 0000000..8fda5dd --- /dev/null +++ b/app/Actions/Fortify/ResetUserPassword.php @@ -0,0 +1,29 @@ + $input + */ + public function reset(User $user, array $input): void + { + Validator::make($input, [ + 'password' => $this->passwordRules(), + ])->validate(); + + $user->forceFill([ + 'password' => $input['password'], + ])->save(); + } +} diff --git a/app/Auth/EncryptedUserProvider.php b/app/Auth/EncryptedUserProvider.php new file mode 100644 index 0000000..c8483b4 --- /dev/null +++ b/app/Auth/EncryptedUserProvider.php @@ -0,0 +1,98 @@ + ! str_contains($key, 'password'), + ARRAY_FILTER_USE_KEY + ); + + // Handle the common Fortify username alias `login` (email or username) + if (array_key_exists('login', $credentials)) { + $login = $credentials['login']; + unset($credentials['login']); // prevent accidental `where login = ?` + } + + // If there are no identifier credentials and no `login`, fall back to the + // currently authenticated user (used by Fortify confirm-password flow). + if ((empty($credentials)) && (!isset($login) || $login === null || $login === '')) { + $currentId = Auth::id(); + if ($currentId) { + return $this->retrieveById($currentId); + } + return null; + } + + $query = $this->newModelQuery(); + + // If a generic `login` was provided, match against blind indexes for email/username + if (isset($login) && is_string($login) && $login !== '') { + if (strlen($login) === 64 && ctype_xdigit($login)) { + // Already a sha256 hash + $query->where(function ($q) use ($login) { + $q->where('email_index', $login) + ->orWhere('username_index', $login); + }); + } else { + $hash = hash('sha256', $login); + $query->where(function ($q) use ($hash) { + $q->where('email_index', $hash) + ->orWhere('username_index', $hash); + }); + } + } + + // Apply any remaining credential filters safely + foreach ($credentials as $key => $value) { + // Skip empty scalars to avoid `WHERE IS NULL` and unknown columns + if ($value === null || (is_string($value) && $value === '')) { + continue; + } + + if (is_array($value) || $value instanceof Arrayable) { + // Only use whereIn for known lookup keys + if (in_array($key, ['id', 'email', 'username', 'email_index', 'username_index'], true)) { + $query->whereIn($key, $value); + } + continue; + } + + if ($key === 'email') { + // Accept either plaintext email (hashed) or a precomputed hash + if (strlen($value) === 64 && ctype_xdigit($value)) { + $query->where('email_index', $value); + } else { + $query->where('email_index', hash('sha256', $value)); + } + } elseif ($key === 'username') { + // Use blind index for username lookup + $query->where('username_index', hash('sha256', $value)); + } elseif (in_array($key, ['id', 'email_index', 'username_index'], true)) { + // Allow direct lookups on safe columns only + $query->where($key, $value); + } else { + // Ignore unknown/non-whitelisted keys to avoid querying non-existent columns + continue; + } + } + + return $query->first(); + } +} diff --git a/app/Casts/EncryptedDecimal.php b/app/Casts/EncryptedDecimal.php new file mode 100644 index 0000000..415cbde --- /dev/null +++ b/app/Casts/EncryptedDecimal.php @@ -0,0 +1,75 @@ + 18) { + throw new InvalidArgumentException('Invalid scale for EncryptedDecimal.'); + } + $this->scale = $scale; + } + + public function get($model, string $key, $value, array $attributes) + { + if ($value === null || $value === '') { + // Treat null as zero but do not mutate DB implicitly + return number_format(0, $this->scale, '.', ''); + } + try { + $plain = Crypt::decryptString((string) $value); + } catch (\Throwable $e) { + // If value is not decryptable (legacy/plain), try to normalize as plain string + $plain = (string) $value; + } + return $this->normalize($plain); + } + + public function set($model, string $key, $value, array $attributes) + { + if ($value === null || $value === '') { + return [$key => null]; + } + $normalized = $this->normalize($value); + return [$key => Crypt::encryptString($normalized)]; + } + + private function normalize($value): string + { + // Accept numeric, string with comma/dot; normalize to string with fixed scale + $s = is_string($value) ? trim($value) : (string) $value; + $s = str_replace([',', ' '], ['', ''], $s); + if (!preg_match('/^-?\d*(?:\.\d+)?$/', $s)) { + throw new InvalidArgumentException('Invalid decimal value.'); + } + if ($s === '' || $s === '-') { + $s = '0'; + } + // Use integer math on scaled value to avoid float precision + $scale = $this->scale; + // Split integer and fractional part + $neg = str_starts_with($s, '-') ? '-' : ''; + if ($neg) $s = substr($s, 1); + [$int, $frac] = array_pad(explode('.', $s, 2), 2, ''); + $frac = substr($frac . str_repeat('0', $scale), 0, $scale); + $intPart = ltrim($int === '' ? '0' : $int, '0'); + if ($intPart === '') { $intPart = '0'; } + $out = ($neg ? '-' : '') . $intPart; + if ($scale > 0) { + $out .= '.' . $frac; + } + return $out; + } +} diff --git a/app/Casts/SafeEncryptedString.php b/app/Casts/SafeEncryptedString.php new file mode 100644 index 0000000..a81ceb7 --- /dev/null +++ b/app/Casts/SafeEncryptedString.php @@ -0,0 +1,88 @@ + $attributes + */ + public function get($model, string $key, $value, array $attributes) + { + if ($value === null || $value === '') { + return $value; + } + + try { + return Crypt::decryptString($value); + } catch (\Throwable $e) { + // Not an encrypted payload (or bad key) — return as-is. + return $value; + } + } + + /** + * Prepare the given value for storage. + * + * @param mixed $model + * @param string $key + * @param mixed $value + * @param array $attributes + * @return mixed + */ + public function set($model, string $key, $value, array $attributes) + { + if ($value === null || $value === '') { + return $value; + } + + $stringValue = (string) $value; + + // Ziel: Immer Klartext in der DB speichern. + // Wenn ein verschlüsselter Laravel-Payload übergeben wurde, entschlüsseln und im Klartext ablegen. + if (self::looksEncrypted($stringValue)) { + try { + return Crypt::decryptString($stringValue); + } catch (\Throwable $e) { + // Falls Entschlüsselung (noch) nicht möglich: als Fallback unverändert ablegen, + // aber bevorzugt sollte der Aufrufer bereits Klartext liefern. + return $stringValue; + } + } + + // Bereits Klartext → so speichern + return $stringValue; + } + + private static function looksEncrypted(string $value): bool + { + // Laravel's Crypt::encryptString returns base64-encoded JSON string + // with keys like iv/value/mac and sometimes tag. + $decoded = base64_decode($value, true); + if ($decoded === false) { + return false; + } + $json = json_decode($decoded, true); + if (!is_array($json)) { + return false; + } + $hasCoreKeys = isset($json['iv'], $json['value'], $json['mac']); + // 'tag' may or may not exist depending on cipher/version + return $hasCoreKeys; + } +} diff --git a/app/Concerns/PasswordValidationRules.php b/app/Concerns/PasswordValidationRules.php new file mode 100644 index 0000000..9b45ef0 --- /dev/null +++ b/app/Concerns/PasswordValidationRules.php @@ -0,0 +1,28 @@ +|string> + */ + protected function passwordRules(): array + { + return ['required', 'string', Password::default(), 'confirmed']; + } + + /** + * Get the validation rules used to validate the current password. + * + * @return array|string> + */ + protected function currentPasswordRules(): array + { + return ['required', 'string', 'current_password']; + } +} diff --git a/app/Concerns/ProfileValidationRules.php b/app/Concerns/ProfileValidationRules.php new file mode 100644 index 0000000..2e11581 --- /dev/null +++ b/app/Concerns/ProfileValidationRules.php @@ -0,0 +1,62 @@ +|string>> + */ + protected function profileRules(?int $userId = null): array + { + return [ + 'name' => $this->nameRules(), + 'email' => $this->emailRules($userId), + // Optional extended profile fields + 'first_name' => ['nullable','string','max:60'], + 'last_name' => ['nullable','string','max:60'], + 'gender' => ['nullable','string','max:24'], + 'birthdate' => ['nullable','date','before:today'], + 'country' => ['nullable','string','size:2'], + 'address_line1' => ['nullable','string','max:120'], + 'address_line2' => ['nullable','string','max:120'], + 'city' => ['nullable','string','max:80'], + 'state' => ['nullable','string','max:80'], + 'postal_code' => ['nullable','string','max:32'], + 'phone' => ['nullable','string','max:32'], + ]; + } + + /** + * Get the validation rules used to validate user names. + * + * @return array|string> + */ + protected function nameRules(): array + { + return ['required', 'string', 'max:255']; + } + + /** + * Get the validation rules used to validate user emails. + * + * @return array|string> + */ + protected function emailRules(?int $userId = null): array + { + return [ + 'required', + 'string', + 'email', + 'max:255', + $userId === null + ? Rule::unique(User::class) + : Rule::unique(User::class)->ignore($userId), + ]; + } +} diff --git a/app/Console/Commands/OperatorCreateCasino.php b/app/Console/Commands/OperatorCreateCasino.php new file mode 100644 index 0000000..774a800 --- /dev/null +++ b/app/Console/Commands/OperatorCreateCasino.php @@ -0,0 +1,55 @@ +argument('name'); + + // Generate key in format betix_{64 hex chars} + $licenseKey = 'betix_' . bin2hex(random_bytes(32)); + + $ipWhitelist = array_filter($this->option('ip')); + $domainWhitelist = array_filter($this->option('domain')); + + $casino = OperatorCasino::create([ + 'name' => $name, + 'license_key_hash' => hash('sha256', $licenseKey), + 'status' => 'active', + 'ip_whitelist' => !empty($ipWhitelist) ? array_values($ipWhitelist) : null, + 'domain_whitelist' => !empty($domainWhitelist) ? array_values($domainWhitelist) : null, + ]); + + $this->newLine(); + $this->components->success("Operator casino created: [{$casino->id}] {$casino->name}"); + $this->newLine(); + + $this->components->warn('⚠ Save this license key — it will NOT be shown again:'); + $this->line(''); + $this->line(" {$licenseKey}"); + $this->line(''); + + if (!empty($casino->ip_whitelist)) { + $this->line('IP whitelist: ' . implode(', ', $casino->ip_whitelist)); + } + if (!empty($casino->domain_whitelist)) { + $this->line('Domain whitelist: ' . implode(', ', $casino->domain_whitelist)); + } + + $this->newLine(); + + return self::SUCCESS; + } +} diff --git a/app/Console/Commands/ReencryptUserData.php b/app/Console/Commands/ReencryptUserData.php new file mode 100644 index 0000000..0e337c6 --- /dev/null +++ b/app/Console/Commands/ReencryptUserData.php @@ -0,0 +1,44 @@ +option('chunk'); + $updated = 0; + + // We want small transactions per chunk to avoid long locks + User::query()->orderBy('id') + ->chunkById($chunk, function ($users) use (&$updated) { + DB::transaction(function () use ($users, &$updated) { + foreach ($users as $u) { + // Read via casts (plaintext) and assign back to trigger normalization + $email = $u->email; + $username = $u->username; + + $dirty = false; + if ($email !== null) { $u->email = $email; $dirty = true; } + if ($username !== null) { $u->username = $username; $dirty = true; } + + if ($dirty) { + $u->saveQuietly(); + $updated++; + } + } + }); + }); + + $this->info("Users normalized: {$updated}"); + $this->info('Tip: Remove APP_PREVIOUS_KEYS after normalization if keys are unified.'); + return self::SUCCESS; + } +} diff --git a/app/Http/Controllers/Admin/GeoBlockController.php b/app/Http/Controllers/Admin/GeoBlockController.php new file mode 100644 index 0000000..8b87e4f --- /dev/null +++ b/app/Http/Controllers/Admin/GeoBlockController.php @@ -0,0 +1,56 @@ + false, + 'blocked_countries' => [], + 'allowed_countries' => [], + 'mode' => 'blacklist', // 'blacklist' or 'whitelist' + 'vpn_block' => false, + 'vpn_provider' => 'none', // 'none', 'ipqualityscore', 'proxycheck' + 'vpn_api_key' => '', + 'block_message' => 'This service is not available in your region.', + 'redirect_url' => '', + ]; + + public function show() + { + $saved = AppSetting::get(self::KEY, []); + $settings = array_merge($this->defaults, is_array($saved) ? $saved : []); + + return Inertia::render('Admin/GeoBlock', [ + 'settings' => $settings, + ]); + } + + public function save(Request $request) + { + $data = $request->validate([ + 'enabled' => 'boolean', + 'mode' => 'required|in:blacklist,whitelist', + 'blocked_countries' => 'array', + 'blocked_countries.*' => 'string|size:2', + 'allowed_countries' => 'array', + 'allowed_countries.*' => 'string|size:2', + 'vpn_block' => 'boolean', + 'vpn_provider' => 'required|in:none,ipqualityscore,proxycheck', + 'vpn_api_key' => 'nullable|string|max:200', + 'block_message' => 'required|string|max:500', + 'redirect_url' => 'nullable|url|max:500', + ]); + + AppSetting::put(self::KEY, $data); + + return back()->with('success', 'GeoBlock settings saved.'); + } +} diff --git a/app/Http/Controllers/Admin/PaymentsSettingsController.php b/app/Http/Controllers/Admin/PaymentsSettingsController.php new file mode 100644 index 0000000..a51d521 --- /dev/null +++ b/app/Http/Controllers/Admin/PaymentsSettingsController.php @@ -0,0 +1,130 @@ +role), ['admin', 'owner']), 403); + + $settings = $this->deposits->getSettings(); + + return Inertia::render('Admin/PaymentsSettings', [ + 'settings' => $settings, + 'defaults' => [ + 'commonCurrencies' => ['BTC','ETH','LTC','SOL','USDT_ERC20','USDT_TRC20','BCH','DOGE'], + 'modes' => ['live','sandbox'], + 'addressModes' => ['per_payment','per_user'], + ], + ]); + } + + /** + * POST /admin/payments/settings + */ + public function save(Request $request) + { + $user = Auth::user(); + abort_unless($user && in_array(strtolower((string) $user->role), ['admin', 'owner']), 403); + + $data = $request->validate([ + 'mode' => ['required','in:live,sandbox'], + 'api_key' => ['nullable','string','max:200'], + 'ipn_secret' => ['nullable','string','max:200'], + 'enabled_currencies' => ['required','array','min:1'], + 'enabled_currencies.*' => ['string','max:32'], + 'global_min_usd' => ['required','numeric','min:0'], + 'global_max_usd' => ['required','numeric','gt:global_min_usd'], + 'btx_per_usd' => ['required','numeric','min:0.00000001'], + 'per_currency_overrides' => ['sometimes','array'], + 'per_currency_overrides.*.min_usd' => ['nullable','numeric','min:0'], + 'per_currency_overrides.*.max_usd' => ['nullable','numeric'], + 'per_currency_overrides.*.btx_per_usd' => ['nullable','numeric','min:0.00000001'], + 'success_url' => ['required','string','max:255'], + 'cancel_url' => ['required','string','max:255'], + 'address_mode' => ['required','in:per_payment,per_user'], + ]); + + // Normalize overrides structure as map keyed by currency + $overrides = []; + if (!empty($data['per_currency_overrides']) && is_array($data['per_currency_overrides'])) { + foreach ($data['per_currency_overrides'] as $cur => $vals) { + if (is_array($vals)) { + $entry = []; + if (array_key_exists('min_usd', $vals) && $vals['min_usd'] !== null) $entry['min_usd'] = (float) $vals['min_usd']; + if (array_key_exists('max_usd', $vals) && $vals['max_usd'] !== null) $entry['max_usd'] = (float) $vals['max_usd']; + if (array_key_exists('btx_per_usd', $vals) && $vals['btx_per_usd'] !== null) $entry['btx_per_usd'] = (float) $vals['btx_per_usd']; + if (!empty($entry)) { + $overrides[strtoupper($cur)] = $entry; + } + } + } + } + + // Preserve existing api_key/ipn_secret if not re-submitted (masked fields) + $existing = AppSetting::get('payments.nowpayments', []); + $apiKey = $data['api_key'] ?? null; + $ipnSecret = $data['ipn_secret'] ?? null; + + $payload = [ + 'mode' => $data['mode'], + 'api_key' => $apiKey ?: ($existing['api_key'] ?? ''), + 'ipn_secret' => $ipnSecret ?: ($existing['ipn_secret'] ?? ''), + 'enabled_currencies' => array_values(array_map('strtoupper', $data['enabled_currencies'])), + 'global_min_usd' => (float) $data['global_min_usd'], + 'global_max_usd' => (float) $data['global_max_usd'], + 'btx_per_usd' => (float) $data['btx_per_usd'], + 'per_currency_overrides' => $overrides, + 'success_url' => (string) $data['success_url'], + 'cancel_url' => (string) $data['cancel_url'], + 'address_mode' => (string) $data['address_mode'], + ]; + + AppSetting::put('payments.nowpayments', $payload); + + return back()->with('success', 'Payment settings saved.'); + } + + /** + * POST /admin/payments/test + */ + public function test(Request $request) + { + $user = Auth::user(); + abort_unless($user && in_array(strtolower((string) $user->role), ['admin', 'owner']), 403); + + $data = $request->validate(['api_key' => 'required|string|max:200']); + + try { + $res = Http::timeout(8)->withHeaders([ + 'x-api-key' => $data['api_key'], + 'Accept' => 'application/json', + ])->get('https://api.nowpayments.io/v1/status'); + + if ($res->ok()) { + return response()->json(['ok' => true, 'message' => 'Verbindung erfolgreich! NOWPayments API erreichbar.']); + } + + return response()->json(['ok' => false, 'message' => 'API antwortet mit Status ' . $res->status() . '. API Key prüfen.'], 422); + } catch (\Throwable $e) { + return response()->json(['ok' => false, 'message' => 'Verbindung fehlgeschlagen: ' . $e->getMessage()], 422); + } + } +} diff --git a/app/Http/Controllers/Admin/PromoAdminController.php b/app/Http/Controllers/Admin/PromoAdminController.php new file mode 100644 index 0000000..e6932e1 --- /dev/null +++ b/app/Http/Controllers/Admin/PromoAdminController.php @@ -0,0 +1,130 @@ +role) !== 'admin') { + abort(403, 'Nur für Admins'); + } + } + + /** + * Show simple admin page to manage promos (data from upstream) + */ + public function index(Request $request): Response + { + $this->assertAdmin(); + + $promos = []; + try { + $res = $this->client->get($request, '/admin/promos', ['per_page' => 20], retry: true); + if ($res->successful()) { + $j = $res->json() ?: []; + $promos = $j['data'] ?? $j['promos'] ?? $j; + } + } catch (\Throwable $e) { + // show empty list with error message on page via flash if desired + } + + return Inertia::render('Admin/Promos', [ + 'promos' => $promos, + ]); + } + + /** + * Create a new promo via upstream + */ + public function store(Request $request) + { + $this->assertAdmin(); + + $data = $this->validateData($request); + $data['code'] = strtoupper(trim($data['code'])); + $data['is_active'] = $data['is_active'] ?? true; + + try { + $res = $this->client->post($request, '/admin/promos', $data); + if ($res->successful()) { + return back()->with('success', 'Promo erstellt.'); + } + if ($res->clientError()) { + $msg = data_get($res->json(), 'message', 'Ungültige Eingabe'); + return back()->withErrors(['promo' => $msg]); + } + if ($res->serverError()) { + return back()->withErrors(['promo' => 'Service temporär nicht verfügbar']); + } + return back()->withErrors(['promo' => 'API Server nicht erreichbar']); + } catch (\Throwable $e) { + return back()->withErrors(['promo' => 'API Server nicht erreichbar']); + } + } + + /** + * Update an existing promo via upstream + */ + public function update(Request $request, int $id) + { + $this->assertAdmin(); + + $data = $this->validateData($request, $id); + if (isset($data['code'])) { + $data['code'] = strtoupper(trim($data['code'])); + } + + try { + $res = $this->client->patch($request, "/admin/promos/{$id}", $data); + if ($res->successful()) { + return back()->with('success', 'Promo aktualisiert.'); + } + if ($res->clientError()) { + $msg = data_get($res->json(), 'message', 'Ungültige Eingabe'); + return back()->withErrors(['promo' => $msg]); + } + if ($res->serverError()) { + return back()->withErrors(['promo' => 'Service temporär nicht verfügbar']); + } + return back()->withErrors(['promo' => 'API Server nicht erreichbar']); + } catch (\Throwable $e) { + return back()->withErrors(['promo' => 'API Server nicht erreichbar']); + } + } + + private function validateData(Request $request, ?int $ignoreId = null): array + { + $isUpdate = $request->isMethod('patch') || $request->isMethod('put'); + + $rules = [ + 'code' => [($isUpdate ? 'sometimes' : 'required'), 'string', 'max:64'], + 'description' => [($isUpdate ? 'sometimes' : 'nullable'), 'nullable', 'string', 'max:255'], + 'bonus_amount' => [($isUpdate ? 'sometimes' : 'required'), 'numeric', 'min:0'], + 'wager_multiplier' => [($isUpdate ? 'sometimes' : 'required'), 'integer', 'min:0', 'max:1000'], + 'per_user_limit' => [($isUpdate ? 'sometimes' : 'required'), 'integer', 'min:1', 'max:1000'], + 'global_limit' => [($isUpdate ? 'sometimes' : 'nullable'), 'nullable', 'integer', 'min:1', 'max:1000000'], + 'starts_at' => [($isUpdate ? 'sometimes' : 'nullable'), 'nullable', 'date'], + 'ends_at' => [($isUpdate ? 'sometimes' : 'nullable'), 'nullable', 'date', 'after_or_equal:starts_at'], + 'min_deposit' => [($isUpdate ? 'sometimes' : 'nullable'), 'nullable', 'numeric', 'min:0'], + 'bonus_expires_days' => [($isUpdate ? 'sometimes' : 'nullable'), 'nullable', 'integer', 'min:1', 'max:365'], + 'is_active' => [($isUpdate ? 'sometimes' : 'nullable'), 'boolean'], + ]; + + return $request->validate($rules); + } +} diff --git a/app/Http/Controllers/Admin/SiteSettingsController.php b/app/Http/Controllers/Admin/SiteSettingsController.php new file mode 100644 index 0000000..85a4ef7 --- /dev/null +++ b/app/Http/Controllers/Admin/SiteSettingsController.php @@ -0,0 +1,79 @@ + 'BetiX Casino', + 'site_tagline' => 'Play. Win. Repeat.', + 'primary_color' => '#df006a', + 'logo_url' => '', + 'favicon_url' => '', + 'maintenance_mode' => false, + 'registration_open' => true, + 'min_deposit_usd' => 10, + 'max_deposit_usd' => 50000, + 'min_withdrawal_usd' => 20, + 'max_withdrawal_usd' => 100000, + 'max_bet_usd' => 5000, + 'house_edge_percent' => 1.0, + 'footer_text' => '', + 'support_email' => '', + 'terms_url' => '/terms', + 'privacy_url' => '/privacy', + 'currency_symbol' => 'BTX', + ]; + + public function show() + { + $saved = AppSetting::get(self::KEY, []); + $settings = array_merge($this->defaults, is_array($saved) ? $saved : []); + + return Inertia::render('Admin/SiteSettings', [ + 'settings' => $settings, + ]); + } + + public function save(Request $request) + { + // Normalize empty strings to null so URL/email validation doesn't fail on blank fields + foreach (['logo_url', 'favicon_url', 'terms_url', 'privacy_url', 'support_email', 'site_tagline', 'footer_text'] as $field) { + if ($request->input($field) === '') { + $request->merge([$field => null]); + } + } + + $data = $request->validate([ + 'site_name' => 'required|string|max:100', + 'site_tagline' => 'nullable|string|max:200', + 'primary_color' => 'required|regex:/^#[0-9a-fA-F]{6}$/', + 'logo_url' => 'nullable|url|max:500', + 'favicon_url' => 'nullable|url|max:500', + 'maintenance_mode' => 'boolean', + 'registration_open' => 'boolean', + 'min_deposit_usd' => 'required|numeric|min:0', + 'max_deposit_usd' => 'required|numeric|min:0', + 'min_withdrawal_usd' => 'required|numeric|min:0', + 'max_withdrawal_usd' => 'required|numeric|min:0', + 'max_bet_usd' => 'required|numeric|min:0', + 'house_edge_percent' => 'required|numeric|min:0|max:100', + 'footer_text' => 'nullable|string|max:1000', + 'support_email' => 'nullable|email|max:200', + 'terms_url' => 'nullable|string|max:500', + 'privacy_url' => 'nullable|string|max:500', + 'currency_symbol' => 'required|string|max:10', + ]); + + AppSetting::put(self::KEY, $data); + + return back()->with('success', 'Site settings saved.'); + } +} diff --git a/app/Http/Controllers/Admin/SupportAdminController.php b/app/Http/Controllers/Admin/SupportAdminController.php new file mode 100644 index 0000000..99d5173 --- /dev/null +++ b/app/Http/Controllers/Admin/SupportAdminController.php @@ -0,0 +1,127 @@ +role) !== 'admin') { + abort(403, 'Nur für Admins'); + } + } + + private function ollamaStatus(): array + { + $host = rtrim(env('OLLAMA_HOST', 'http://127.0.0.1:11434'), '/'); + $model = env('OLLAMA_MODEL', 'llama3'); + try { + $res = Http::timeout(2)->get($host . '/api/tags'); + return ['healthy' => $res->ok(), 'host' => $host, 'model' => $model, 'error' => $res->ok() ? null : 'Keine Verbindung']; + } catch (\Throwable $e) { + return ['healthy' => false, 'host' => $host, 'model' => $model, 'error' => $e->getMessage()]; + } + } + + private function getThreads(): array + { + $index = cache()->get('support_threads_index', []); + $threads = []; + foreach (array_reverse($index) as $row) { + $full = cache()->get('support_threads:' . $row['id']); + if (is_array($full)) { + $threads[] = $full; + } else { + $threads[] = $row; + } + } + return $threads; + } + + public function index(Request $request): Response + { + $this->assertAdmin(); + + $enabled = (bool) (cache()->get('support_chat_enabled') ?? config('app.support_chat_enabled', true)); + + return Inertia::render('Admin/Support', [ + 'enabled' => $enabled, + 'threads' => $this->getThreads(), + 'ollama' => $this->ollamaStatus(), + ]); + } + + public function settings(Request $request) + { + $this->assertAdmin(); + $data = $request->validate(['enabled' => 'required|boolean']); + cache()->put('support_chat_enabled', (bool) $data['enabled'], now()->addYear()); + return back()->with('success', 'Support-Chat Einstellungen gespeichert.'); + } + + public function reply(Request $request, string $thread) + { + $this->assertAdmin(); + $data = $request->validate(['text' => 'required|string|min:1|max:1000']); + + $record = cache()->get('support_threads:' . $thread); + if (!is_array($record)) { + return back()->withErrors(['text' => 'Thread nicht gefunden.']); + } + + $record['messages'][] = [ + 'id' => Str::uuid()->toString(), + 'sender' => 'agent', + 'body' => $data['text'], + 'at' => now()->toIso8601String(), + ]; + $record['status'] = 'agent'; + $record['updated_at'] = now()->toIso8601String(); + + cache()->put('support_threads:' . $thread, $record, now()->addDay()); + + // Update index entry + $index = cache()->get('support_threads_index', []); + foreach ($index as &$row) { + if (($row['id'] ?? null) === $thread) { + $row['status'] = 'agent'; + $row['updated_at'] = $record['updated_at']; + break; + } + } + cache()->put('support_threads_index', $index, now()->addDay()); + + return back()->with('success', 'Nachricht gesendet.'); + } + + public function close(Request $request, string $thread) + { + $this->assertAdmin(); + + $record = cache()->get('support_threads:' . $thread); + if (is_array($record)) { + $record['status'] = 'closed'; + $record['updated_at'] = now()->toIso8601String(); + cache()->put('support_threads:' . $thread, $record, now()->addDay()); + } + + $index = cache()->get('support_threads_index', []); + foreach ($index as &$row) { + if (($row['id'] ?? null) === $thread) { + $row['status'] = 'closed'; + break; + } + } + cache()->put('support_threads_index', $index, now()->addDay()); + + return back()->with('success', 'Chat geschlossen.'); + } +} diff --git a/app/Http/Controllers/Admin/WalletsAdminController.php b/app/Http/Controllers/Admin/WalletsAdminController.php new file mode 100644 index 0000000..8d83e28 --- /dev/null +++ b/app/Http/Controllers/Admin/WalletsAdminController.php @@ -0,0 +1,73 @@ +role === 'Admin' || $user->role === 'Owner'), 403); + + $defaults = [ + 'pin_max_attempts' => 5, + 'pin_lock_minutes' => 15, + 'min_tx_btx' => 0.0001, + 'max_tx_btx' => 100000, + 'daily_max_btx' => 100000, + 'actions_per_minute' => 20, + 'reason_required' => true, + ]; + $settings = AppSetting::get('wallet.settings', $defaults) ?: $defaults; + // Ensure defaults filled + $settings = array_replace($defaults, is_array($settings) ? $settings : []); + + return Inertia::render('Admin/WalletsSettings', [ + 'settings' => $settings, + 'defaults' => $defaults, + ]); + } + + /** + * POST /admin/wallets/settings — Save policies and limits. + */ + public function save(Request $request) + { + $user = Auth::user(); + abort_unless($user && ($user->role === 'Admin' || $user->role === 'Owner'), 403); + + $data = $request->validate([ + 'pin_max_attempts' => ['required','integer','min:1','max:20'], + 'pin_lock_minutes' => ['required','integer','min:1','max:1440'], + 'min_tx_btx' => ['required','numeric','min:0'], + 'max_tx_btx' => ['required','numeric','gt:min_tx_btx'], + 'daily_max_btx' => ['required','numeric','gte:max_tx_btx'], + 'actions_per_minute' => ['required','integer','min:1','max:600'], + 'reason_required' => ['required','boolean'], + ]); + + // Normalize numeric precision (BTX uses 4 decimals commonly) + $payload = [ + 'pin_max_attempts' => (int) $data['pin_max_attempts'], + 'pin_lock_minutes' => (int) $data['pin_lock_minutes'], + 'min_tx_btx' => round((float) $data['min_tx_btx'], 4), + 'max_tx_btx' => round((float) $data['max_tx_btx'], 4), + 'daily_max_btx' => round((float) $data['daily_max_btx'], 4), + 'actions_per_minute' => (int) $data['actions_per_minute'], + 'reason_required' => (bool) $data['reason_required'], + ]; + + AppSetting::put('wallet.settings', $payload); + + return back()->with('success', 'Wallet settings saved.'); + } +} diff --git a/app/Http/Controllers/AdminController.php b/app/Http/Controllers/AdminController.php new file mode 100644 index 0000000..c62ea1f --- /dev/null +++ b/app/Http/Controllers/AdminController.php @@ -0,0 +1,604 @@ +role) !== 'admin') { + abort(403, 'Nur für Admins'); + } + } + + /** + * Redirect old /admin to the new dashboard + */ + public function index(Request $request) + { + $this->ensureAdmin(); + return redirect()->route('admin.casino'); + } + + public function casinoDashboard(Request $request) + { + $this->ensureAdmin(); + + $stats = [ + 'total_users' => User::count(), + 'total_wagered' => GameBet::sum('wager_amount'), + 'total_payout' => GameBet::sum('payout_amount'), + 'active_bans' => User::where('is_banned', true)->count(), + 'new_users_24h' => User::where('created_at', '>=', now()->subDay())->count(), + ]; + + $stats['house_edge'] = $stats['total_wagered'] - $stats['total_payout']; + + // Get chart data for the last 7 days + $chartData = []; + for ($i = 6; $i >= 0; $i--) { + $date = Carbon::today()->subDays($i); + $nextDate = Carbon::today()->subDays($i - 1); + + $wagered = GameBet::whereBetween('created_at', [$date, $nextDate])->sum('wager_amount'); + $payout = GameBet::whereBetween('created_at', [$date, $nextDate])->sum('payout_amount'); + $newUsers = User::whereBetween('created_at', [$date, $nextDate])->count(); + + $chartData[] = [ + 'date' => $date->format('Y-m-d'), + 'label' => $date->format('D, d M'), + 'wagered' => (float)$wagered, + 'payout' => (float)$payout, + 'profit' => (float)($wagered - $payout), + 'new_users' => $newUsers, + ]; + } + + $recent_bets = GameBet::with('user:id,username') + ->orderByDesc('id') + ->limit(10) + ->get(); + + $recent_users = User::orderByDesc('id') + ->limit(10) + ->get(); + + return Inertia::render('Admin/CasinoDashboard', [ + 'stats' => $stats, + 'chartData' => $chartData, + 'recentBets' => $recent_bets, + 'recentUsers' => $recent_users, + ]); + } + + public function usersIndex(Request $request) + { + $this->ensureAdmin(); + + $query = User::orderByDesc('id'); + + if ($request->has('search')) { + $search = $request->input('search'); + $query->where(function($q) use ($search) { + $q->where('id', 'like', "%$search%") + ->orWhere('username', 'like', "%$search%") + ->orWhere('email', 'like', "%$search%") + ->orWhere('first_name', 'like', "%$search%") + ->orWhere('last_name', 'like', "%$search%"); + }); + } + + if ($request->has('role') && $request->input('role') !== '') { + $query->where('role', $request->input('role')); + } + + $users = $query->paginate(20)->withQueryString(); + $roles = ['Admin', 'Moderator', 'User']; + + return Inertia::render('Admin/Users', [ + 'users' => $users, + 'roles' => $roles, + 'filters' => $request->only(['search', 'role']) + ]); + } + + public function userShow(Request $request, $id) + { + $this->ensureAdmin(); + $user = User::with('wallets')->findOrFail($id); + + $restrictions = UserRestriction::where('user_id', $user->id)->orderByDesc('id')->get(); + + $vaultTransfers = WalletTransfer::where('user_id', $user->id) + ->whereIn('type', ['vault_deposit', 'vault_withdraw']) + ->orderByDesc('id') + ->get(); + + $deposits = CryptoPayment::where('user_id', $user->id) + ->orderByDesc('id') + ->get(); + + $kycDocuments = class_exists(KycDocument::class) + ? KycDocument::where('user_id', $user->id)->get() + : []; + + return Inertia::render('Admin/UserShow', [ + 'user' => $user, + 'restrictions' => $restrictions, + 'wallets' => $user->wallets, + 'vaultTransfers' => $vaultTransfers, + 'deposits' => $deposits, + 'kycDocuments' => $kycDocuments, + ]); + } + + public function updateUser(Request $request, $id) + { + $this->ensureAdmin(); + + $validated = $request->validate([ + 'username' => 'required|string|max:255', + 'email' => 'required|email|max:255', + 'first_name' => 'nullable|string|max:255', + 'last_name' => 'nullable|string|max:255', + 'birthdate' => 'nullable|date', + 'gender' => 'nullable|string|max:50', + 'phone' => 'nullable|string|max:50', + 'country' => 'nullable|string|max:10', + 'address_line1' => 'nullable|string|max:255', + 'address_line2' => 'nullable|string|max:255', + 'city' => 'nullable|string|max:255', + 'postal_code' => 'nullable|string|max:50', + 'currency' => 'nullable|string|max:10', + 'vip_level' => 'nullable|integer|min:0|max:100', + 'balance' => 'nullable|numeric', + 'vault_balance' => 'nullable|numeric', + 'is_banned' => 'boolean', + 'is_chat_banned' => 'boolean', + 'ban_reason' => 'nullable|string|max:255', + 'role' => 'nullable|string|max:50' + ]); + + DB::transaction(function() use ($id, $validated, $request) { + $user = User::where('id', $id)->lockForUpdate()->firstOrFail(); + + $user->fill($validated); + + // Update restrictions for ban + if ($request->has('is_banned')) { + $user->is_banned = $request->input('is_banned'); + if ($user->is_banned) { + UserRestriction::updateOrCreate( + ['user_id' => $user->id, 'type' => 'account_ban'], + [ + 'active' => true, + 'reason' => $request->input('ban_reason'), + 'expires_at' => $request->input('ban_ends_at') + ] + ); + } else { + UserRestriction::where('user_id', $user->id)->where('type', 'account_ban')->update(['active' => false]); + } + } + + // Update restrictions for chat ban (via UserRestriction only, no column on users table) + if ($request->has('is_chat_banned')) { + if ($request->boolean('is_chat_banned')) { + UserRestriction::updateOrCreate( + ['user_id' => $user->id, 'type' => 'chat_ban'], + [ + 'active' => true, + 'reason' => 'Admin intervention', + 'ends_at' => $request->input('chat_ban_ends_at'), + ] + ); + } else { + UserRestriction::where('user_id', $user->id)->where('type', 'chat_ban')->update(['active' => false]); + } + } + + $user->save(); + }); + + return back()->with('success', 'User updated successfully.'); + } + + public function userHistory(Request $request, $id) + { + $this->ensureAdmin(); + $user = User::findOrFail($id); + + // Fetch wallet transfers as history + $transfers = WalletTransfer::where('user_id', $user->id) + ->orderByDesc('created_at') + ->limit(50) + ->get(); + + return response()->json(['data' => $transfers]); + } + + public function chatIndex(Request $request) + { + $this->ensureAdmin(); + + $aiEnabled = AppSetting::get('chat.ai_enabled', false); + + return Inertia::render('Admin/Chat', [ + 'aiEnabled' => $aiEnabled + ]); + } + + public function toggleAi(Request $request) + { + $this->ensureAdmin(); + $enabled = $request->input('enabled', false); + AppSetting::put('chat.ai_enabled', $enabled); + + return back()->with('success', 'AI Chat ' . ($enabled ? 'enabled' : 'disabled')); + } + + public function deleteChatMessage($id) + { + $this->ensureAdmin(); + // implement chat deletion - typically this would proxy to the backend API + // For now, we'll just mock it as success if we don't have direct DB access to it here + // (ChatMessage model exists locally, but messages might be on the upstream) + return back()->with('success', 'Message deleted (Mock)'); + } + + public function chatReportShow(Request $request, $id) + { + $this->ensureAdmin(); + + $report = ChatMessageReport::with([ + 'reporter:id,username,email,avatar,avatar_url,role,vip_level,is_banned,created_at', + ])->findOrFail($id); + + $restrictionWith = fn ($q) => $q->withTrashed(false)->orderByDesc('created_at'); + + $senderUser = null; + if ($report->sender_id) { + $senderUser = User::with(['restrictions' => $restrictionWith]) + ->find($report->sender_id); + } + + $reporterUser = User::with(['restrictions' => $restrictionWith]) + ->find($report->reporter_id); + + return Inertia::render('Admin/ChatReportShow', [ + 'report' => $report, + 'senderUser' => $senderUser, + 'reporterUser' => $reporterUser, + 'flash' => session('success'), + ]); + } + + public function punishFromChatReport(Request $request, $id) + { + $this->ensureAdmin(); + + $report = ChatMessageReport::findOrFail($id); + + $validated = $request->validate([ + 'type' => 'required|in:chat_ban,account_ban', + 'reason' => 'required|string|max:300', + 'hours' => 'nullable|integer|min:1', // null = permanent + ]); + + $targetId = $report->sender_id; + if (!$targetId) { + return back()->withErrors(['error' => 'Kein Nutzer mit dieser Nachricht verknüpft.']); + } + + $user = User::findOrFail($targetId); + $admin = Auth::user(); + $endsAt = $validated['hours'] ? now()->addHours($validated['hours']) : null; + + UserRestriction::updateOrCreate( + ['user_id' => $user->id, 'type' => $validated['type']], + [ + 'active' => true, + 'reason' => $validated['reason'], + 'notes' => "Via Chat-Report #$report->id", + 'imposed_by' => $admin->id, + 'starts_at' => now(), + 'ends_at' => $endsAt, + 'source' => 'admin_panel', + 'metadata' => ['report_id' => $report->id], + ] + ); + + if ($validated['type'] === 'account_ban') { + $user->update([ + 'is_banned' => true, + 'ban_reason' => $validated['reason'], + ]); + } + + $report->update(['status' => 'reviewed']); + + return back()->with('success', 'Strafe wurde erfolgreich verhängt.'); + } + + public function chatReports(Request $request) + { + $this->ensureAdmin(); + + $query = ChatMessageReport::with('reporter:id,username,email,avatar,avatar_url') + ->orderByDesc('id'); + + $status = $request->input('status', 'pending'); + if ($status && $status !== 'all') { + $query->where('status', $status); + } + + $search = trim((string) $request->input('search', '')); + if ($search !== '') { + if (is_numeric($search)) { + $query->where('id', $search); + } else { + $q = '%' . $search . '%'; + $query->where(function ($sub) use ($q) { + $sub->where('sender_username', 'like', $q) + ->orWhereHas('reporter', fn($r) => $r->where('username', 'like', $q)); + }); + } + } + + $stats = [ + 'pending' => ChatMessageReport::where('status', 'pending')->count(), + 'reviewed' => ChatMessageReport::where('status', 'reviewed')->count(), + 'dismissed' => ChatMessageReport::where('status', 'dismissed')->count(), + ]; + $stats['total'] = $stats['pending'] + $stats['reviewed'] + $stats['dismissed']; + + $reports = $query->paginate(25)->withQueryString(); + + return Inertia::render('Admin/ChatReports', [ + 'reports' => $reports, + 'filters' => $request->only(['status', 'search']), + 'stats' => $stats, + ]); + } + + public function updateChatReport(Request $request, $id) + { + $this->ensureAdmin(); + + $validated = $request->validate([ + 'status' => 'required|in:pending,reviewed,dismissed', + 'admin_note' => 'nullable|string|max:1000', + ]); + + $report = ChatMessageReport::findOrFail($id); + $report->update($validated); + + return back()->with('success', 'Report updated.'); + } + + public function liftRestriction(Request $request, $id) + { + $this->ensureAdmin(); + + $restriction = UserRestriction::findOrFail($id); + $restriction->update(['active' => false]); + + if ($restriction->type === 'account_ban') { + User::where('id', $restriction->user_id)->update(['is_banned' => false]); + } + + return back()->with('success', 'Sperre aufgehoben.'); + } + + public function extendRestriction(Request $request, $id) + { + $this->ensureAdmin(); + + $validated = $request->validate([ + 'hours' => 'required|integer|min:1', + ]); + + $restriction = UserRestriction::findOrFail($id); + $base = $restriction->ends_at && $restriction->ends_at->isFuture() + ? $restriction->ends_at + : now(); + + $restriction->update(['ends_at' => $base->addHours($validated['hours'])]); + + return back()->with('success', 'Sperre verlängert.'); + } + + public function profileReports(Request $request) + { + $this->ensureAdmin(); + + $query = ProfileReport::with([ + 'reporter:id,username,email,avatar,avatar_url', + 'profile:id,username,email,avatar,avatar_url,role,vip_level,bio', + ])->orderByDesc('id'); + + $status = $request->input('status', 'pending'); + if ($status && $status !== 'all') { + $query->where('status', $status); + } + + $search = trim((string) $request->input('search', '')); + if ($search !== '') { + if (is_numeric($search)) { + $query->where('id', $search); + } else { + $q = '%' . $search . '%'; + $query->where(function ($sub) use ($q) { + $sub->whereHas('reporter', fn($r) => $r->where('username', 'like', $q)) + ->orWhereHas('profile', fn($r) => $r->where('username', 'like', $q)); + }); + } + } + + $stats = [ + 'pending' => ProfileReport::where('status', 'pending')->count(), + 'reviewed' => ProfileReport::where('status', 'reviewed')->count(), + 'dismissed' => ProfileReport::where('status', 'dismissed')->count(), + ]; + $stats['total'] = $stats['pending'] + $stats['reviewed'] + $stats['dismissed']; + + $reports = $query->paginate(25)->withQueryString(); + + return Inertia::render('Admin/ProfileReports', [ + 'reports' => $reports, + 'filters' => $request->only(['status', 'search']), + 'stats' => $stats, + ]); + } + + public function profileReportShow(Request $request, $id) + { + $this->ensureAdmin(); + + $report = ProfileReport::with([ + 'reporter:id,username,email,avatar,avatar_url,role,vip_level,is_banned,created_at', + 'profile:id,username,email,avatar,avatar_url,role,vip_level,is_banned,created_at', + ])->findOrFail($id); + + // Attach restrictions to both users + $reporterUser = null; + $profileUser = null; + + if ($report->reporter_id) { + $reporterUser = User::with(['restrictions' => function ($q) { + $q->orderByDesc('id'); + }])->find($report->reporter_id); + } + if ($report->profile_id) { + $profileUser = User::with(['restrictions' => function ($q) { + $q->orderByDesc('id'); + }])->find($report->profile_id); + } + + // Load current live profile data from DB for comparison + $currentProfile = null; + if ($report->profile_id) { + $u = User::find($report->profile_id); + if ($u) { + $likesCount = ProfileLike::where('profile_id', $u->id)->count(); + $comments = ProfileComment::with(['user:id,username,avatar']) + ->where('profile_id', $u->id) + ->latest() + ->limit(15) + ->get() + ->map(fn($c) => [ + 'id' => $c->id, + 'content' => $c->content, + 'created_at' => $c->created_at, + 'user' => [ + 'id' => $c->user->id, + 'username' => $c->user->username, + 'avatar' => $c->user->avatar, + ], + ]); + + $currentProfile = [ + 'id' => $u->id, + 'username' => $u->username, + 'avatar' => $u->avatar ?? $u->avatar_url, + 'banner' => $u->banner, + 'bio' => $u->bio, + 'role' => $u->role ?? 'User', + 'vip_level' => (int)($u->vip_level ?? 0), + 'clan_tag' => $u->clan_tag, + 'stats' => [ + 'wagered' => (float)($u->stats?->total_wagered ?? 0), + 'wins' => (int)($u->stats?->total_wins ?? 0), + 'biggest_win' => (float)($u->stats?->biggest_win ?? 0), + 'likes_count' => $likesCount, + 'join_date' => optional($u->created_at)->toDateString(), + ], + 'comments' => $comments, + ]; + } + } + + $screenshotUrl = $report->screenshot_path + ? asset('storage/' . $report->screenshot_path) + : null; + + return Inertia::render('Admin/ProfileReportShow', [ + 'report' => $report, + 'reporterUser' => $reporterUser, + 'profileUser' => $profileUser, + 'screenshotUrl' => $screenshotUrl, + 'currentProfile' => $currentProfile, + 'flash' => session('success'), + ]); + } + + public function updateProfileReport(Request $request, $id) + { + $this->ensureAdmin(); + + $validated = $request->validate([ + 'status' => 'required|in:pending,reviewed,dismissed', + 'admin_note' => 'nullable|string|max:1000', + ]); + + $report = ProfileReport::findOrFail($id); + $report->update($validated); + + return back()->with('success', 'Report updated.'); + } + + public function punishFromProfileReport(Request $request, $id) + { + $this->ensureAdmin(); + + $data = $request->validate([ + 'type' => 'required|in:chat_ban,account_ban', + 'reason' => 'required|string|max:500', + 'hours' => 'nullable|integer|min:1', + ]); + + $report = ProfileReport::findOrFail($id); + $target = User::findOrFail($report->profile_id); + + $startsAt = now(); + $endsAt = $data['hours'] ? now()->addHours($data['hours']) : null; + + UserRestriction::create([ + 'user_id' => $target->id, + 'type' => $data['type'], + 'reason' => $data['reason'], + 'active' => true, + 'starts_at' => $startsAt, + 'ends_at' => $endsAt, + ]); + + if ($data['type'] === 'account_ban') { + $target->update(['is_banned' => true]); + } + + $report->update(['status' => 'reviewed']); + + return back()->with('success', 'Strafe erfolgreich verhängt!'); + } +} diff --git a/app/Http/Controllers/Auth/AvailabilityController.php b/app/Http/Controllers/Auth/AvailabilityController.php new file mode 100644 index 0000000..71c3d5c --- /dev/null +++ b/app/Http/Controllers/Auth/AvailabilityController.php @@ -0,0 +1,69 @@ +query('field'); + $value = (string) $request->query('value', ''); + + if (!in_array($field, ['username', 'email'], true)) { + return response()->json([ + 'ok' => false, + 'error' => 'Unsupported field', + ], 422); + } + + // Basic format checks to reduce unnecessary upstream calls + if ($field === 'username' && mb_strlen($value) < 3) { + return response()->json(['ok' => true, 'available' => false, 'reason' => 'too_short']); + } + if ($field === 'email' && !filter_var($value, FILTER_VALIDATE_EMAIL)) { + return response()->json(['ok' => true, 'available' => false, 'reason' => 'invalid_format']); + } + + try { + $res = $this->client->get($request, '/api/auth/availability', [ + 'field' => $field, + 'value' => $value, + ], retry: true); + + if ($res->successful()) { + $j = $res->json() ?: []; + // Normalize to { ok: true, available: bool, reason? } + $available = $j['available'] ?? $j['is_available'] ?? null; + if ($available !== null) { + $out = ['ok' => true, 'available' => (bool) $available]; + if (isset($j['reason'])) { + $out['reason'] = $j['reason']; + } + return response()->json($out, 200); + } + // Fallback: pass-through + return response()->json($j, 200); + } + if ($res->clientError()) return $this->mapClientError($res); + if ($res->serverError()) return $this->mapServiceUnavailable($res); + return $this->mapBadGateway(); + } catch (\Throwable $e) { + return $this->mapBadGateway('API server not reachable'); + } + } +} diff --git a/app/Http/Controllers/Auth/EmailVerificationCodeController.php b/app/Http/Controllers/Auth/EmailVerificationCodeController.php new file mode 100644 index 0000000..1ca0e6a --- /dev/null +++ b/app/Http/Controllers/Auth/EmailVerificationCodeController.php @@ -0,0 +1,49 @@ +validate([ + 'code' => ['required','string','regex:/^\d{6}$/'], + ]); + + $user = $request->user(); + if (!$user) { + return redirect()->route('login'); + } + + if ($user->hasVerifiedEmail()) { + return redirect()->route('dashboard')->with('status', 'Email already verified.'); + } + + $cacheKey = 'email_verify_code:'.$user->getKey(); + $expected = Cache::get($cacheKey); + + // Normalize submitted code to digits-only string + $submitted = (string) $request->input('code', ''); + $submitted = preg_replace('/\D+/', '', $submitted ?? ''); + + if (!$expected || $expected !== $submitted) { + return back()->withErrors(['code' => 'Invalid or expired verification code.']); + } + + // Mark as verified and clear the code + if ($user->markEmailAsVerified()) { + event(new Verified($user)); + } + Cache::forget($cacheKey); + + return redirect()->route('dashboard')->with('status', 'Email verified successfully.'); + } +} diff --git a/app/Http/Controllers/BetiXWebhookController.php b/app/Http/Controllers/BetiXWebhookController.php new file mode 100644 index 0000000..dcf380e --- /dev/null +++ b/app/Http/Controllers/BetiXWebhookController.php @@ -0,0 +1,171 @@ +getContent(); + $sig = $request->header('X-Signature', ''); + $secret = config('services.betix.webhook_secret', ''); + + // If a webhook secret is configured, verify the signature + if ($secret !== '' && !$client->verifyWebhook($rawBody, $sig)) { + Log::warning('[BetiX] Webhook signature mismatch', ['ip' => $request->ip()]); + return response('Unauthorized', 401); + } + + $payload = json_decode($rawBody, true); + if (!$payload || !isset($payload['session_token'])) { + return response('Bad Request', 400); + } + + $token = $payload['session_token']; + $playerId = $payload['player_id'] ?? null; + $endBalance = isset($payload['end_balance']) ? (float) $payload['end_balance'] : null; + $startBalance = isset($payload['start_balance']) ? (float) $payload['start_balance'] : null; + $game = $payload['game'] ?? null; + $rounds = $payload['rounds'] ?? 0; + + $session = OperatorSession::where('session_token', $token)->first(); + + if (!$session) { + // Unknown session — could be a replay or a stale token, acknowledge anyway + Log::info('[BetiX] Webhook for unknown session', ['token' => $token]); + return response('OK', 200); + } + + if ($session->status !== 'active') { + // Already processed (idempotency) + return response('OK', 200); + } + + DB::transaction(function () use ($session, $playerId, $endBalance, $startBalance, $rounds, $game) { + // Mark session as expired + $session->update([ + 'status' => 'expired', + 'current_balance' => $endBalance ?? $session->current_balance, + ]); + + if ($endBalance === null || $playerId === null) { + return; + } + + // Apply balance delta to user (safe even if other transactions happened mid-session) + $user = User::lockForUpdate()->find((int) $playerId); + if (!$user) { + return; + } + + $sessionStart = $startBalance ?? (float) $session->start_balance; + $delta = $endBalance - $sessionStart; // positive = player won, negative = player lost + + $newBalance = max(0, (float) $user->balance + $delta); + $user->balance = $newBalance; + $user->save(); + + Log::info(sprintf( + '[BetiX] Session ended | player=%s | game=%s | rounds=%d | delta=%+.4f BTX | new_balance=%.4f', + $playerId, $game ?? '?', $rounds, $delta, $newBalance + )); + }); + + return response('OK', 200); + } + + /** + * POST /api/betix/round + * + * Called by the BetiX game server after each completed round. + * Updates the user's balance incrementally and logs to game_bets. + * + * Payload: { session_token, player_id, new_balance, currency, server_seed_hash, round } + */ + public function roundUpdate(Request $request, BetiXClient $client): \Illuminate\Http\Response + { + $rawBody = $request->getContent(); + $sig = $request->header('X-Signature', ''); + $secret = config('services.betix.webhook_secret', ''); + + if ($secret !== '' && !$client->verifyWebhook($rawBody, $sig)) { + Log::warning('[BetiX] Round webhook signature mismatch', ['ip' => $request->ip()]); + return response('Unauthorized', 401); + } + + $payload = json_decode($rawBody, true); + if (!$payload || !isset($payload['session_token'], $payload['player_id'], $payload['new_balance'])) { + return response('Bad Request', 400); + } + + $token = $payload['session_token']; + $playerId = (int) $payload['player_id']; + $newBalance = (float) $payload['new_balance']; + $seedHash = $payload['server_seed_hash'] ?? null; + $roundNumber = $payload['round'] ?? null; + $currency = strtoupper($payload['currency'] ?? 'EUR'); + + $session = OperatorSession::where('session_token', $token)->first(); + + if (!$session || $session->status !== 'active') { + // Unknown or already-ended session — acknowledge without processing + return response('OK', 200); + } + + DB::transaction(function () use ($session, $playerId, $newBalance, $seedHash, $roundNumber, $currency) { + $oldSessionBalance = (float) $session->current_balance; + $delta = $newBalance - $oldSessionBalance; // positive = win, negative = loss + + // Update session's running balance + optionally rotate the seed hash + $sessionUpdate = ['current_balance' => $newBalance]; + if ($seedHash) { + $sessionUpdate['server_seed_hash'] = $seedHash; + } + $session->update($sessionUpdate); + + // Apply balance change to user (row-locked for safety) + $user = User::lockForUpdate()->find($playerId); + if (!$user) { + return; + } + + $userBalanceBefore = (float) $user->balance; + $userBalanceAfter = max(0.0, $userBalanceBefore + $delta); + $user->balance = $userBalanceAfter; + $user->save(); + + // Log the round to game_bets (wager = loss side, payout = win side) + GameBet::create([ + 'user_id' => $playerId, + 'game_name' => $session->game_slug, + 'wager_amount' => $delta < 0 ? abs($delta) : 0, + 'payout_amount' => $delta > 0 ? $delta : 0, + 'payout_multiplier'=> 0, + 'currency' => $currency, + 'session_token' => $session->session_token, + 'round_number' => $roundNumber, + 'server_seed_hash' => $seedHash, + ]); + + Log::info(sprintf( + '[BetiX] Round update | player=%d | game=%s | round=%s | delta=%+.4f | balance=%.4f→%.4f', + $playerId, + $session->game_slug, + $roundNumber ?? '?', + $delta, + $userBalanceBefore, + $userBalanceAfter + )); + }); + + return response('OK', 200); + } +} diff --git a/app/Http/Controllers/BonusApiController.php b/app/Http/Controllers/BonusApiController.php new file mode 100644 index 0000000..278deac --- /dev/null +++ b/app/Http/Controllers/BonusApiController.php @@ -0,0 +1,156 @@ +middleware(function ($request, $next) { + $provided = $this->extractToken($request); + $expected = config('services.bonus_api.token'); + + if (!$expected || !hash_equals((string) $expected, (string) $provided)) { + return response()->json(['message' => 'Unauthorized'], 401); + } + + return $next($request); + }); + } + + private function extractToken(Request $request): ?string + { + $auth = $request->header('Authorization'); + if ($auth && str_starts_with($auth, 'Bearer ')) { + return substr($auth, 7); + } + return $request->query('api_token'); // fallback: allow ?api_token= + } + + public function index(Request $request) + { + try { + $query = []; + if ($status = $request->query('status')) { + $query['status'] = $status; + } + if ($request->boolean('active_only')) { + $query['active_only'] = 1; + } + $query['per_page'] = min(200, max(1, (int) $request->query('per_page', 50))); + + $res = $this->client->get($request, '/bonuses', $query, retry: true); + if ($res->successful()) { + return response()->json($res->json() ?: []); + } + if ($res->clientError()) return $this->mapClientError($res); + if ($res->serverError()) return $this->mapServiceUnavailable($res); + return $this->mapBadGateway(); + } catch (\Throwable $e) { + return $this->mapBadGateway('API server not reachable'); + } + } + + public function show(Request $request, int $id) + { + try { + $res = $this->client->get($request, "/bonuses/{$id}", [], retry: true); + if ($res->successful()) { + return response()->json($res->json() ?: []); + } + if ($res->clientError()) return $this->mapClientError($res); + if ($res->serverError()) return $this->mapServiceUnavailable($res); + return $this->mapBadGateway(); + } catch (\Throwable $e) { + return $this->mapBadGateway('API server not reachable'); + } + } + + public function store(Request $request) + { + $data = $this->validateData($request); + try { + $res = $this->client->post($request, '/bonuses', $data); + if ($res->successful()) { + return response()->json($res->json() ?: [], 201); + } + if ($res->clientError()) return $this->mapClientError($res); + if ($res->serverError()) return $this->mapServiceUnavailable($res); + return $this->mapBadGateway(); + } catch (\Throwable $e) { + return $this->mapBadGateway('API server not reachable'); + } + } + + public function update(Request $request, int $id) + { + $data = $this->validateData($request, partial: true); + try { + $res = $this->client->patch($request, "/bonuses/{$id}", $data); + if ($res->successful()) { + return response()->json($res->json() ?: []); + } + if ($res->clientError()) return $this->mapClientError($res); + if ($res->serverError()) return $this->mapServiceUnavailable($res); + return $this->mapBadGateway(); + } catch (\Throwable $e) { + return $this->mapBadGateway('API server not reachable'); + } + } + + public function destroy(Request $request, int $id) + { + try { + $res = $this->client->delete($request, "/bonuses/{$id}"); + if ($res->successful()) { + $body = $res->json(); + return response()->json($body ?: ['message' => 'Deleted']); + } + if ($res->clientError()) return $this->mapClientError($res); + if ($res->serverError()) return $this->mapServiceUnavailable($res); + return $this->mapBadGateway(); + } catch (\Throwable $e) { + return $this->mapBadGateway('API server not reachable'); + } + } + + private function validateData(Request $request, bool $partial = false): array + { + $required = $partial ? 'sometimes' : 'required'; + + $rules = [ + 'title' => [$required, 'string', 'max:255'], + 'type' => ['sometimes', 'nullable', 'string', 'max:64'], + 'amount_value' => ['sometimes', 'nullable', 'numeric'], + 'amount_unit' => ['sometimes', 'nullable', Rule::in(['USD','EUR','BTC','ETH','PERCENT','SPINS'])], + 'min_deposit' => ['sometimes', 'nullable', 'numeric', 'min:0'], + 'max_amount' => ['sometimes', 'nullable', 'numeric', 'min:0'], + 'currency' => ['sometimes', 'nullable', 'string', 'max:16'], + 'code' => ['sometimes', 'nullable', 'string', 'max:64'], + 'status' => ['sometimes', 'required', Rule::in(['draft','active','paused','expired'])], + 'starts_at' => ['sometimes', 'nullable', 'date'], + 'expires_at' => ['sometimes', 'nullable', 'date', 'after_or_equal:starts_at'], + 'rules' => ['sometimes', 'nullable', 'array'], + 'description' => ['sometimes', 'nullable', 'string'], + ]; + + $validated = $request->validate($rules); + + // Normalize empty strings to null + foreach (['type','amount_unit','currency','code','description'] as $k) { + if (array_key_exists($k, $validated) && $validated[$k] === '') { + $validated[$k] = null; + } + } + + return $validated; + } +} diff --git a/app/Http/Controllers/BonusesController.php b/app/Http/Controllers/BonusesController.php new file mode 100644 index 0000000..c9d4b22 --- /dev/null +++ b/app/Http/Controllers/BonusesController.php @@ -0,0 +1,70 @@ +client->get($request, '/bonuses/app', [ + 'limit' => min(100, (int) $request->query('limit', 50)), + ], retry: true); + + if ($res->successful()) { + $body = $res->json() ?: []; + $available = $body['available'] ?? $body['bonuses'] ?? []; + $active = $body['active'] ?? []; + $history = $body['history'] ?? []; + + // Normalize available list to fields expected by UI (keep unknowns as-is) + $availableDto = []; + foreach ((array) $available as $b) { + if (!is_array($b)) continue; + $availableDto[] = [ + 'id' => $b['id'] ?? null, + 'title' => $b['title'] ?? null, + 'type' => $b['type'] ?? null, + 'amount_value' => isset($b['amount_value']) ? (float) $b['amount_value'] : (isset($b['amount']) ? (float) $b['amount'] : null), + 'amount_unit' => $b['amount_unit'] ?? $b['unit'] ?? null, + 'min_deposit' => isset($b['min_deposit']) ? (float) $b['min_deposit'] : null, + 'max_amount' => isset($b['max_amount']) ? (float) $b['max_amount'] : null, + 'currency' => $b['currency'] ?? null, + 'code' => $b['code'] ?? null, + 'starts_at' => $b['starts_at'] ?? null, + 'expires_at' => $b['expires_at'] ?? null, + 'description' => $b['description'] ?? null, + ]; + } + + return response()->json([ + 'available' => $availableDto, + 'active' => $active, + 'history' => $history, + 'now' => $body['now'] ?? now()->toIso8601String(), + ], 200); + } + + if ($res->clientError()) return $this->mapClientError($res); + if ($res->serverError()) return $this->mapServiceUnavailable($res); + return $this->mapBadGateway(); + } catch (\Throwable $e) { + return $this->mapBadGateway('API server not reachable'); + } + } +} diff --git a/app/Http/Controllers/ChatController.php b/app/Http/Controllers/ChatController.php new file mode 100644 index 0000000..1236984 --- /dev/null +++ b/app/Http/Controllers/ChatController.php @@ -0,0 +1,249 @@ +user; + $reactionsAgg = []; + + foreach ($m->reactions->groupBy('emoji') as $emoji => $group) { + $reactionsAgg[] = [ + 'emoji' => $emoji, + 'count' => $group->count(), + 'reactedByMe' => $group->contains('user_id', $authId), + ]; + } + + $replyTo = null; + if ($m->reply_to_id && $m->replyTo) { + $replyTo = [ + 'id' => $m->replyTo->id, + 'message' => $m->replyTo->message, + 'user' => [ + 'id' => $m->replyTo->user?->id, + 'username' => $m->replyTo->user?->username, + ], + ]; + } + + return [ + 'id' => $m->id, + 'user_id' => $m->user_id, + 'message' => $m->is_deleted ? null : $m->message, + 'is_deleted' => (bool) $m->is_deleted, + 'deleted_by_user' => $m->is_deleted && $m->deletedByUser ? [ + 'id' => $m->deletedByUser->id, + 'username' => $m->deletedByUser->username, + 'role' => $m->deletedByUser->role ?? 'user', + ] : null, + 'reply_to_id' => $m->reply_to_id, + 'reply_to' => $replyTo, + 'created_at' => $m->created_at?->toIso8601String(), + 'user' => [ + 'id' => $user?->id, + 'username' => $user?->username, + 'avatar_url' => $user?->avatar_url ?? $user?->avatar ?? null, + 'role' => $user?->role ?? 'user', + 'vip_level' => (int) ($user?->vip_level ?? 0), + 'clan_tag' => $user?->clan_tag ?? null, + ], + 'reactions_agg' => $reactionsAgg, + ]; + } + + /** + * List recent chat messages from local DB. + */ + public function index(Request $request) + { + $limit = min(max((int) $request->query('limit', 50), 1), 200); + $afterId = $request->query('after_id'); + $authId = (int) optional($request->user())->id; + + $query = ChatMessage::with([ + 'user:id,username,avatar,avatar_url,role,vip_level,clan_tag', + 'reactions', + 'replyTo:id,message,user_id', + 'replyTo.user:id,username', + 'deletedByUser:id,username,role', + ])->orderBy('id', 'asc'); + + if ($afterId !== null) { + $query->where('id', '>', (int) $afterId); + } else { + // Return last N messages + $query = ChatMessage::with([ + 'user:id,username,avatar,avatar_url,role,vip_level,clan_tag', + 'reactions', + 'replyTo:id,message,user_id', + 'replyTo.user:id,username', + ])->orderBy('id', 'desc')->limit($limit); + + $messages = $query->get()->reverse()->values(); + $formatted = $messages->map(fn ($m) => $this->formatMessage($m, $authId))->values()->all(); + $lastId = $messages->last()?->id; + + return response()->json(['data' => $formatted, 'last_id' => $lastId]); + } + + $messages = $query->limit($limit)->get(); + $formatted = $messages->map(fn ($m) => $this->formatMessage($m, $authId))->values()->all(); + $lastId = $messages->last()?->id; + + return response()->json(['data' => $formatted, 'last_id' => $lastId]); + } + + /** + * Store a new chat message in local DB. + */ + public function store(Request $request) + { + $user = $request->user(); + if (!$user) return response()->json(['message' => 'Unauthenticated'], 401); + + $validated = $request->validate([ + 'message' => ['required', 'string', 'min:1', 'max:300'], + 'reply_to_id' => ['nullable', 'integer', 'exists:chat_messages,id'], + ]); + + // Check local chat ban + $chatBan = \App\Models\UserRestriction::where('user_id', $user->id) + ->where('type', 'chat_ban') + ->where('active', true) + ->where(fn($q) => $q->whereNull('ends_at')->orWhere('ends_at', '>', now())) + ->first(); + if ($chatBan) { + return response()->json([ + 'message' => 'Du bist vom Chat gebannt.', + 'type' => 'chat_ban', + 'ends_at' => $chatBan->ends_at?->toIso8601String(), + ], 403); + } + + $msg = ChatMessage::create([ + 'user_id' => $user->id, + 'message' => $validated['message'], + 'reply_to_id' => $validated['reply_to_id'] ?? null, + ]); + + $msg->load([ + 'user:id,username,avatar,avatar_url,role,vip_level,clan_tag', + 'reactions', + 'replyTo:id,message,user_id', + 'replyTo.user:id,username', + 'deletedByUser:id,username,role', + ]); + + return response()->json(['data' => $this->formatMessage($msg, (int) $user->id)], 201); + } + + /** + * Soft-delete a chat message (admin/mod only). + */ + public function destroy(Request $request, int $id) + { + $user = $request->user(); + if (!$user) return response()->json(['message' => 'Unauthenticated'], 401); + + $role = strtolower((string) $user->role); + if (!in_array($role, ['admin', 'moderator', 'mod'])) { + return response()->json(['message' => 'Forbidden'], 403); + } + + $msg = ChatMessage::findOrFail($id); + $msg->update(['is_deleted' => true, 'deleted_by' => $user->id]); + + $msg->load([ + 'user:id,username,avatar,avatar_url,role,vip_level,clan_tag', + 'reactions', + 'replyTo:id,message,user_id', + 'replyTo.user:id,username', + 'deletedByUser:id,username,role', + ]); + + return response()->json(['data' => $this->formatMessage($msg, (int) $user->id)]); + } + + /** + * Toggle a reaction on a message. + */ + public function react(Request $request, int $id) + { + $user = $request->user(); + if (!$user) return response()->json(['message' => 'Unauthenticated'], 401); + + $validated = $request->validate([ + 'emoji' => ['required', 'string', 'max:8'], + ]); + + $msg = ChatMessage::findOrFail($id); + + $existing = ChatMessageReaction::where([ + 'message_id' => $msg->id, + 'user_id' => $user->id, + 'emoji' => $validated['emoji'], + ])->first(); + + if ($existing) { + $existing->delete(); + } else { + ChatMessageReaction::create([ + 'message_id' => $msg->id, + 'user_id' => $user->id, + 'emoji' => $validated['emoji'], + ]); + } + + $msg->load([ + 'user:id,username,avatar,avatar_url,role,vip_level,clan_tag', + 'reactions', + 'replyTo:id,message,user_id', + 'replyTo.user:id,username', + 'deletedByUser:id,username,role', + ]); + + return response()->json(['data' => $this->formatMessage($msg, (int) $user->id)]); + } + + /** + * Report a chat message. + */ + public function report(Request $request, int $id) + { + $user = $request->user(); + if (!$user) return response()->json(['message' => 'Unauthenticated'], 401); + + $validated = $request->validate([ + 'message_text' => ['required', 'string', 'max:500'], + 'sender_id' => ['nullable', 'integer'], + 'sender_username' => ['nullable', 'string', 'max:255'], + 'reason' => ['nullable', 'string', 'max:255'], + 'context_messages' => ['nullable', 'array'], + 'context_messages.*.id' => ['nullable'], + 'context_messages.*.message' => ['nullable', 'string', 'max:500'], + 'context_messages.*.user' => ['nullable', 'array'], + 'context_messages.*.created_at' => ['nullable', 'string'], + ]); + + ChatMessageReport::create([ + 'reporter_id' => $user->id, + 'message_id' => (string) $id, + 'message_text' => $validated['message_text'], + 'sender_id' => $validated['sender_id'] ?? null, + 'sender_username' => $validated['sender_username'] ?? null, + 'reason' => $validated['reason'] ?? null, + 'context_messages' => $validated['context_messages'] ?? null, + ]); + + return response()->json(['message' => 'Reported.'], 201); + } +} diff --git a/app/Http/Controllers/Concerns/ProxiesBackend.php b/app/Http/Controllers/Concerns/ProxiesBackend.php new file mode 100644 index 0000000..6e78a86 --- /dev/null +++ b/app/Http/Controllers/Concerns/ProxiesBackend.php @@ -0,0 +1,34 @@ +json(), 'message') ?? 'Invalid request'; + return response()->json([ + 'error' => 'client_error', + 'message' => $msg, + ], $res->status()); + } + + protected function mapServiceUnavailable(Response $res) + { + $msg = data_get($res->json(), 'message') ?? 'Internal server error'; + return response()->json([ + 'error' => 'service_unavailable', + 'message' => $msg, + ], 503); + } + + protected function mapBadGateway(string $msg = 'API server not reachable') + { + return response()->json([ + 'error' => 'bad_gateway', + 'message' => $msg, + ], 502); + } +} diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php new file mode 100644 index 0000000..8677cd5 --- /dev/null +++ b/app/Http/Controllers/Controller.php @@ -0,0 +1,8 @@ +json($this->deposits->currenciesForUser(), 200); + } + + /** + * POST /wallet/deposits — start a deposit via NOWPayments + * Body: { currency: string, amount: number|string } + */ + public function create(Request $request) + { + $user = Auth::user(); + abort_unless($user, 403); + + $data = $request->validate([ + 'currency' => ['required','string','max:32'], + 'amount' => ['required','numeric','min:0.00000001'], // USD/BTX in MVP + ]); + + $res = $this->deposits->startDeposit($user, strtoupper($data['currency']), (float) $data['amount']); + if (!$res || isset($res['error'])) { + $err = $res['error'] ?? 'unknown_error'; + $payload = ['message' => $err]; + if ($err === 'amount_out_of_bounds') { + $payload['min_usd'] = $res['min_usd'] ?? null; + $payload['max_usd'] = $res['max_usd'] ?? null; + } + return response()->json($payload, 422); + } + + return response()->json($res, 201); + } + + /** + * GET /wallet/deposits/history — get user's payment history + */ + public function history(Request $request) + { + $user = Auth::user(); + abort_unless($user, 403); + + $limit = min(50, max(1, (int) $request->query('limit', 10))); + $history = $this->deposits->getUserHistory($user, $limit); + + return response()->json(['data' => $history], 200); + } + + /** + * DELETE /wallet/deposits/{order_id} — cancel a pending deposit + */ + public function cancel(string $orderId) + { + $user = Auth::user(); + abort_unless($user, 403); + + $cp = CryptoPayment::query() + ->where('user_id', $user->id) + ->where('order_id', $orderId) + ->first(); + + if (!$cp) { + return response()->json(['message' => 'Not found'], 404); + } + + // Only allow cancellation of pending deposits + if (!in_array($cp->status, ['waiting', 'new', 'confirming'])) { + return response()->json(['message' => 'This deposit can no longer be canceled.'], 422); + } + + $cp->status = 'canceled'; + $cp->save(); + + return response()->json(['message' => 'Deposit canceled.'], 200); + } + + /** + * GET /wallet/deposits/{order_id} — optional polling endpoint to see current status + */ + public function show(string $orderId) + { + $user = Auth::user(); + abort_unless($user, 403); + + $cp = CryptoPayment::query() + ->where('user_id', $user->id) + ->where('order_id', $orderId) + ->first(); + + if (!$cp) { + return response()->json(['message' => 'Not found'], 404); + } + + return response()->json([ + 'order_id' => $cp->order_id, + 'invoice_id' => $cp->invoice_id, + 'payment_id' => $cp->payment_id, + 'status' => $cp->status, + 'pay_currency' => $cp->pay_currency, + 'price_amount' => (string) $cp->price_amount, + 'credited_btx' => $cp->credited_btx !== null ? (string) $cp->credited_btx : null, + 'credited_at' => $cp->credited_at?->toIso8601String(), + ], 200); + } +} diff --git a/app/Http/Controllers/DirectMessageController.php b/app/Http/Controllers/DirectMessageController.php new file mode 100644 index 0000000..1ceb072 --- /dev/null +++ b/app/Http/Controllers/DirectMessageController.php @@ -0,0 +1,198 @@ + $u->id, + 'username' => $u->username, + 'name' => $u->name, + 'avatar' => $u->avatar, + 'avatar_url' => $u->avatar_url, + ]; + } + + private function formatMessage(DirectMessage $m): array + { + return [ + 'id' => $m->id, + 'message' => $m->is_deleted ? null : $m->message, + 'sender_id' => $m->sender_id, + 'is_deleted' => $m->is_deleted, + 'is_read' => $m->is_read, + 'created_at' => $m->created_at, + 'user' => $m->sender ? $this->formatUser($m->sender) : null, + 'reply_to' => $m->replyTo ? [ + 'id' => $m->replyTo->id, + 'message' => $m->replyTo->is_deleted ? null : $m->replyTo->message, + 'sender_id' => $m->replyTo->sender_id, + ] : null, + ]; + } + + // GET /api/dm/conversations + public function conversations() + { + $me = Auth::id(); + + $sent = DirectMessage::where('sender_id', $me)->select('receiver_id as partner_id'); + $received = DirectMessage::where('receiver_id', $me)->select('sender_id as partner_id'); + + $partnerIds = $sent->union($received)->pluck('partner_id')->unique()->values(); + + $conversations = $partnerIds->map(function ($pid) use ($me) { + $partner = User::select('id', 'username', 'name', 'avatar', 'avatar_url')->find($pid); + if (!$partner) return null; + + $lastMsg = DirectMessage::where(function ($q) use ($me, $pid) { + $q->where('sender_id', $me)->where('receiver_id', $pid); + })->orWhere(function ($q) use ($me, $pid) { + $q->where('sender_id', $pid)->where('receiver_id', $me); + })->orderByDesc('created_at')->first(); + + $unread = DirectMessage::where('sender_id', $pid) + ->where('receiver_id', $me) + ->where('is_read', false) + ->count(); + + return [ + 'partner' => $this->formatUser($partner), + 'last_message' => $lastMsg ? [ + 'id' => $lastMsg->id, + 'message' => $lastMsg->is_deleted ? null : $lastMsg->message, + 'sender_id' => $lastMsg->sender_id, + 'created_at' => $lastMsg->created_at, + ] : null, + 'unread_count' => $unread, + ]; + })->filter()->sortByDesc(fn ($c) => optional($c['last_message'])['created_at'])->values(); + + return response()->json(['data' => $conversations]); + } + + // GET /api/dm/{userId} + public function messages($userId) + { + $me = Auth::id(); + + $msgs = DirectMessage::where(function ($q) use ($me, $userId) { + $q->where('sender_id', $me)->where('receiver_id', $userId); + })->orWhere(function ($q) use ($me, $userId) { + $q->where('sender_id', $userId)->where('receiver_id', $me); + }) + ->with(['sender:id,username,name,avatar,avatar_url', 'replyTo']) + ->orderBy('created_at') + ->limit(100) + ->get() + ->map(fn ($m) => $this->formatMessage($m)); + + // Mark as read + DirectMessage::where('sender_id', $userId) + ->where('receiver_id', $me) + ->where('is_read', false) + ->update(['is_read' => true]); + + return response()->json(['data' => $msgs]); + } + + // POST /api/dm/{userId} + public function send(Request $request, $userId) + { + $me = Auth::id(); + $request->validate([ + 'message' => 'required|string|max:1000', + 'reply_to_id' => 'nullable|integer|exists:direct_messages,id', + ]); + + User::findOrFail($userId); + + $areFriends = Friend::where(function ($q) use ($me, $userId) { + $q->where('user_id', $me)->where('friend_id', $userId); + })->orWhere(function ($q) use ($me, $userId) { + $q->where('user_id', $userId)->where('friend_id', $me); + })->where('status', 'accepted')->exists(); + + if (!$areFriends) { + return response()->json(['error' => 'You must be friends to send messages.'], 403); + } + + $msg = DirectMessage::create([ + 'sender_id' => $me, + 'receiver_id' => $userId, + 'message' => $request->message, + 'reply_to_id' => $request->reply_to_id, + ]); + + $msg->load(['sender:id,username,name,avatar,avatar_url', 'replyTo']); + + return response()->json(['data' => $this->formatMessage($msg)], 201); + } + + // POST /api/dm/messages/{id}/report + public function report(Request $request, $messageId) + { + $me = Auth::id(); + $request->validate([ + 'reason' => 'required|string|max:64', + 'details' => 'nullable|string|max:500', + ]); + + $msg = DirectMessage::where(function ($q) use ($me) { + $q->where('sender_id', $me)->orWhere('receiver_id', $me); + })->findOrFail($messageId); + + DirectMessageReport::firstOrCreate( + ['reporter_id' => $me, 'message_id' => $msg->id], + ['reason' => $request->reason, 'details' => $request->details] + ); + + return response()->json(['ok' => true]); + } + + // GET /api/friends + public function friends() + { + $me = Auth::id(); + + $friends = Friend::where(function ($q) use ($me) { + $q->where('user_id', $me)->orWhere('friend_id', $me); + })->where('status', 'accepted') + ->with(['user:id,username,name,avatar,avatar_url', 'friend:id,username,name,avatar,avatar_url']) + ->get() + ->map(function ($f) use ($me) { + $partner = $f->user_id === $me ? $f->friend : $f->user; + return $partner ? $this->formatUser($partner) : null; + })->filter()->values(); + + return response()->json(['data' => $friends]); + } + + // GET /api/friends/requests + public function friendRequests() + { + $me = Auth::id(); + + $requests = Friend::where('friend_id', $me) + ->where('status', 'pending') + ->with('user:id,username,name,avatar,avatar_url') + ->orderByDesc('created_at') + ->get() + ->map(fn ($f) => [ + 'id' => $f->id, + 'from' => $f->user ? $this->formatUser($f->user) : null, + 'created_at' => $f->created_at, + ])->filter(fn ($r) => $r['from'] !== null)->values(); + + return response()->json(['data' => $requests]); + } +} diff --git a/app/Http/Controllers/EmbedController.php b/app/Http/Controllers/EmbedController.php new file mode 100644 index 0000000..815b25e --- /dev/null +++ b/app/Http/Controllers/EmbedController.php @@ -0,0 +1,130 @@ +query('mode'); + $normalized = null; + if (is_string($rawMode)) { + $val = strtolower(trim($rawMode)); + if (in_array($val, ['demo', 'real'], true)) { + $normalized = $val; + } + } + + // If mode is missing or invalid → redirect to add/fix it (default demo) + if ($normalized === null) { + $normalized = 'demo'; + $query = array_merge($request->query(), ['mode' => $normalized]); + $url = $request->url() . (empty($query) ? '' : ('?' . http_build_query($query))); + return redirect()->to($url, 302); + } + + // Localhost provider integration for specific games + $slugKey = strtolower($slug); + $supported = [ + 'dice' => 'dice', + 'plinko' => 'plinko', + 'mines' => 'mines', + ]; + $base = rtrim((string) config('games.providers.local.base_url', 'http://localhost:3001/games'), '/'); + + if (array_key_exists($slugKey, $supported)) { + $gamePath = $supported[$slugKey]; + $src = $base . '/' . $gamePath . '/index.html?mode=' . urlencode($normalized); + + $title = htmlspecialchars("Local Provider • {$slug} ({$normalized})", ENT_QUOTES, 'UTF-8'); + $srcAttr = htmlspecialchars($src, ENT_QUOTES, 'UTF-8'); + $slugJson = json_encode($slug, JSON_UNESCAPED_SLASHES); + $modeJson = json_encode($normalized); + $srcJson = json_encode($src, JSON_UNESCAPED_SLASHES); + $html = << + + + + + {$title} + + + + +
+
Localhost Provider
+
Slug: {$slugKey}
+
Mode: {$normalized}
+
+
src: {$srcAttr}
+
+ + + + + HTML; + + return new Response($html, 200, ['Content-Type' => 'text/html; charset=utf-8']); + } + + // Fallback minimal HTML placeholder for unknown slugs + $slugJson = json_encode($slug, JSON_UNESCAPED_SLASHES); + $modeJson = json_encode($normalized); + $html = << + + + + + Game Embed • {$slug} ({$normalized}) + + + +
+
+

Embedded Game (Fallback)

+

Unbekannter Slug: {$slug}

+

Mode: {$normalized}

+

Unterstützte lokale Spiele: dice, plinko, mines. Bitte URL prüfen.

+
+
+ + + + HTML; + + return new Response($html, 200, ['Content-Type' => 'text/html; charset=utf-8']); + } +} diff --git a/app/Http/Controllers/FavoriteController.php b/app/Http/Controllers/FavoriteController.php new file mode 100644 index 0000000..1454198 --- /dev/null +++ b/app/Http/Controllers/FavoriteController.php @@ -0,0 +1,68 @@ +id) + ->orderByDesc('created_at') + ->get(['game_slug', 'game_name', 'game_image', 'game_provider', 'created_at']); + + return response()->json(['data' => $favorites]); + } + + /** + * POST /api/favorites — add a game to favorites + * Body: { slug, name?, image?, provider? } + */ + public function store(Request $request) + { + $user = Auth::user(); + abort_unless($user, 401); + + $data = $request->validate([ + 'slug' => ['required', 'string', 'max:128'], + 'name' => ['nullable', 'string', 'max:255'], + 'image' => ['nullable', 'string', 'max:512'], + 'provider' => ['nullable', 'string', 'max:100'], + ]); + + $fav = UserFavorite::firstOrCreate( + ['user_id' => $user->id, 'game_slug' => $data['slug']], + [ + 'game_name' => $data['name'] ?? null, + 'game_image' => $data['image'] ?? null, + 'game_provider' => $data['provider'] ?? null, + ] + ); + + return response()->json(['data' => $fav], 201); + } + + /** + * DELETE /api/favorites/{slug} — remove from favorites + */ + public function destroy(Request $request, string $slug) + { + $user = Auth::user(); + abort_unless($user, 401); + + UserFavorite::where('user_id', $user->id) + ->where('game_slug', $slug) + ->delete(); + + return response()->json(['success' => true]); + } +} diff --git a/app/Http/Controllers/FeedbackController.php b/app/Http/Controllers/FeedbackController.php new file mode 100644 index 0000000..73aa20d --- /dev/null +++ b/app/Http/Controllers/FeedbackController.php @@ -0,0 +1,105 @@ +validate([ + 'category' => 'required|string|in:general,ux,mobile,feature,complaint', + 'overall_rating' => 'nullable|integer|min:1|max:5', + 'ux_rating' => 'nullable|integer|min:1|max:5', + 'comfort_rating' => 'nullable|integer|min:1|max:5', + 'mobile_rating' => 'nullable|integer|min:1|max:5', + 'uses_mobile' => 'nullable|boolean', + 'nps_score' => 'nullable|integer|min:1|max:10', + 'ux_comment' => 'nullable|string|max:2000', + 'mobile_comment' => 'nullable|string|max:2000', + 'feature_request' => 'nullable|string|max:2000', + 'improvements' => 'nullable|string|max:2000', + 'general_comment' => 'nullable|string|max:2000', + ]); + + $data['user_id'] = auth()->id(); + + UserFeedback::create($data); + + return back()->with('success', 'Danke für dein Feedback!'); + } + + // ── Admin ────────────────────────────────────────────────────────────── + + public function adminIndex(Request $request) + { + $status = $request->input('status', 'new'); + $search = trim($request->input('search', '')); + + $query = UserFeedback::with('user') + ->orderByDesc('created_at'); + + if ($status && $status !== 'all') { + $query->where('status', $status); + } + + if ($search !== '') { + if (is_numeric($search)) { + $query->where('id', (int) $search); + } else { + $query->whereHas('user', function ($q) use ($search) { + $q->where('username', 'like', '%' . $search . '%'); + }); + } + } + + $feedbacks = $query->paginate(25)->withQueryString(); + + $stats = [ + 'total' => UserFeedback::count(), + 'new' => UserFeedback::where('status', 'new')->count(), + 'read' => UserFeedback::where('status', 'read')->count(), + ]; + + return Inertia::render('Admin/Feedback', [ + 'feedbacks' => $feedbacks, + 'filters' => ['status' => $status, 'search' => $search], + 'stats' => $stats, + ]); + } + + public function adminShow(int $id) + { + $feedback = UserFeedback::with('user')->findOrFail($id); + + if ($feedback->status === 'new') { + $feedback->update(['status' => 'read']); + } + + return Inertia::render('Admin/FeedbackShow', [ + 'feedback' => $feedback, + ]); + } + + public function adminUpdate(Request $request, int $id) + { + $feedback = UserFeedback::findOrFail($id); + + $data = $request->validate([ + 'status' => 'nullable|in:new,read', + 'admin_note' => 'nullable|string|max:2000', + ]); + + $feedback->update(array_filter($data, fn ($v) => $v !== null)); + + return back()->with('success', 'Gespeichert.'); + } +} diff --git a/app/Http/Controllers/GuildActionController.php b/app/Http/Controllers/GuildActionController.php new file mode 100644 index 0000000..5f0fd62 --- /dev/null +++ b/app/Http/Controllers/GuildActionController.php @@ -0,0 +1,220 @@ +exists()); + return $code; + } + + public function store(Request $request) + { + $user = $request->user(); + if (!$user) return response()->json(['message' => 'Unauthenticated'], 401); + + if (GuildMember::where('user_id', $user->id)->exists()) { + return response()->json(['message' => 'Du bist bereits in einer Gilde.'], 422); + } + + $validated = $request->validate([ + 'name' => ['required', 'string', 'min:3', 'max:40', 'unique:guilds,name'], + 'tag' => ['required', 'string', 'min:2', 'max:6', 'regex:/^[A-Za-z0-9]{2,6}$/'], + 'description' => ['nullable', 'string', 'max:500'], + 'logo' => ['nullable', 'file', 'image', 'max:2048'], + ]); + + $tag = strtoupper($validated['tag']); + if (Guild::where('tag', $tag)->exists()) { + return response()->json(['errors' => ['tag' => ['Dieses Tag ist bereits vergeben.']]], 422); + } + + $logoUrl = null; + if ($request->hasFile('logo') && $request->file('logo')->isValid()) { + $path = $request->file('logo')->store('guild-logos', 'public'); + $logoUrl = Storage::url($path); + } + + $guild = Guild::create([ + 'name' => $validated['name'], + 'tag' => $tag, + 'logo_url' => $logoUrl, + 'description' => isset($validated['description']) ? strip_tags($validated['description']) : null, + 'owner_id' => $user->id, + 'invite_code' => $this->generateInviteCode(), + 'points' => 0, + 'members_count' => 1, + ]); + + GuildMember::create([ + 'guild_id' => $guild->id, + 'user_id' => $user->id, + 'role' => 'owner', + 'joined_at' => now(), + ]); + + GuildMessage::create([ + 'guild_id' => $guild->id, + 'user_id' => $user->id, + 'type' => 'system', + 'message' => 'hat die Gilde gegründet 🎉', + ]); + + return response()->json(['success' => true, 'data' => $guild], 201); + } + + public function join(Request $request) + { + $user = $request->user(); + if (!$user) return response()->json(['message' => 'Unauthenticated'], 401); + + if (GuildMember::where('user_id', $user->id)->exists()) { + return response()->json(['message' => 'Du bist bereits in einer Gilde.'], 422); + } + + $data = $request->validate([ + 'invite_code' => ['required', 'string', 'min:4', 'max:16'], + ]); + + $guild = Guild::where('invite_code', strtoupper($data['invite_code']))->first(); + if (!$guild) { + return response()->json(['message' => 'Ungültiger Einladungscode.'], 422); + } + + GuildMember::create([ + 'guild_id' => $guild->id, + 'user_id' => $user->id, + 'role' => 'member', + 'joined_at' => now(), + ]); + + $guild->increment('members_count'); + + GuildMessage::create([ + 'guild_id' => $guild->id, + 'user_id' => $user->id, + 'type' => 'system', + 'message' => 'ist der Gilde beigetreten 👋', + ]); + + return response()->json(['success' => true], 200); + } + + public function leave(Request $request) + { + $user = $request->user(); + if (!$user) return response()->json(['message' => 'Unauthenticated'], 401); + + $member = GuildMember::where('user_id', $user->id)->first(); + if (!$member) return response()->json(['message' => 'Du bist in keiner Gilde.'], 422); + + // Owner cannot leave — must disband or transfer first + if ($member->role === 'owner') { + return response()->json(['message' => 'Als Owner kannst du die Gilde nicht verlassen. Lösche die Gilde oder übertrage den Besitz.'], 422); + } + + $guildId = $member->guild_id; + $member->delete(); + Guild::where('id', $guildId)->decrement('members_count'); + + return response()->json(['success' => true], 200); + } + + public function kick(Request $request) + { + $user = $request->user(); + if (!$user) return response()->json(['message' => 'Unauthenticated'], 401); + + $data = $request->validate([ + 'user_id' => ['required', 'integer'], + ]); + + $actor = GuildMember::where('user_id', $user->id)->first(); + if (!$actor || !in_array($actor->role, ['owner', 'officer'])) { + return response()->json(['message' => 'Keine Berechtigung.'], 403); + } + + $target = GuildMember::where('user_id', $data['user_id']) + ->where('guild_id', $actor->guild_id) + ->first(); + + if (!$target) return response()->json(['message' => 'Mitglied nicht gefunden.'], 404); + if ($target->role === 'owner') return response()->json(['message' => 'Den Owner kannst du nicht kicken.'], 422); + + $target->delete(); + Guild::where('id', $actor->guild_id)->decrement('members_count'); + + return response()->json(['success' => true], 200); + } + + public function update(Request $request) + { + $user = $request->user(); + if (!$user) return response()->json(['message' => 'Unauthenticated'], 401); + + $member = GuildMember::where('user_id', $user->id)->first(); + if (!$member || !in_array($member->role, ['owner', 'officer'])) { + return response()->json(['message' => 'Keine Berechtigung.'], 403); + } + + $validated = $request->validate([ + 'description' => ['sometimes', 'nullable', 'string', 'max:500'], + 'name' => ['sometimes', 'string', 'min:3', 'max:40'], + 'tag' => ['sometimes', 'string', 'min:2', 'max:6', 'regex:/^[A-Za-z0-9]{2,6}$/'], + 'logo' => ['sometimes', 'nullable', 'file', 'image', 'max:2048'], + ]); + + $guild = Guild::findOrFail($member->guild_id); + + if ($request->hasFile('logo') && $request->file('logo')->isValid()) { + // Delete old logo if stored locally + if ($guild->logo_url && str_contains($guild->logo_url, '/storage/')) { + $oldPath = str_replace('/storage/', 'public/', $guild->logo_url); + Storage::delete($oldPath); + } + $path = $request->file('logo')->store('guild-logos', 'public'); + $guild->logo_url = Storage::url($path); + } + + if (isset($validated['description'])) { + $guild->description = strip_tags($validated['description']); + } + if (isset($validated['name'])) { + $guild->name = $validated['name']; + } + if (isset($validated['tag'])) { + $guild->tag = strtoupper($validated['tag']); + } + + $guild->save(); + + return response()->json(['success' => true, 'data' => $guild], 200); + } + + public function regenerateInvite(Request $request) + { + $user = $request->user(); + if (!$user) return response()->json(['message' => 'Unauthenticated'], 401); + + $member = GuildMember::where('user_id', $user->id)->first(); + if (!$member || !in_array($member->role, ['owner', 'officer'])) { + return response()->json(['message' => 'Keine Berechtigung.'], 403); + } + + $guild = Guild::findOrFail($member->guild_id); + $guild->update(['invite_code' => $this->generateInviteCode()]); + + return response()->json(['invite_code' => $guild->invite_code], 200); + } +} diff --git a/app/Http/Controllers/GuildChatController.php b/app/Http/Controllers/GuildChatController.php new file mode 100644 index 0000000..b37cdfb --- /dev/null +++ b/app/Http/Controllers/GuildChatController.php @@ -0,0 +1,127 @@ + $m->id, + 'type' => $m->type ?? 'message', + 'message' => $m->is_deleted ? null : $m->message, + 'user_id' => $m->user_id, + 'is_deleted' => $m->is_deleted, + 'created_at' => $m->created_at, + 'user' => $m->user ? [ + 'id' => $m->user->id, + 'username' => $m->user->username, + 'avatar' => $m->user->avatar ?? $m->user->avatar_url, + ] : null, + 'reply_to' => $m->replyTo ? [ + 'id' => $m->replyTo->id, + 'message' => $m->replyTo->is_deleted ? null : $m->replyTo->message, + 'user_id' => $m->replyTo->user_id, + ] : null, + ]; + } + + // GET /api/guild-chat/me + public function myGuild() + { + $me = Auth::id(); + $member = GuildMember::where('user_id', $me)->with('guild')->first(); + + if (!$member || !$member->guild) { + return response()->json(['data' => null]); + } + + $guild = $member->guild; + + return response()->json([ + 'data' => [ + 'id' => $guild->id, + 'name' => $guild->name, + 'tag' => $guild->tag, + 'logo_url' => $guild->logo_url, + ], + ]); + } + + // GET /api/guild-chat/{guildId} + public function messages($guildId) + { + $me = Auth::id(); + + $isMember = GuildMember::where('guild_id', $guildId)->where('user_id', $me)->exists(); + if (!$isMember) { + return response()->json(['error' => 'Not a guild member.'], 403); + } + + $msgs = GuildMessage::where('guild_id', $guildId) + ->with(['user:id,username,avatar,avatar_url', 'replyTo']) + ->orderBy('created_at') + ->limit(100) + ->get() + ->map(fn ($m) => $this->formatMessage($m)); + + return response()->json(['data' => $msgs]); + } + + // POST /api/guild-chat/{guildId} + public function send(Request $request, $guildId) + { + $me = Auth::id(); + + $isMember = GuildMember::where('guild_id', $guildId)->where('user_id', $me)->exists(); + if (!$isMember) { + return response()->json(['error' => 'Not a guild member.'], 403); + } + + $request->validate([ + 'message' => 'required|string|max:1000', + 'reply_to_id' => 'nullable|integer|exists:guild_messages,id', + ]); + + $msg = GuildMessage::create([ + 'guild_id' => $guildId, + 'user_id' => $me, + 'type' => 'message', + 'message' => $request->message, + 'reply_to_id' => $request->reply_to_id, + ]); + + $msg->load(['user:id,username,avatar,avatar_url', 'replyTo']); + + return response()->json(['data' => $this->formatMessage($msg)], 201); + } + + // GET /api/guild-chat/{guildId}/members + public function members($guildId) + { + $me = Auth::id(); + + $isMember = GuildMember::where('guild_id', $guildId)->where('user_id', $me)->exists(); + if (!$isMember) { + return response()->json(['error' => 'Not a guild member.'], 403); + } + + $members = GuildMember::where('guild_id', $guildId) + ->with('user:id,username,avatar,avatar_url') + ->orderByRaw("FIELD(role, 'owner', 'officer', 'member')") + ->get() + ->map(fn ($m) => [ + 'id' => $m->user->id, + 'username' => $m->user->username, + 'avatar' => $m->user->avatar ?? $m->user->avatar_url, + 'role' => $m->role, + ]); + + return response()->json(['data' => $members]); + } +} diff --git a/app/Http/Controllers/GuildController.php b/app/Http/Controllers/GuildController.php new file mode 100644 index 0000000..4415837 --- /dev/null +++ b/app/Http/Controllers/GuildController.php @@ -0,0 +1,101 @@ +user(); + + if (!$user) { + return Inertia::render('guilds/Index', [ + 'guild' => null, + 'myRole' => null, + 'canManage' => false, + 'invite' => null, + 'topPlayers' => [], + ]); + } + + $member = GuildMember::where('user_id', $user->id)->first(); + + if (!$member) { + return Inertia::render('guilds/Index', [ + 'guild' => null, + 'myRole' => null, + 'canManage' => false, + 'invite' => null, + 'topPlayers' => [], + ]); + } + + $guild = Guild::with(['owner:id,username,avatar_url', 'members:id,username,avatar_url']) + ->findOrFail($member->guild_id); + + $myRole = $member->role; + $canManage = in_array($myRole, ['owner', 'officer']); + + // Build member list with pivot data + $memberRows = GuildMember::where('guild_id', $guild->id) + ->with('user:id,username,avatar_url') + ->get() + ->map(fn ($m) => [ + 'id' => $m->user_id, + 'username' => $m->user?->username, + 'avatar_url'=> $m->user?->avatar_url, + 'role' => $m->role, + 'joined_at' => $m->joined_at?->toIso8601String(), + 'wagered' => (float) ($m->wagered ?? 0), + ]); + + $guildData = [ + 'id' => $guild->id, + 'name' => $guild->name, + 'tag' => $guild->tag, + 'logo_url' => $guild->logo_url, + 'description' => $guild->description, + 'points' => $guild->points, + 'members_count'=> $guild->members_count, + 'owner' => [ + 'id' => $guild->owner->id, + 'username' => $guild->owner->username, + 'avatar_url'=> $guild->owner->avatar_url, + ], + 'members' => $memberRows, + ]; + + return Inertia::render('guilds/Index', [ + 'guild' => $guildData, + 'myRole' => $myRole, + 'canManage' => $canManage, + 'invite' => $canManage ? $guild->invite_code : null, + 'topPlayers' => [], + ]); + } + + public function top(Request $request) + { + $guilds = Guild::with('owner:id,username') + ->orderByDesc('points') + ->orderByDesc('members_count') + ->limit(50) + ->get() + ->map(fn ($g) => [ + 'id' => $g->id, + 'name' => $g->name, + 'tag' => $g->tag, + 'logo_url' => $g->logo_url, + 'points' => $g->points, + 'members_count'=> $g->members_count, + 'owner' => ['id' => $g->owner?->id, 'username' => $g->owner?->username], + ]); + + return Inertia::render('guilds/Top', ['guilds' => $guilds]); + } +} diff --git a/app/Http/Controllers/LocaleController.php b/app/Http/Controllers/LocaleController.php new file mode 100644 index 0000000..7948ffe --- /dev/null +++ b/app/Http/Controllers/LocaleController.php @@ -0,0 +1,48 @@ + */ + private array $available = [ + 'en','de','es','pt_BR','tr','pl', + 'fr','it','ru','uk','vi','id','zh_CN','ja','ko','sv','no','fi','nl', + ]; + + public function set(Request $request) + { + $data = $request->validate([ + 'locale' => ['required','string','max:8'], + ]); + $code = $this->normalize($data['locale']); + if (!in_array($code, $this->available, true)) { + return response()->json(['message' => 'Unsupported locale.'], 422); + } + + // Persist to session and cookie (1 year) + $request->session()->put('locale', $code); + Cookie::queue(cookie('locale', $code, 60 * 24 * 365)); + + // Update user preference if logged in (no local DB writes in gateway) + if ($user = $request->user()) { + if (($user->preferred_locale ?? null) !== $code) { + // Defer persistence to external API if needed; here we only keep session/cookie. + // Optionally enqueue an event to sync upstream. + } + } + + return response()->noContent(); + } + + private function normalize(string $code): string + { + $code = str_replace([' ', '-'], ['','_'], trim($code)); + if (strtolower($code) === 'pt_br') return 'pt_BR'; + if (strtolower($code) === 'zh_cn') return 'zh_CN'; + return strtolower($code); + } +} diff --git a/app/Http/Controllers/NowPaymentsWebhookController.php b/app/Http/Controllers/NowPaymentsWebhookController.php new file mode 100644 index 0000000..907412e --- /dev/null +++ b/app/Http/Controllers/NowPaymentsWebhookController.php @@ -0,0 +1,27 @@ +deposits->handleIpn($request); + return response()->json([ + 'ok' => (bool) ($out['ok'] ?? false), + 'message' => $out['message'] ?? 'ok', + ], (int) ($out['status'] ?? 200)); + } +} diff --git a/app/Http/Controllers/OperatorController.php b/app/Http/Controllers/OperatorController.php new file mode 100644 index 0000000..0da9945 --- /dev/null +++ b/app/Http/Controllers/OperatorController.php @@ -0,0 +1,112 @@ +map(fn ($g) => array_merge($g, [ + 'thumbnail_url' => "{$baseUrl}/assets/games/{$g['slug']}.png", + 'launch_path' => "/{$g['slug']}", + ])); + + return response()->json(['games' => $games]); + } + + /** + * POST /operator/launch + * + * Creates a new operator game session and returns the launch URL. + */ + public function launch(Request $request) + { + /** @var OperatorCasino $casino */ + $casino = $request->attributes->get('operator_casino'); + + $data = $request->validate([ + 'player_id' => ['required', 'string', 'max:255'], + 'balance' => ['required', 'numeric', 'min:0'], + 'currency' => ['required', 'string', 'size:3'], + 'game' => ['required', 'string', 'in:' . implode(',', self::VALID_GAMES)], + // license_key is consumed by middleware; allow it in the body without failing validation + 'license_key' => ['sometimes', 'string'], + ]); + + // Generate server seed for provably-fair and store it encrypted + $serverSeed = bin2hex(random_bytes(32)); + $serverSeedHash = hash('sha256', $serverSeed); + + $token = (string) Str::uuid(); + $expiresAt = now()->addHours(4); + + OperatorSession::create([ + 'session_token' => $token, + 'operator_casino_id' => $casino->id, + 'player_id' => $data['player_id'], + 'game_slug' => $data['game'], + 'currency' => strtoupper($data['currency']), + 'start_balance' => $data['balance'], + 'current_balance' => $data['balance'], + 'server_seed' => encrypt($serverSeed), + 'server_seed_hash' => $serverSeedHash, + 'status' => 'active', + 'expires_at' => $expiresAt, + ]); + + $baseUrl = rtrim((string) config('games.game_base_url', config('app.url')), '/'); + $launchUrl = "{$baseUrl}/{$data['game']}?session={$token}"; + + return response()->json([ + 'session_token' => $token, + 'launch_url' => $launchUrl, + 'server_seed_hash' => $serverSeedHash, + 'expires_at' => $expiresAt->toIso8601String(), + ]); + } + + /** + * GET /operator/session/{token} + * + * Returns the current state of a session — including the final balance delta. + * The casino should call this after the player's session ends. + */ + public function session(Request $request, string $token) + { + /** @var OperatorCasino $casino */ + $casino = $request->attributes->get('operator_casino'); + + $session = OperatorSession::where('session_token', $token) + ->where('operator_casino_id', $casino->id) + ->firstOrFail(); + + $session->expireIfNeeded(); + + return response()->json([ + 'session_token' => $session->session_token, + 'player_id' => $session->player_id, + 'game' => $session->game_slug, + 'currency' => $session->currency, + 'start_balance' => (float) $session->start_balance, + 'current_balance' => (float) $session->current_balance, + 'balance_delta' => round((float) $session->current_balance - (float) $session->start_balance, 4), + 'status' => $session->status, + 'expires_at' => $session->expires_at->toIso8601String(), + ]); + } +} diff --git a/app/Http/Controllers/PromoController.php b/app/Http/Controllers/PromoController.php new file mode 100644 index 0000000..675789d --- /dev/null +++ b/app/Http/Controllers/PromoController.php @@ -0,0 +1,51 @@ +all(), [ + 'code' => ['required','string','max:64'], + ])->validate(); + + $code = strtoupper(trim($data['code'])); + try { + $res = $this->client->post($request, '/promos/apply', [ 'code' => $code ]); + if ($res->successful()) { + $body = $res->json() ?: []; + // Backward compatibility: ensure a message key exists + if (!isset($body['message'])) { + $body['message'] = 'Promo applied successfully.'; + } + // PromoControllerTest expects { success: true, message: '...' } + return response()->json([ + 'success' => true, + 'message' => $body['message'] + ], 200); + } + if ($res->clientError()) return $this->mapClientError($res); + if ($res->serverError()) return $this->mapServiceUnavailable($res); + return $this->mapBadGateway(); + } catch (\Throwable $e) { + return $this->mapBadGateway('API server not reachable'); + } + } +} diff --git a/app/Http/Controllers/RecentlyPlayedController.php b/app/Http/Controllers/RecentlyPlayedController.php new file mode 100644 index 0000000..bfe3e3b --- /dev/null +++ b/app/Http/Controllers/RecentlyPlayedController.php @@ -0,0 +1,35 @@ +id) + ->select('game_name', \Illuminate\Support\Facades\DB::raw('MAX(created_at) as last_played_at')) + ->groupBy('game_name') + ->orderByDesc('last_played_at') + ->limit(8) + ->get() + ->map(fn($row) => [ + 'game_name' => $row->game_name, + 'slug' => strtolower(preg_replace('/[^a-z0-9]+/i', '-', $row->game_name)), + 'last_played_at' => $row->last_played_at, + ]); + + return response()->json(['data' => $games]); + } +} diff --git a/app/Http/Controllers/Settings/KycController.php b/app/Http/Controllers/Settings/KycController.php new file mode 100644 index 0000000..d7da8fe --- /dev/null +++ b/app/Http/Controllers/Settings/KycController.php @@ -0,0 +1,136 @@ +client->get($request, '/kyc/documents', [], retry: true); + if ($res->successful()) { + $j = $res->json() ?: []; + $docs = $j['data'] ?? $j['documents'] ?? $j; + } + } catch (\Throwable $e) { + // ignore; page can still render and show empty state + } + + return Inertia::render('settings/Kyc', [ + 'documents' => $docs, + 'accepted' => [ + 'identity' => ['passport','driver_license','id_card','other'], + 'address' => ['bank_statement','utility_bill','other'], + 'payment' => ['online_banking','other'], + ], + 'maxUploadMb' => 15, + ]); + } + + /** + * Upload a new KYC document via upstream (multipart) + */ + public function store(Request $request) + { + $validated = $request->validate([ + 'category' => ['required', Rule::in(['identity','address','payment'])], + 'type' => ['required', Rule::in(['passport','driver_license','id_card','bank_statement','utility_bill','online_banking','other'])], + 'file' => ['required','file','max:15360', 'mimetypes:image/jpeg,image/png,image/webp,application/pdf'], + ]); + + try { + $res = $this->client->postMultipart($request, '/kyc/documents', [ + 'category' => $validated['category'], + 'type' => $validated['type'], + ], $request->file('file'), 'file'); + + if ($res->successful()) { + return back()->with('status', 'Document uploaded'); + } + if ($res->clientError()) { + $msg = data_get($res->json(), 'message', 'Invalid request'); + return back()->withErrors(['kyc' => $msg]); + } + if ($res->serverError()) { + return back()->withErrors(['kyc' => 'Service temporarily unavailable']); + } + return back()->withErrors(['kyc' => 'API server not reachable']); + } catch (\Throwable $e) { + return back()->withErrors(['kyc' => 'API server not reachable']); + } + } + + /** + * Delete a KYC document via upstream + */ + public function destroy(Request $request, int $docId) + { + try { + $res = $this->client->delete($request, "/kyc/documents/{$docId}"); + if ($res->successful()) { + return back()->with('status', 'Document deleted'); + } + if ($res->clientError()) { + $msg = data_get($res->json(), 'message', 'Invalid request'); + return back()->withErrors(['kyc' => $msg]); + } + if ($res->serverError()) { + return back()->withErrors(['kyc' => 'Service temporarily unavailable']); + } + return back()->withErrors(['kyc' => 'API server not reachable']); + } catch (\Throwable $e) { + return back()->withErrors(['kyc' => 'API server not reachable']); + } + } + + /** + * Download a document via upstream: prefer redirect to signed URL if provided + */ + public function download(Request $request, int $docId) + { + try { + $res = $this->client->get($request, "/kyc/documents/{$docId}/download", [], retry: false); + if ($res->successful()) { + $j = $res->json(); + $url = $j['url'] ?? null; + if ($url) { + return redirect()->away($url); + } + // If upstream responds with binary directly, just passthrough headers/body + $content = $res->body(); + $headers = [ + 'Content-Type' => $res->header('Content-Type', 'application/octet-stream'), + ]; + return response($content, 200, $headers); + } + if ($res->clientError()) { + $msg = data_get($res->json(), 'message', 'Invalid request'); + return back()->withErrors(['kyc' => $msg]); + } + if ($res->serverError()) { + return back()->withErrors(['kyc' => 'Service temporarily unavailable']); + } + return back()->withErrors(['kyc' => 'API server not reachable']); + } catch (\Throwable $e) { + return back()->withErrors(['kyc' => 'API server not reachable']); + } + } +} diff --git a/app/Http/Controllers/Settings/PasswordController.php b/app/Http/Controllers/Settings/PasswordController.php new file mode 100644 index 0000000..d51afe4 --- /dev/null +++ b/app/Http/Controllers/Settings/PasswordController.php @@ -0,0 +1,32 @@ +user()->update([ + 'password' => $request->password, + ]); + + return back(); + } +} diff --git a/app/Http/Controllers/Settings/ProfileController.php b/app/Http/Controllers/Settings/ProfileController.php new file mode 100644 index 0000000..ab07fb9 --- /dev/null +++ b/app/Http/Controllers/Settings/ProfileController.php @@ -0,0 +1,60 @@ + $request->user() instanceof MustVerifyEmail, + 'status' => $request->session()->get('status'), + ]); + } + + /** + * Update the user's profile information. + */ + public function update(ProfileUpdateRequest $request): RedirectResponse + { + $request->user()->fill($request->validated()); + + if ($request->user()->isDirty('email')) { + $request->user()->email_verified_at = null; + } + + $request->user()->save(); + + return to_route('profile.edit'); + } + + /** + * Delete the user's profile. + */ + public function destroy(ProfileDeleteRequest $request): RedirectResponse + { + $user = $request->user(); + + Auth::logout(); + + $user->delete(); + + $request->session()->invalidate(); + $request->session()->regenerateToken(); + + return redirect('/'); + } +} diff --git a/app/Http/Controllers/Settings/SecurityController.php b/app/Http/Controllers/Settings/SecurityController.php new file mode 100644 index 0000000..d30d9e7 --- /dev/null +++ b/app/Http/Controllers/Settings/SecurityController.php @@ -0,0 +1,70 @@ + (bool) optional($request->user())->hasEnabledTwoFactorAuthentication(), + ]); + } + + /** + * List active sessions for the current user (from database sessions table). + */ + public function sessions(Request $request) + { + $userId = Auth::id(); + $rows = DB::table('sessions') + ->where('user_id', $userId) + ->orderByDesc('last_activity') + ->limit(100) + ->get(['id', 'ip_address', 'user_agent', 'last_activity']); + + // Format response + $data = $rows->map(function ($r) use ($request) { + $isCurrent = $request->session()->getId() === $r->id; + return [ + 'id' => $r->id, + 'ip' => $r->ip_address, + 'user_agent' => $r->user_agent, + 'last_activity' => $r->last_activity, + 'current' => $isCurrent, + ]; + })->values(); + + return response()->json(['data' => $data]); + } + + /** + * Revoke a specific session by ID (current user's session only) + */ + public function revoke(Request $request, string $id) + { + $userId = Auth::id(); + $session = DB::table('sessions')->where('id', $id)->first(); + if (! $session || $session->user_id != $userId) { + abort(404); + } + // Prevent revoking current session via this endpoint to avoid lockouts + if ($request->session()->getId() === $id) { + return response()->json(['message' => 'Cannot revoke current session via API.'], 422); + } + DB::table('sessions')->where('id', $id)->delete(); + return response()->json(['message' => 'Session revoked']); + } +} diff --git a/app/Http/Controllers/Settings/TwoFactorAuthenticationController.php b/app/Http/Controllers/Settings/TwoFactorAuthenticationController.php new file mode 100644 index 0000000..4402383 --- /dev/null +++ b/app/Http/Controllers/Settings/TwoFactorAuthenticationController.php @@ -0,0 +1,37 @@ +ensureStateIsValid(); + + return Inertia::render('settings/TwoFactor', [ + 'twoFactorEnabled' => $request->user()->hasEnabledTwoFactorAuthentication(), + 'requiresConfirmation' => Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm'), + ]); + } +} diff --git a/app/Http/Controllers/SocialController.php b/app/Http/Controllers/SocialController.php new file mode 100644 index 0000000..9792ca6 --- /dev/null +++ b/app/Http/Controllers/SocialController.php @@ -0,0 +1,377 @@ +where('username', $username) + ->first(); + + if (!$user) { + abort(404, 'User not found'); + } + + $auth = Auth::user(); + $authId = $auth?->id; + + $likesCount = ProfileLike::where('profile_id', $user->id)->count(); + $hasLiked = $authId ? ProfileLike::where('profile_id', $user->id)->where('user_id', $authId)->exists() : false; + + $comments = ProfileComment::with(['user:id,username,avatar']) + ->where('profile_id', $user->id) + ->latest() + ->limit(20) + ->get() + ->map(function ($c) { + return [ + 'id' => $c->id, + 'user' => [ + 'id' => $c->user->id, + 'username' => $c->user->username, + 'avatar' => $c->user->avatar, + ], + 'content' => $c->content, + 'created_at' => $c->created_at, + ]; + }); + + // Friendship flags + $isFriend = false; + $isPending = false; // I sent them a request (outgoing) + $theyRequestedMe = false; // They sent me a request (incoming — I can accept/decline) + $friendRowId = null; + if ($authId) { + $friendRow = Friend::where(function ($q) use ($authId, $user) { + $q->where('user_id', $authId)->where('friend_id', $user->id); + })->orWhere(function ($q) use ($authId, $user) { + $q->where('user_id', $user->id)->where('friend_id', $authId); + })->first(); + if ($friendRow) { + $isFriend = $friendRow->status === 'accepted'; + $isPending = $friendRow->status === 'pending' && $friendRow->user_id == $authId; + $theyRequestedMe = $friendRow->status === 'pending' && $friendRow->user_id == $user->id; + $friendRowId = $friendRow->id; + } + } + + $profile = [ + 'id' => $user->id, + 'username' => $user->username, + 'avatar' => $user->avatar ?? $user->avatar_url, + 'banner' => $user->banner, + 'bio' => $user->bio, + 'vip_level' => (int) ($user->vip_level ?? 0), + 'role' => $user->role ?? 'User', + 'clan_tag' => $user->clan_tag, + 'stats' => [ + 'wagered' => (float) ($user->stats?->total_wagered ?? 0), + 'wins' => (int) ($user->stats?->total_wins ?? 0), + 'losses' => null, + 'biggest_win' => (float) ($user->stats?->biggest_win ?? 0), + 'biggest_win_game' => $user->stats?->biggest_win_game ?? null, + 'join_date' => optional($user->created_at)->toDateString(), + 'likes_count' => $likesCount, + ], + 'best_wins' => [], + 'comments' => $comments, + ]; + + return Inertia::render('Social/Profile', [ + 'profile' => $profile, + 'isOwnProfile' => $authId ? ((int)$authId === (int)$user->id) : false, + 'isFriend' => $isFriend, + 'isPending' => $isPending, + 'theyRequestedMe' => $theyRequestedMe, + 'friendRowId' => $friendRowId, + 'hasLiked' => $hasLiked, + ]); + } + + // Update own profile + public function update(Request $request) + { + $user = Auth::user(); + abort_unless($user, 403); + + $data = $request->validate([ + 'is_public' => 'boolean', + 'bio' => 'nullable|string|max:160', + 'avatar' => 'nullable|string', + 'banner' => 'nullable|string', + ]); + + $user->fill([ + 'is_public' => (bool) ($data['is_public'] ?? $user->is_public), + 'bio' => $data['bio'] ?? $user->bio, + 'avatar' => $data['avatar'] ?? $user->avatar, + 'banner' => $data['banner'] ?? $user->banner, + ])->save(); + + return back()->with('success', 'Profile updated.'); + } + + // Upload avatar or banner + public function uploadImage(Request $request) + { + $user = Auth::user(); + abort_unless($user, 403); + + $request->validate([ + 'type' => 'required|in:avatar,banner', + 'file' => 'required|file|mimes:jpeg,jpg,png,gif,webp,bmp|max:5120', + ]); + + $file = $request->file('file'); + $type = $request->input('type'); + $path = $file->store("profile/{$type}s", 'public'); + $url = Storage::disk('public')->url($path); + + if ($type === 'banner') { + $user->banner = $url; + } else { + $user->avatar = $url; + } + $user->save(); + + return response()->json(['url' => $url], 200); + } + + // Toggle like on a profile + public function like($id) + { + $me = Auth::user(); + abort_unless($me, 403); + $target = User::findOrFail($id); + if ($target->id === $me->id) { + return back(); + } + $like = ProfileLike::where('user_id', $me->id)->where('profile_id', $target->id)->first(); + if ($like) { + $like->delete(); + } else { + ProfileLike::create(['user_id' => $me->id, 'profile_id' => $target->id]); + } + return back(); + } + + // Add a comment to a profile + public function comment(Request $request, $id) + { + $me = Auth::user(); + abort_unless($me, 403); + $data = $request->validate(['content' => 'required|string|max:500']); + $target = User::findOrFail($id); + ProfileComment::create([ + 'user_id' => $me->id, + 'profile_id' => $target->id, + 'content' => $data['content'], + ]); + return back()->with('success', 'Comment posted.'); + } + + // Report a profile + public function report(Request $request, $id) + { + $me = Auth::user(); + abort_unless($me, 403); + $data = $request->validate([ + 'reason' => 'required|string', + 'details' => 'nullable|string', + 'snapshot' => 'nullable|array', + 'screenshot' => 'nullable|string', + ]); + $target = User::findOrFail($id); + + $screenshotPath = null; + if (!empty($data['screenshot'])) { + $base64 = $data['screenshot']; + // Strip data URI prefix if present + if (str_contains($base64, ',')) { + $base64 = explode(',', $base64, 2)[1]; + } + $decoded = base64_decode($base64, true); + if ($decoded !== false) { + $filename = 'reports/profile-screenshots/' . $target->id . '_' . time() . '.png'; + Storage::disk('public')->put($filename, $decoded); + $screenshotPath = $filename; + } + } + + ProfileReport::create([ + 'reporter_id' => $me->id, + 'profile_id' => $target->id, + 'reason' => $data['reason'], + 'details' => $data['details'] ?? null, + 'snapshot' => $data['snapshot'] ?? null, + 'screenshot_path' => $screenshotPath, + ]); + + if ($request->expectsJson()) { + return response()->json(['success' => true]); + } + return back()->with('success', 'Profile reported.'); + } + + // User search + public function search(Request $request) + { + $q = (string) $request->input('q', ''); + if (strlen($q) < 1) return response()->json([]); + + $users = User::query() + ->where('username', 'like', "%" . str_replace(['%','_'], ['\\%','\\_'], $q) . "%") + ->orWhere('id', '=', $q) + ->orderBy('username') + ->limit(10) + ->get(['id', 'username', 'avatar', 'avatar_url', 'vip_level', 'balance']); + + $out = $users->map(function ($u) { + return [ + 'id' => $u->id, + 'username' => $u->username, + 'avatar' => $u->avatar ?? $u->avatar_url, + 'avatar_url' => $u->avatar_url ?? $u->avatar, + 'vip_level' => (int) ($u->vip_level ?? 0), + 'stats' => [ + 'wager' => 0, // Placeholder + 'wins' => 0, // Placeholder + 'balance' => $u->balance + ] + ]; + }); + return response()->json($out->all(), 200); + } + + // Send a tip (balance transfer) and record it + public function tip(Request $request, $id) + { + $sender = Auth::user(); + abort_unless($sender, 403); + + $data = $request->validate([ + 'currency' => 'required|string|max:10', + 'amount' => 'required|numeric|min:0.00000001', + 'note' => 'nullable|string|max:140', + ]); + + if ((int)$id === (int)$sender->id) { + return back()->withErrors(['amount' => 'You cannot tip yourself.']); + } + + $receiver = User::findOrFail($id); + $amount = (float) $data['amount']; + + if ($sender->balance < $amount) { + return back()->withErrors(['amount' => 'Insufficient balance']); + } + + DB::transaction(function () use ($sender, $receiver, $amount, $data) { + // Simple balance transfer using users.balance + $sender->balance = (float) $sender->balance - $amount; + $receiver->balance = (float) $receiver->balance + $amount; + $sender->save(); + $receiver->save(); + + Tip::create([ + 'from_user_id' => $sender->id, + 'to_user_id' => $receiver->id, + 'currency' => strtoupper($data['currency']), + 'amount' => $amount, + 'note' => $data['note'] ?? null, + ]); + }); + + return back()->with('success', 'Tip sent successfully.'); + } + + // --- Friends --- + public function requestFriend(Request $request) + { + $me = Auth::user(); + abort_unless($me, 403); + $data = $request->validate([ + 'user_id' => 'required|integer', + ]); + $targetId = (int) $data['user_id']; + if ($targetId === (int)$me->id) { + return back()->withErrors(['friend' => 'You cannot add yourself.']); + } + $target = User::findOrFail($targetId); + + $existing = Friend::where('user_id', $me->id)->where('friend_id', $target->id)->first(); + if ($existing) { + if ($existing->status === 'pending') { + return back()->with('success', 'Friend request already sent.'); + } + if ($existing->status === 'accepted') { + return back()->with('success', 'You are already friends.'); + } + } + Friend::updateOrCreate([ + 'user_id' => $me->id, + 'friend_id' => $target->id, + ], [ 'status' => 'pending' ]); + + return back()->with('success', 'Friend request sent.'); + } + + public function acceptFriend($id) + { + $me = Auth::user(); + abort_unless($me, 403); + $requestRow = Friend::where('user_id', $id)->where('friend_id', $me->id)->where('status', 'pending')->first(); + if (!$requestRow) { + return back()->withErrors(['friend' => 'Request not found.']); + } + DB::transaction(function () use ($requestRow, $me, $id) { + $requestRow->status = 'accepted'; + $requestRow->save(); + // Ensure reciprocal row + Friend::updateOrCreate([ + 'user_id' => $me->id, + 'friend_id' => (int) $id, + ], [ 'status' => 'accepted' ]); + }); + return back()->with('success', 'Friend request accepted.'); + } + + public function declineFriend($id) + { + $me = Auth::user(); + abort_unless($me, 403); + $requestRow = Friend::where('user_id', $id)->where('friend_id', $me->id)->where('status', 'pending')->first(); + if (!$requestRow) { + return back()->withErrors(['friend' => 'Request not found.']); + } + $requestRow->delete(); + return back()->with('success', 'Friend request declined.'); + } + + public function hub() + { + return Inertia::render('Social/Hub'); + } + + public function me() + { + return Inertia::render('Social/Settings', [ + 'user' => Auth::user(), + ]); + } +} diff --git a/app/Http/Controllers/SupportChatController.php b/app/Http/Controllers/SupportChatController.php new file mode 100644 index 0000000..8407f4f --- /dev/null +++ b/app/Http/Controllers/SupportChatController.php @@ -0,0 +1,400 @@ +get('support_chat_enabled'); + $enabled = is_null($flag) ? (bool) config('app.support_chat_enabled', env('SUPPORT_CHAT_ENABLED', true)) : (bool) $flag; + if (!$enabled) { + abort(503, 'Support chat is currently unavailable.'); + } + } + + private function isChatEnabled(): bool + { + $flag = cache()->get('support_chat_enabled'); + return is_null($flag) ? (bool) config('app.support_chat_enabled', env('SUPPORT_CHAT_ENABLED', true)) : (bool) $flag; + } + + private function sessionState(Request $request): array + { + $state = $request->session()->get(self::SESSION_KEY, [ + 'thread_id' => null, + 'status' => 'new', + 'topic' => null, + 'messages' => [], + 'data_access_granted' => false, + ]); + + if (!empty($state['thread_id'])) { + $cached = cache()->get('support_threads:'.$state['thread_id']); + if (is_array($cached)) { + $state['status'] = $cached['status'] ?? $state['status']; + foreach ($cached['messages'] ?? [] as $msg) { + if (($msg['sender'] ?? '') === 'agent') { + $state['status'] = 'agent'; + break; + } + } + if (count($cached['messages'] ?? []) > count($state['messages'])) { + $state['messages'] = $cached['messages']; + } + } + } + return $state; + } + + private function saveState(Request $request, array $state): void + { + $state['messages'] = array_slice($state['messages'], -100); + $state['updated_at'] = now()->toIso8601String(); + $request->session()->put(self::SESSION_KEY, $state); + $this->persistThread($request, $state); + } + + private function persistThread(Request $request, array $state): void + { + if (empty($state['thread_id'])) return; + $user = $request->user(); + + // Construct user data safely without assuming local DB columns exist if Auth is mocked + $userData = null; + if ($user) { + $userData = [ + 'id' => $user->id ?? 'guest', + 'username' => $user->username ?? $user->name ?? 'Guest', + 'email' => $user->email ?? '', + 'avatar_url' => $user->avatar_url ?? $user->profile_photo_url ?? null, + ]; + } + + $record = [ + 'id' => $state['thread_id'], + 'status' => $state['status'] ?? 'new', + 'topic' => $state['topic'] ?? null, + 'user' => $userData, + 'messages' => $state['messages'] ?? [], + 'updated_at' => now()->toIso8601String(), + ]; + + // Cache is file/redis based, so this is fine (no SQL DB) + cache()->put('support_threads:'.$state['thread_id'], $record, now()->addDay()); + + $index = cache()->get('support_threads_index', []); + $found = false; + foreach ($index as &$row) { + if (($row['id'] ?? null) === $record['id']) { + $row['updated_at'] = $record['updated_at']; + $row['status'] = $record['status']; + $row['topic'] = $record['topic']; + $row['user'] = $record['user']; + $found = true; + break; + } + } + if (!$found) { + $index[] = [ + 'id' => $record['id'], + 'updated_at' => $record['updated_at'], + 'status' => $record['status'], + 'topic' => $record['topic'], + 'user' => $record['user'], + ]; + } + cache()->put('support_threads_index', $index, now()->addDay()); + } + + public function start(Request $request) + { + // ensureEnabled removed here to allow handoff logic + $user = $request->user(); + abort_unless($user, 401); + + $data = $request->validate(['topic' => 'nullable|string|max:60']); + $enabled = $this->isChatEnabled(); + + $state = $this->sessionState($request); + if (!$state['thread_id']) { + $state['thread_id'] = (string) Str::uuid(); + $state['status'] = $enabled ? 'ai' : 'handoff'; + $state['topic'] = $data['topic'] ?? null; + $state['messages'] = []; + $state['data_access_granted'] = false; + $state['user'] = [ + 'id' => $user->id, + 'username' => $user->username ?? $user->name, + 'email' => $user->email + ]; + + if (!empty($data['topic'])) { + $state['messages'][] = ['id' => Str::uuid()->toString(), 'sender' => 'user', 'body' => 'Thema gewählt: ' . $data['topic'], 'at' => now()->toIso8601String()]; + + if ($enabled) { + $aiReply = $this->askOllama($state); + if ($aiReply) { + $state['messages'][] = ['id' => Str::uuid()->toString(), 'sender' => 'ai', 'body' => $aiReply, 'at' => now()->toIso8601String()]; + } else { + // Ollama offline or returned nothing — fall back to human handoff + $state['status'] = 'handoff'; + $state['messages'][] = ['id' => Str::uuid()->toString(), 'sender' => 'system', 'body' => 'Unser KI-Assistent ist gerade nicht erreichbar. Ein Mitarbeiter wird sich in Kürze bei dir melden.', 'at' => now()->toIso8601String()]; + } + } else { + $state['messages'][] = ['id' => Str::uuid()->toString(), 'sender' => 'system', 'body' => 'Der KI-Assistent ist derzeit inaktiv. Ein Mitarbeiter wird sich in Kürze bei dir melden.', 'at' => now()->toIso8601String()]; + } + } + } + + $this->saveState($request, $state); + return response()->json($state); + } + + public function close(Request $request) + { + abort_unless(Auth::check(), 401); + $state = $this->sessionState($request); + + if (!empty($state['thread_id'])) { + cache()->forget('support_threads:'.$state['thread_id']); + $index = cache()->get('support_threads_index', []); + $index = array_filter($index, fn($item) => $item['id'] !== $state['thread_id']); + cache()->put('support_threads_index', array_values($index), now()->addDay()); + } + + $request->session()->forget(self::SESSION_KEY); + + return response()->json(['thread_id' => null, 'status' => 'closed', 'topic' => null, 'messages' => []]); + } + + public function message(Request $request) + { + $user = $request->user(); + abort_unless($user, 401); + + $data = $request->validate(['text' => 'required|string|min:1|max:1000']); + $text = trim($data['text']); + $state = $this->sessionState($request); + $enabled = $this->isChatEnabled(); + + if (!$state['thread_id'] || ($state['status'] ?? null) === 'closed') { + return response()->json(['error' => 'No active chat thread.'], 422); + } + + $state['messages'][] = ['id' => Str::uuid()->toString(), 'sender' => 'user', 'body' => $text, 'at' => now()->toIso8601String()]; + + if (!$enabled) { + $lastMsg = collect($state['messages'])->where('sender', 'system')->last(); + if (!$lastMsg || !Str::contains($lastMsg['body'], 'Mitarbeiter')) { + $state['status'] = 'handoff'; + $state['messages'][] = ['id' => Str::uuid()->toString(), 'sender' => 'system', 'body' => 'Deine Nachricht wurde empfangen. Bitte warte auf einen Mitarbeiter.', 'at' => now()->toIso8601String()]; + } + $this->saveState($request, $state); + return response()->json($state); + } + + if (Str::of(Str::lower($text))->contains(['ja', 'yes', 'erlaubt', 'okay', 'ok', 'zugriff'])) { + $lastSystemMsg = collect($state['messages'])->where('sender', 'system')->last(); + if ($lastSystemMsg && Str::contains($lastSystemMsg['body'], 'zugreifen')) { + $state['data_access_granted'] = true; + $state['messages'][] = ['id' => Str::uuid()->toString(), 'sender' => 'system', 'body' => 'Zugriff erlaubt. Ich sehe mir deine Daten an...', 'at' => now()->toIso8601String()]; + $aiReply = $this->askOllama($state); + if ($aiReply) { + $state['messages'][] = ['id' => Str::uuid()->toString(), 'sender' => 'ai', 'body' => $aiReply, 'at' => now()->toIso8601String()]; + } + $this->saveState($request, $state); + return response()->json($state); + } + } + + if (Str::contains($text, ['🛑', ':stop:', 'STOP'])) { + $state['status'] = 'stopped'; + $state['messages'][] = ['id' => Str::uuid()->toString(), 'sender' => 'system', 'body' => 'Verstanden. Soll ein Mitarbeiter übernehmen?', 'at' => now()->toIso8601String()]; + $this->saveState($request, $state); + return response()->json($state); + } + + if ($state['status'] === 'stopped' && Str::of(Str::lower($text))->contains(['ja', 'yes', 'y'])) { + return $this->handoff($request); + } + + if (in_array($state['status'], ['handoff', 'agent'])) { + $this->saveState($request, $state); + return response()->json($state); + } + + $aiReply = $this->askOllama($state); + + if ($aiReply && (str_contains($aiReply, '[HANDOFF]') || trim($aiReply) === '[HANDOFF]')) { + return $this->handoff($request); + } + + if ($aiReply && (str_contains($aiReply, '[REQUEST_DATA]') || trim($aiReply) === '[REQUEST_DATA]')) { + $state['messages'][] = ['id' => Str::uuid()->toString(), 'sender' => 'system', 'body' => 'Um dir besser helfen zu können, müsste ich auf deine Kontodaten (Wallet, Boni, etc.) zugreifen. Ist das in Ordnung? (Antworte mit "Ja")', 'at' => now()->toIso8601String()]; + $this->saveState($request, $state); + return response()->json($state); + } + + if ($aiReply) { + $state['messages'][] = ['id' => Str::uuid()->toString(), 'sender' => 'ai', 'body' => $aiReply, 'at' => now()->toIso8601String()]; + } else { + $state['messages'][] = ['id' => Str::uuid()->toString(), 'sender' => 'system', 'body' => 'Ich konnte gerade keine Antwort generieren. Bitte versuche es erneut oder stoppe die KI mit 🛑.', 'at' => now()->toIso8601String()]; + } + + $this->saveState($request, $state); + return response()->json($state); + } + + private function askOllama(array $state): ?string + { + $host = rtrim(env('OLLAMA_HOST', 'http://127.0.0.1:11434'), '/'); + $model = env('OLLAMA_MODEL', 'llama3'); + if (!$host) return null; + + $topic = $state['topic'] ? (string) $state['topic'] : 'Allgemeiner Support'; + $hasAccess = $state['data_access_granted'] ?? false; + + $system = "Du bist ein hilfsbereiter Casino-Support-Assistent. Antworte knapp, freundlich und in deutscher Sprache. Frage maximal eine Rückfrage gleichzeitig. Wenn du eine Frage nicht beantworten kannst oder der Nutzer frustriert wirkt, antworte NUR mit dem Wort `[HANDOFF]`. Thema: {$topic}."; + + if (!$hasAccess) { + $system .= "\n\nWICHTIG: Du hast AKTUELL KEINEN ZUGRIFF auf Nutzerdaten. Wenn der Nutzer nach persönlichen Informationen fragt (z.B. E-Mail, Guthaben, Boni, Transaktionen), antworte SOFORT und AUSSCHLIESSLICH mit dem Wort `[REQUEST_DATA]`. Erkläre nichts, entschuldige dich nicht. Nur `[REQUEST_DATA]`."; + } else { + $contextJson = ''; + try { + // REPLACED DB CALLS WITH API CALLS + $u = Auth::user(); + if ($u) { + $apiBase = config('app.api_url'); + // We assume the user is authenticated via a token or session that is passed along. + // Since we are in a local environment acting as a client, we might need to forward the token. + // For now, we assume the API endpoints are protected and we need a way to call them. + // If Auth::user() works, it means we have a session. + + // Fetch data from external API + // Note: In a real microservice setup, we would pass the user's token. + // Here we try to fetch from the API using the configured base URL. + + // Example: Fetch Wallets + $walletsResp = Http::acceptJson()->get($apiBase . '/user/wallets'); + $wallets = $walletsResp->ok() ? $walletsResp->json() : []; + + // Example: Fetch Bonuses + $bonusesResp = Http::acceptJson()->get($apiBase . '/user/bonuses/active'); + $activeBonuses = $bonusesResp->ok() ? $bonusesResp->json() : []; + + $ctx = [ + 'user' => [ + 'username' => $u->username ?? $u->name, + 'email' => $u->email, + 'id' => $u->id + ], + 'wallets' => $wallets, + 'active_bonuses' => $activeBonuses, + ]; + $contextJson = json_encode($ctx, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); + } + } catch (\Throwable $e) { + // Fallback if API calls fail + \Illuminate\Support\Facades\Log::error('Failed to fetch remote context: ' . $e->getMessage()); + } + + if ($contextJson) { + $system .= "\n\nNutzerdaten (ZUGRIFF ERLAUBT):\n" . $contextJson . "\n\nDU DARFST DIESE DATEN JETZT VERWENDEN. Die Daten im JSON beziehen sich AUSSCHLIESSLICH auf den aktuellen Gesprächspartner. Wenn der Nutzer nach seinen EIGENEN Daten fragt (z.B. 'meine E-Mail', 'mein Guthaben'), antworte ihm direkt mit den Werten aus dem JSON. Wenn der Nutzer nach den Daten einer ANDEREN Person fragt (z.B. 'die E-Mail von Bingo'), musst du die Anfrage ablehnen und antworten, dass du nur Auskunft über sein eigenes Konto geben darfst."; + } + } + + $recent = array_slice($state['messages'], -10); + $chatText = $system."\n\n"; + foreach ($recent as $m) { + $role = $m['sender'] === 'user' ? 'User' : ucfirst($m['sender']); + $chatText .= "{$role}: {$m['body']}\n"; + } + $chatText .= "Assistant:"; + + try { + $res = Http::timeout(12)->post($host.'/api/generate', [ + 'model' => $model, + 'prompt' => $chatText, + 'stream' => false, + 'options' => ['temperature' => 0.3, 'num_predict' => 180], + ]); + if (!$res->ok()) return null; + $json = $res->json(); + $out = trim((string)($json['response'] ?? '')); + return $out ?: null; + } catch (\Throwable $e) { + return null; + } + } + + public function status(Request $request) { return response()->json($this->sessionState($request)); } + + public function stop(Request $request) + { + abort_unless(Auth::check(), 401); + $state = $this->sessionState($request); + if (!$state['thread_id'] || ($state['status'] ?? null) === 'closed') { + return response()->json(['error' => 'No active chat thread.'], 422); + } + if ($state['status'] === 'ai') { + $state['status'] = 'stopped'; + $state['messages'][] = [ + 'id' => Str::uuid()->toString(), + 'sender' => 'system', + 'body' => 'KI-Assistent gestoppt. Du kannst einen Mitarbeiter anfordern.', + 'at' => now()->toIso8601String(), + ]; + $this->saveState($request, $state); + } + return response()->json($state); + } + + public function handoff(Request $request) { + $this->ensureEnabled(); + abort_unless(Auth::check(), 401); + $state = $this->sessionState($request); + if (!$state['thread_id']) return response()->json(['message' => 'No active support thread'], 422); + $state['status'] = 'handoff'; + $state['messages'][] = ['id' => Str::uuid()->toString(), 'sender' => 'system', 'body' => 'Ich leite dich an einen Mitarbeiter weiter. Bitte habe einen Moment Geduld.', 'at' => now()->toIso8601String()]; + $this->saveState($request, $state); + $webhook = env('SUPPORT_DASHBOARD_WEBHOOK_URL'); + if ($webhook) { try { Http::timeout(5)->post($webhook, ['event' => 'support.handoff', 'thread_id' => $state['thread_id']]); } catch (\Throwable $e) {} } + return response()->json(['message' => 'Hand-off requested', 'state' => $state]); + } + public function stream(Request $request) { + $state = $this->sessionState($request); + if (empty($state['thread_id'])) return response('', 204); + $threadId = $state['thread_id']; + return new StreamedResponse(function () use ($request, $threadId) { + $send = fn($data) => print('data: ' . json_encode($data) . "\n\n"); + $start = time(); + $lastUpdated = null; + $lastCount = -1; + while (time() - $start < 60) { + usleep(500000); + $cached = cache()->get('support_threads:'.$threadId); + $nowState = is_array($cached) ? $cached : $this->sessionState($request); + $count = count($nowState['messages'] ?? []); + $updated = $nowState['updated_at'] ?? null; + if ($count !== $lastCount || $updated !== $lastUpdated) { + $send($nowState); + $lastCount = $count; + $lastUpdated = $updated; + } + if ((time() - $start) % 15 === 0) { print(": ping\n\n"); } + @ob_flush(); @flush(); + } + }, 200, ['Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Connection' => 'keep-alive', 'X-Accel-Buffering' => 'no']); + } +} diff --git a/app/Http/Controllers/TrophyController.php b/app/Http/Controllers/TrophyController.php new file mode 100644 index 0000000..062cdcf --- /dev/null +++ b/app/Http/Controllers/TrophyController.php @@ -0,0 +1,146 @@ + ['title' => 'First Bet', 'desc' => 'Place your very first bet.', 'icon' => '🎲'], + 'first_win' => ['title' => 'First Win', 'desc' => 'Win your first game.', 'icon' => '🏆'], + 'big_winner' => ['title' => 'Big Winner', 'desc' => 'Land a 10× or higher multiplier.', 'icon' => '💥'], + 'high_roller' => ['title' => 'High Roller', 'desc' => 'Wager 100+ BTX in total.', 'icon' => '💎'], + 'frequent_player' => ['title' => 'Frequent Player', 'desc' => 'Place 50+ bets.', 'icon' => '🔥'], + 'hundred_bets' => ['title' => 'Centurion', 'desc' => 'Place 100+ bets.', 'icon' => '⚡'], + 'vault_user' => ['title' => 'Vault Guardian', 'desc' => 'Make your first vault deposit.', 'icon' => '🔒'], + 'vip_level2' => ['title' => 'Rising Star', 'desc' => 'Reach VIP Level 2.', 'icon' => '⭐'], + 'vip_level5' => ['title' => 'Elite', 'desc' => 'Reach VIP Level 5.', 'icon' => '👑'], + 'guild_member' => ['title' => 'Team Player', 'desc' => 'Join a guild.', 'icon' => '🛡️'], + 'promo_user' => ['title' => 'Promo Hunter', 'desc' => 'Redeem your first promo code.', 'icon' => '🎁'], + ]; + + /** + * Trophy room page — show user's achievements + locked ones. + */ + public function index(Request $request) + { + $user = Auth::user(); + abort_unless($user, 403); + + // Sync achievements before showing + $this->syncAchievements($user); + + $unlocked = UserAchievement::where('user_id', $user->id) + ->pluck('unlocked_at', 'achievement_key'); + + $achievements = []; + foreach (self::$definitions as $key => $def) { + $achievements[] = [ + 'key' => $key, + 'title' => $def['title'], + 'description' => $def['desc'], + 'icon' => $def['icon'], + 'unlocked' => isset($unlocked[$key]), + 'unlocked_at' => isset($unlocked[$key]) ? $unlocked[$key]->toIso8601String() : null, + ]; + } + + // Sort: unlocked first, then locked + usort($achievements, fn($a, $b) => ($b['unlocked'] <=> $a['unlocked'])); + + return Inertia::render('Trophy', [ + 'achievements' => $achievements, + 'total' => count(self::$definitions), + 'unlocked' => $unlocked->count(), + ]); + } + + /** + * Trophy room for a specific user (public profiles only). + */ + public function show(Request $request, string $username) + { + $user = User::where('username', $username)->firstOrFail(); + + // Only show trophies for public profiles (or own profile) + if (!$user->is_public && Auth::id() !== $user->id) { + abort(403, 'This profile is private.'); + } + + $this->syncAchievements($user); + + $unlocked = UserAchievement::where('user_id', $user->id) + ->pluck('unlocked_at', 'achievement_key'); + + $achievements = []; + foreach (self::$definitions as $key => $def) { + $achievements[] = [ + 'key' => $key, + 'title' => $def['title'], + 'description' => $def['desc'], + 'icon' => $def['icon'], + 'unlocked' => isset($unlocked[$key]), + 'unlocked_at' => isset($unlocked[$key]) ? $unlocked[$key]->toIso8601String() : null, + ]; + } + + usort($achievements, fn($a, $b) => ($b['unlocked'] <=> $a['unlocked'])); + + return Inertia::render('Trophy', [ + 'achievements' => $achievements, + 'total' => count(self::$definitions), + 'unlocked' => $unlocked->count(), + 'profileUser' => ['username' => $user->username, 'avatar' => $user->avatar], + ]); + } + + /** + * Check and unlock achievements for the given user. + */ + public function syncAchievements(\App\Models\User $user): void + { + $bets = GameBet::where('user_id', $user->id); + $betCount = $bets->count(); + $totalWager = $bets->sum('wager_amount'); + $maxMulti = $bets->max('payout_multiplier') ?? 0; + $hasWin = $bets->where('payout_amount', '>', 0)->exists(); + + $toUnlock = []; + + if ($betCount >= 1) $toUnlock[] = 'first_bet'; + if ($hasWin) $toUnlock[] = 'first_win'; + if ($maxMulti >= 10) $toUnlock[] = 'big_winner'; + if ($totalWager >= 100) $toUnlock[] = 'high_roller'; + if ($betCount >= 50) $toUnlock[] = 'frequent_player'; + if ($betCount >= 100) $toUnlock[] = 'hundred_bets'; + if ($user->vip_level >= 2) $toUnlock[] = 'vip_level2'; + if ($user->vip_level >= 5) $toUnlock[] = 'vip_level5'; + if ($user->guildMember()->exists()) $toUnlock[] = 'guild_member'; + + // Vault usage + if (\App\Models\WalletTransfer::where('user_id', $user->id)->where('type', 'deposit')->exists()) { + $toUnlock[] = 'vault_user'; + } + // Promo usage + if (\App\Models\PromoUsage::where('user_id', $user->id)->exists()) { + $toUnlock[] = 'promo_user'; + } + + foreach ($toUnlock as $key) { + UserAchievement::firstOrCreate( + ['user_id' => $user->id, 'achievement_key' => $key], + ['unlocked_at' => now()] + ); + } + } +} diff --git a/app/Http/Controllers/UserBonusController.php b/app/Http/Controllers/UserBonusController.php new file mode 100644 index 0000000..fb04ce3 --- /dev/null +++ b/app/Http/Controllers/UserBonusController.php @@ -0,0 +1,54 @@ +client->get($request, '/users/me/bonuses', [], retry: true); + if ($res->successful()) { + $body = $res->json() ?: []; + $items = $body['data'] ?? $body['bonuses'] ?? $body; + $out = []; + foreach ((array) $items as $b) { + if (!is_array($b)) continue; + $out[] = [ + 'id' => $b['id'] ?? null, + 'amount' => isset($b['amount']) ? (float) $b['amount'] : 0.0, + 'wager_required' => isset($b['wager_required']) ? (float) $b['wager_required'] : 0.0, + 'wager_progress' => isset($b['wager_progress']) ? (float) $b['wager_progress'] : 0.0, + 'expires_at' => $b['expires_at'] ?? null, + 'is_active' => (bool) ($b['is_active'] ?? false), + 'completed_at' => $b['completed_at'] ?? null, + 'promo' => isset($b['promo']) && is_array($b['promo']) ? [ + 'code' => $b['promo']['code'] ?? null, + 'wager_multiplier' => isset($b['promo']['wager_multiplier']) ? (int) $b['promo']['wager_multiplier'] : null, + ] : null, + ]; + } + return response()->json(['data' => $out], 200); + } + if ($res->clientError()) return $this->mapClientError($res); + if ($res->serverError()) return $this->mapServiceUnavailable($res); + return $this->mapBadGateway(); + } catch (\Throwable $e) { + return $this->mapBadGateway('API server not reachable'); + } + } +} diff --git a/app/Http/Controllers/UserRestrictionApiController.php b/app/Http/Controllers/UserRestrictionApiController.php new file mode 100644 index 0000000..8db7e66 --- /dev/null +++ b/app/Http/Controllers/UserRestrictionApiController.php @@ -0,0 +1,171 @@ +middleware(function ($request, $next) { + $provided = $this->extractToken($request); + $expected = config('services.moderation_api.token'); + + if (!$expected || !hash_equals((string) $expected, (string) $provided)) { + return response()->json(['message' => 'Unauthorized'], 401); + } + + return $next($request); + }); + } + + private function extractToken(Request $request): ?string + { + $auth = $request->header('Authorization'); + if ($auth && str_starts_with($auth, 'Bearer ')) { + return substr($auth, 7); + } + return $request->query('api_token'); + } + + // GET /api/users/{id}/restrictions?active_only=1 + public function listForUser(Request $request, int $userId) + { + try { + $query = []; + if ($request->boolean('active_only')) { + $query['active_only'] = 1; + } + $res = $this->client->get($request, "/users/{$userId}/restrictions", $query, retry: true); + if ($res->successful()) { + return response()->json($res->json() ?: []); + } + if ($res->clientError()) return $this->mapClientError($res); + if ($res->serverError()) return $this->mapServiceUnavailable($res); + return $this->mapBadGateway(); + } catch (\Throwable $e) { + return $this->mapBadGateway('API server not reachable'); + } + } + + // GET /api/users/{id}/restrictions/check + public function checkForUser(Request $request, int $userId) + { + try { + $res = $this->client->get($request, "/users/{$userId}/restrictions/check", [], retry: true); + if ($res->successful()) { + return response()->json($res->json() ?: []); + } + if ($res->clientError()) return $this->mapClientError($res); + if ($res->serverError()) return $this->mapServiceUnavailable($res); + return $this->mapBadGateway(); + } catch (\Throwable $e) { + return $this->mapBadGateway('API server not reachable'); + } + } + + // POST /api/users/{id}/restrictions + public function createForUser(Request $request, int $userId) + { + $data = $this->validatePayload($request, partial: false); + try { + $res = $this->client->post($request, "/users/{$userId}/restrictions", $data); + if ($res->successful()) { + return response()->json($res->json() ?: [], 201); + } + if ($res->clientError()) return $this->mapClientError($res); + if ($res->serverError()) return $this->mapServiceUnavailable($res); + return $this->mapBadGateway(); + } catch (\Throwable $e) { + return $this->mapBadGateway('API server not reachable'); + } + } + + // POST /api/users/{id}/restrictions/upsert + public function upsertForUser(Request $request, int $userId) + { + $data = $this->validatePayload($request, partial: true); + if (empty($data['type'])) { + return response()->json(['message' => 'type is required for upsert'], 422); + } + try { + $res = $this->client->post($request, "/users/{$userId}/restrictions/upsert", $data); + if ($res->successful()) { + // 200 or 201 upstream; forward as given + $status = $res->status() === 201 ? 201 : 200; + return response()->json($res->json() ?: [], $status); + } + if ($res->clientError()) return $this->mapClientError($res); + if ($res->serverError()) return $this->mapServiceUnavailable($res); + return $this->mapBadGateway(); + } catch (\Throwable $e) { + return $this->mapBadGateway('API server not reachable'); + } + } + + // PATCH /api/restrictions/{id} + public function update(Request $request, int $id) + { + $data = $this->validatePayload($request, partial: true); + try { + $res = $this->client->patch($request, "/restrictions/{$id}", $data); + if ($res->successful()) { + return response()->json($res->json() ?: []); + } + if ($res->clientError()) return $this->mapClientError($res); + if ($res->serverError()) return $this->mapServiceUnavailable($res); + return $this->mapBadGateway(); + } catch (\Throwable $e) { + return $this->mapBadGateway('API server not reachable'); + } + } + + // DELETE /api/restrictions/{id} + public function destroy(Request $request, int $id) + { + try { + $res = $this->client->delete($request, "/restrictions/{$id}"); + if ($res->successful()) { + return response()->json($res->json() ?: ['message' => 'Deactivated']); + } + if ($res->clientError()) return $this->mapClientError($res); + if ($res->serverError()) return $this->mapServiceUnavailable($res); + return $this->mapBadGateway(); + } catch (\Throwable $e) { + return $this->mapBadGateway('API server not reachable'); + } + } + + private function validatePayload(Request $request, bool $partial = false): array + { + $required = $partial ? 'sometimes' : 'required'; + $rules = [ + 'type' => [$required, Rule::in($this->allowedTypes)], + 'reason' => ['sometimes', 'nullable', 'string', 'max:255'], + 'notes' => ['sometimes', 'nullable', 'string'], + 'imposed_by' => ['sometimes', 'nullable', 'integer'], + 'starts_at' => ['sometimes', 'nullable', 'date'], + 'ends_at' => ['sometimes', 'nullable', 'date', 'after_or_equal:starts_at'], + 'active' => ['sometimes', 'boolean'], + 'source' => ['sometimes', 'nullable', 'string', 'max:64'], + 'metadata' => ['sometimes', 'nullable', 'array'], + ]; + $validated = $request->validate($rules); + return $validated; + } +} diff --git a/app/Http/Controllers/VaultApiController.php b/app/Http/Controllers/VaultApiController.php new file mode 100644 index 0000000..07e01e0 --- /dev/null +++ b/app/Http/Controllers/VaultApiController.php @@ -0,0 +1,124 @@ +middleware(function ($request, $next) { + $provided = $this->extractToken($request); + $expected = config('services.vault_api.token'); + + if (!$expected || !hash_equals((string) $expected, (string) $provided)) { + return response()->json(['message' => 'Unauthorized'], 401); + } + + return $next($request); + }); + } + + private function extractToken(Request $request): ?string + { + $auth = $request->header('Authorization'); + if ($auth && str_starts_with($auth, 'Bearer ')) { + return substr($auth, 7); + } + return $request->query('api_token'); // fallback: allow ?api_token= + } + + /** + * GET /api/users/{id}/vault + */ + public function showForUser(Request $request, int $id) + { + $perPage = min(200, max(1, (int) $request->query('per_page', 50))); + try { + $res = $this->client->get($request, "/users/{$id}/vault", [ 'per_page' => $perPage ], retry: true); + if ($res->successful()) { + return response()->json($res->json() ?: [], 200); + } + if ($res->clientError()) return $this->mapClientError($res); + if ($res->serverError()) return $this->mapServiceUnavailable($res); + return $this->mapBadGateway(); + } catch (\Throwable $e) { + return $this->mapBadGateway('API server not reachable'); + } + } + + /** + * POST /api/users/{id}/vault/deposit + */ + public function depositForUser(Request $request, int $id) + { + $data = $request->validate([ + 'amount' => ['required','string','regex:/^\d+(?:\.\d{1,4})?$/'], + 'idempotency_key' => ['sometimes','nullable','string','max:64'], + 'reason' => ['sometimes','nullable','string','max:255'], + 'created_by' => ['sometimes','nullable','integer'], + ]); + + // Pass through metadata we can gather; upstream decides usage + $payload = [ + 'amount' => $data['amount'], + 'idempotency_key' => $data['idempotency_key'] ?? null, + 'reason' => $data['reason'] ?? null, + 'created_by' => $data['created_by'] ?? null, + 'ip' => $request->ip(), + 'user_agent' => $request->userAgent(), + ]; + + try { + $res = $this->client->post($request, "/users/{$id}/vault/deposit", $payload); + if ($res->successful()) { + return response()->json($res->json() ?: [], 201); + } + if ($res->clientError()) return $this->mapClientError($res); + if ($res->serverError()) return $this->mapServiceUnavailable($res); + return $this->mapBadGateway(); + } catch (\Throwable $e) { + return $this->mapBadGateway('API server not reachable'); + } + } + + /** + * POST /api/users/{id}/vault/withdraw + */ + public function withdrawForUser(Request $request, int $id) + { + $data = $request->validate([ + 'amount' => ['required','string','regex:/^\d+(?:\.\d{1,4})?$/'], + 'idempotency_key' => ['sometimes','nullable','string','max:64'], + 'reason' => ['sometimes','nullable','string','max:255'], + 'created_by' => ['sometimes','nullable','integer'], + ]); + + $payload = [ + 'amount' => $data['amount'], + 'idempotency_key' => $data['idempotency_key'] ?? null, + 'reason' => $data['reason'] ?? null, + 'created_by' => $data['created_by'] ?? null, + 'ip' => $request->ip(), + 'user_agent' => $request->userAgent(), + ]; + + try { + $res = $this->client->post($request, "/users/{$id}/vault/withdraw", $payload); + if ($res->successful()) { + return response()->json($res->json() ?: [], 201); + } + if ($res->clientError()) return $this->mapClientError($res); + if ($res->serverError()) return $this->mapServiceUnavailable($res); + return $this->mapBadGateway(); + } catch (\Throwable $e) { + return $this->mapBadGateway('API server not reachable'); + } + } +} diff --git a/app/Http/Controllers/VaultController.php b/app/Http/Controllers/VaultController.php new file mode 100644 index 0000000..289f39a --- /dev/null +++ b/app/Http/Controllers/VaultController.php @@ -0,0 +1,121 @@ +query('per_page', 20))); + $items = WalletTransfer::where('user_id', $user->id) + ->orderByDesc('id') + ->limit($perPage) + ->get(['id','type','amount','currency','created_at']); + + $transfers = $items->map(fn($t) => [ + 'id' => $t->id, + 'type' => $t->type, + 'amount' => (string) $t->amount, + 'currency' => $t->currency, + 'created_at' => $t->created_at?->toIso8601String(), + ]); + + $map = $user->vault_balances ?? []; + + return response()->json([ + 'balance' => (string) ($user->balance ?? '0.0000'), + 'vault_balance' => (string) ($user->vault_balance ?? '0.0000'), + 'vault_balances' => array_merge( + ['BTX' => (string) ($user->vault_balance ?? '0.0000')], + $map + ), + 'currency' => 'BTX', + 'transfers' => $transfers, + 'now' => now()->toIso8601String(), + ], 200); + } + + /** + * POST /api/wallet/vault/deposit + */ + public function deposit(Request $request) + { + $user = Auth::user(); + abort_unless($user, 403); + + $data = $request->validate([ + 'amount' => ['required','string','regex:/^\d+(?:\.\d{1,4})?$/'], + 'pin' => ['required','string','regex:/^\d{4,8}$/'], + 'currency' => ['sometimes','string','in:' . implode(',', self::SUPPORTED_CURRENCIES)], + 'idempotency_key' => ['sometimes','nullable','string','max:64'], + ]); + + $currency = strtoupper($data['currency'] ?? 'BTX'); + + if ($resp = $this->wallet->verifyVaultPin($user, (string) $data['pin'])) { + return $resp; + } + + $out = $this->wallet->depositToVault($user, $data['amount'], $data['idempotency_key'] ?? null, $currency); + + return response()->json([ + 'data' => ['type' => 'deposit', 'amount' => $data['amount'], 'currency' => $currency], + 'balances' => [ + 'balance' => $out['balance'], + 'vault_balance' => $out['vault_balance'], + 'vault_balances' => $out['vault_balances'], + ], + ], 201); + } + + /** + * POST /api/wallet/vault/withdraw + */ + public function withdraw(Request $request) + { + $user = Auth::user(); + abort_unless($user, 403); + + $data = $request->validate([ + 'amount' => ['required','string','regex:/^\d+(?:\.\d{1,4})?$/'], + 'pin' => ['required','string','regex:/^\d{4,8}$/'], + 'currency' => ['sometimes','string','in:' . implode(',', self::SUPPORTED_CURRENCIES)], + 'idempotency_key' => ['sometimes','nullable','string','max:64'], + ]); + + $currency = strtoupper($data['currency'] ?? 'BTX'); + + if ($resp = $this->wallet->verifyVaultPin($user, (string) $data['pin'])) { + return $resp; + } + + $out = $this->wallet->withdrawFromVault($user, $data['amount'], $data['idempotency_key'] ?? null, $currency); + + return response()->json([ + 'data' => ['type' => 'withdraw', 'amount' => $data['amount'], 'currency' => $currency], + 'balances' => [ + 'balance' => $out['balance'], + 'vault_balance' => $out['vault_balance'], + 'vault_balances' => $out['vault_balances'], + ], + ], 201); + } +} diff --git a/app/Http/Controllers/VaultPinController.php b/app/Http/Controllers/VaultPinController.php new file mode 100644 index 0000000..e2a54f2 --- /dev/null +++ b/app/Http/Controllers/VaultPinController.php @@ -0,0 +1,86 @@ +validate([ + 'pin' => ['required','string','regex:/^\d{4,8}$/'], + 'current_pin' => ['sometimes','nullable','string','regex:/^\d{4,8}$/'], + ]); + + // If PIN already set, require current_pin and verify + if (!empty($user->vault_pin_hash)) { + if (empty($data['current_pin']) || !Hash::check((string) $data['current_pin'], $user->vault_pin_hash)) { + return response()->json(['message' => 'Current PIN invalid'], 400); + } + } + + $user->vault_pin_hash = Hash::make((string) $data['pin']); + $user->vault_pin_set_at = now(); + $user->vault_pin_attempts = 0; + $user->vault_pin_locked_until = null; + $user->save(); + + return response()->json([ + 'success' => true, + 'message' => 'Vault PIN saved.', + ], 200); + } + + /** + * POST /api/wallet/vault/pin/verify — local implementation + */ + public function verify(Request $request) + { + $user = Auth::user(); + abort_unless($user, 403); + + $data = $request->validate([ + 'pin' => ['required','string','regex:/^\d{4,8}$/'], + ]); + + // Locked? + if (!empty($user->vault_pin_locked_until) && now()->lessThan($user->vault_pin_locked_until)) { + return response()->json([ + 'success' => false, + 'message' => 'Vault PIN locked. Try again later.', + 'locked_until' => optional($user->vault_pin_locked_until)->toIso8601String(), + ], 423); + } + if (empty($user->vault_pin_hash)) { + return response()->json(['success' => false, 'message' => 'Vault PIN not set'], 400); + } + if (!Hash::check((string) $data['pin'], $user->vault_pin_hash)) { + $attempts = (int) ($user->vault_pin_attempts ?? 0) + 1; + $user->vault_pin_attempts = $attempts; + if ($attempts >= 5) { + $user->vault_pin_locked_until = now()->addMinutes(15); + $user->vault_pin_attempts = 0; + } + $user->save(); + return response()->json(['success' => false, 'message' => 'Invalid PIN'], 423); + } + // OK + if (!empty($user->vault_pin_attempts)) { + $user->vault_pin_attempts = 0; + $user->save(); + } + return response()->json([ + 'success' => true, + 'message' => 'Verified', + ], 200); + } +} diff --git a/app/Http/Controllers/VipController.php b/app/Http/Controllers/VipController.php new file mode 100644 index 0000000..04a5779 --- /dev/null +++ b/app/Http/Controllers/VipController.php @@ -0,0 +1,85 @@ +user(); + $defaultProps = [ + 'claimedLevels' => [], + 'cashRewards' => [], + 'userStats' => $user ? [ + 'vip_level' => $user->vip_level ?? 0, + 'vip_points' => $user->stats?->vip_points ?? 0, + ] : null, + 'userVipLevel' => $user?->vip_level ?? 0, + ]; + + try { + $res = $this->client->get($request, '/vip-levels', [], retry: true); + if ($res->successful()) { + $j = $res->json() ?: []; + return Inertia::render('VipLevels', [ + 'claimedLevels' => $j['claimedLevels'] ?? $j['claimed_levels'] ?? [], + 'cashRewards' => $j['cashRewards'] ?? $j['rewards'] ?? [], + 'userStats' => $j['userStats'] ?? $j['stats'] ?? $defaultProps['userStats'], + 'userVipLevel' => $j['userVipLevel'] ?? $j['vip_level'] ?? $defaultProps['userVipLevel'], + ]); + } + } catch (\Throwable $e) { + // Fall through to local fallback + } + + // Render page with local data when external API is unavailable + return Inertia::render('VipLevels', $defaultProps); + } + + /** + * Claim VIP reward → proxy to external API. + */ + public function claim(Request $request) + { + $data = $request->validate([ + 'level' => 'required|integer|min:1|max:100', + ]); + try { + $res = $this->client->post($request, '/vip-levels/claim', [ + 'level' => (int) $data['level'], + ]); + if ($res->successful()) { + $body = $res->json() ?: []; + // Backward compat: ensure a success message + if (!isset($body['message'])) { + $body['message'] = 'Reward claimed successfully!'; + } + return back()->with('success', $body['message']); + } + if ($res->clientError()) { + $msg = data_get($res->json(), 'message', 'Invalid request'); + return back()->withErrors(['message' => $msg]); + } + if ($res->serverError()) { + return back()->withErrors(['message' => 'Service temporarily unavailable']); + } + return back()->withErrors(['message' => 'API server not reachable']); + } catch (\Throwable $e) { + return back()->withErrors(['message' => 'API server not reachable']); + } + } +} diff --git a/app/Http/Controllers/WalletController.php b/app/Http/Controllers/WalletController.php new file mode 100644 index 0000000..c44b716 --- /dev/null +++ b/app/Http/Controllers/WalletController.php @@ -0,0 +1,89 @@ +balance ?? '0'); + + return Inertia::render('Wallet', [ + 'wallets' => $wallets, + 'btxBalance' => $btxBalance, + ]); + } + + /** + * GET /api/wallet/balance — return current authenticated user balance + */ + public function balance(Request $request) + { + $user = Auth::user(); + if (!$user) return response()->json(['error' => 'unauthorized'], 401); + + return response()->json([ + 'balance' => (string) $user->balance, + 'btx_balance' => (string) $user->balance, + // Add other currencies if user has multiple wallets + 'wallets' => $user->wallets()->get()->map(fn($w) => [ + 'currency' => $w->currency, + 'balance' => (string) $w->balance, + ]), + ]); + } + + /** + * GET /wallet/bets — fetch user's game bets + */ + public function bets(Request $request) + { + $user = Auth::user(); + abort_unless($user, 403); + + $query = GameBet::where('user_id', $user->id); + + if ($request->filled('search')) { + $query->where('game_name', 'like', '%' . $request->input('search') . '%'); + } + + $sort = $request->input('sort', 'created_at'); + $order = $request->input('order', 'desc'); + + if (in_array($sort, ['created_at', 'wager_amount', 'payout_amount', 'payout_multiplier', 'game_name'])) { + $query->orderBy($sort, $order === 'asc' ? 'asc' : 'desc'); + } + + $bets = $query->paginate(20)->through(function ($bet) { + return [ + 'id' => $bet->id, + 'game_name' => $bet->game_name, + 'wager_amount' => (string) $bet->wager_amount, + 'payout_multiplier' => (string) $bet->payout_multiplier, + 'payout_amount' => (string) $bet->payout_amount, + 'currency' => $bet->currency, + 'created_at' => $bet->created_at->toIso8601String(), + ]; + }); + + return response()->json($bets); + } +} diff --git a/app/Http/Middleware/CheckBanned.php b/app/Http/Middleware/CheckBanned.php new file mode 100644 index 0000000..b116aea --- /dev/null +++ b/app/Http/Middleware/CheckBanned.php @@ -0,0 +1,25 @@ +is_banned) { + // Get reason + $ban = Auth::user()->restrictions()->where('type', 'account_ban')->where('active', true)->first(); + + return Inertia::render('Errors/Banned', [ + 'reason' => $ban ? $ban->reason : 'No reason provided.' + ])->toResponse($request)->setStatusCode(403); + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/DetectCiphertextInJson.php b/app/Http/Middleware/DetectCiphertextInJson.php new file mode 100644 index 0000000..5f8f9f3 --- /dev/null +++ b/app/Http/Middleware/DetectCiphertextInJson.php @@ -0,0 +1,43 @@ +environment(['local', 'development', 'staging'])) { + $contentType = (string) $response->headers->get('Content-Type', ''); + if (str_contains($contentType, 'application/json')) { + $body = (string) $response->getContent(); + if ($this->containsLaravelCiphertext($body)) { + // Log minimal info without PII values + logger()->warning('Ciphertext detected in JSON response', [ + 'path' => $request->path(), + 'method' => $request->getMethod(), + ]); + + // If you prefer to hard-block in dev/stage, uncomment: + // return response()->json(['error' => 'Ciphertext detected in response'], 422); + } + } + } + + return $response; + } + + private function containsLaravelCiphertext(string $json): bool + { + // Heuristic: look for base64-encoded JSON blobs and typical Laravel keys iv/value/mac + if (!str_contains($json, 'eyJ')) { + return false; + } + return (str_contains($json, '"iv"') && str_contains($json, '"value"') && str_contains($json, '"mac"')); + } +} diff --git a/app/Http/Middleware/EnforceRestriction.php b/app/Http/Middleware/EnforceRestriction.php new file mode 100644 index 0000000..54158ff --- /dev/null +++ b/app/Http/Middleware/EnforceRestriction.php @@ -0,0 +1,67 @@ + $types One or more restriction types to check + */ + public function handle(Request $request, Closure $next, ...$types) + { + $user = $request->user(); + if (!$user) { + return $next($request); + } + + if (empty($types)) { + $types = ['account_ban']; + } + + try { + // Ask upstream whether any of the requested types are active for the current user + $query = ['types' => implode(',', $types)]; + $res = $this->client->get($request, '/users/me/restrictions/check', $query, retry: true); + if ($res->successful()) { + $json = $res->json() ?: []; + $data = $json['data'] ?? $json; // support either {data:{...}} or flat map + $isRestricted = false; + $hitType = null; + foreach ($types as $t) { + if (!empty($data[$t])) { $isRestricted = true; $hitType = $t; break; } + } + + if ($isRestricted) { + // For web requests: log out if account is banned and block access + if (!$request->expectsJson() && in_array('account_ban', $types, true)) { + Auth::logout(); + $request->session()->invalidate(); + $request->session()->regenerateToken(); + } + + $payload = [ + 'message' => 'Access is restricted for this action.', + 'type' => $hitType, + ]; + + return response()->json($payload, 403); + } + } + // On client/server error we fail open to avoid locking out users due to upstream outage + } catch (\Throwable $e) { + // swallow and continue + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/GeoBlockMiddleware.php b/app/Http/Middleware/GeoBlockMiddleware.php new file mode 100644 index 0000000..f9ddf2b --- /dev/null +++ b/app/Http/Middleware/GeoBlockMiddleware.php @@ -0,0 +1,168 @@ +shouldBypass($request)) { + return $next($request); + } + + $settings = AppSetting::get('geo.settings', []); + + if (empty($settings['enabled'])) { + return $next($request); + } + + $ip = $request->ip(); + + // Never block localhost in dev + if (in_array($ip, ['127.0.0.1', '::1', '::ffff:127.0.0.1'])) { + return $next($request); + } + + $geoData = $this->fetchGeoData($ip); + $country = $geoData['countryCode'] ?? null; + $isProxy = $geoData['proxy'] ?? false; + $isHosting = $geoData['hosting'] ?? false; + + // VPN / Proxy check + if (!empty($settings['vpn_block'])) { + $vpnProvider = $settings['vpn_provider'] ?? 'none'; + + $isVpn = match ($vpnProvider) { + 'ipqualityscore' => Cache::remember("geo_vpn_iqs_{$ip}", 3600, fn() => $this->checkIpQualityScore($ip, $settings['vpn_api_key'] ?? '')), + 'proxycheck' => Cache::remember("geo_vpn_pc_{$ip}", 3600, fn() => $this->checkProxyCheck($ip, $settings['vpn_api_key'] ?? '')), + default => ($isProxy || $isHosting), // free ip-api.com fallback + }; + + if ($isVpn) { + return $this->blockResponse($request, $settings, 'vpn'); + } + } + + // Country check + if ($country) { + $mode = $settings['mode'] ?? 'blacklist'; + $blocked = array_map('strtoupper', $settings['blocked_countries'] ?? []); + $allowed = array_map('strtoupper', $settings['allowed_countries'] ?? []); + $upper = strtoupper($country); + + $isBlocked = match ($mode) { + 'whitelist' => !empty($allowed) && !in_array($upper, $allowed), + default => !empty($blocked) && in_array($upper, $blocked), + }; + + if ($isBlocked) { + return $this->blockResponse($request, $settings, 'country'); + } + } + + return $next($request); + } + + private function shouldBypass(Request $request): bool + { + $path = ltrim($request->path(), '/'); + + foreach (self::BYPASS_PATHS as $bypass) { + if ($path === $bypass || str_starts_with($path, $bypass . '/')) { + return true; + } + } + + // Skip API routes + if ($request->is('api/*')) { + return true; + } + + return false; + } + + private function blockResponse(Request $request, array $settings, string $reason) + { + $message = $settings['block_message'] ?? 'This service is not available in your region.'; + $redirectUrl = $settings['redirect_url'] ?? ''; + + if ($redirectUrl) { + return redirect()->away($redirectUrl); + } + + if ($request->header('X-Inertia')) { + return Inertia::render('GeoBlocked', [ + 'message' => $message, + 'reason' => $reason, + ])->toResponse($request)->setStatusCode(403); + } + + return Inertia::render('GeoBlocked', [ + 'message' => $message, + 'reason' => $reason, + ])->toResponse($request)->setStatusCode(403); + } + + private function fetchGeoData(string $ip): array + { + return Cache::remember("geo_data_{$ip}", 3600, function () use ($ip) { + try { + $res = Http::timeout(3)->get("http://ip-api.com/json/{$ip}", [ + 'fields' => 'countryCode,proxy,hosting', + ]); + if ($res->ok()) { + return $res->json() ?? []; + } + } catch (\Throwable) {} + return []; + }); + } + + private function checkIpQualityScore(string $ip, string $apiKey): bool + { + if (!$apiKey) return false; + try { + $res = Http::timeout(4)->get("https://ipqualityscore.com/api/json/ip/{$apiKey}/{$ip}", [ + 'strictness' => 1, + 'allow_public_access_points' => 'true', + 'fast' => 'true', + ]); + if ($res->ok()) { + $data = $res->json(); + return (bool) ($data['vpn'] ?? $data['proxy'] ?? $data['tor'] ?? false); + } + } catch (\Throwable) {} + return false; + } + + private function checkProxyCheck(string $ip, string $apiKey): bool + { + if (!$apiKey) return false; + try { + $res = Http::timeout(4)->get("https://proxycheck.io/v2/{$ip}", [ + 'key' => $apiKey, + 'vpn' => 1, + 'asn' => 0, + ]); + if ($res->ok()) { + $data = $res->json(); + $entry = $data[$ip] ?? []; + return strtolower($entry['proxy'] ?? 'no') === 'yes'; + } + } catch (\Throwable) {} + return false; + } +} diff --git a/app/Http/Middleware/HandleAppearance.php b/app/Http/Middleware/HandleAppearance.php new file mode 100644 index 0000000..f1a02bb --- /dev/null +++ b/app/Http/Middleware/HandleAppearance.php @@ -0,0 +1,23 @@ +cookie('appearance') ?? 'system'); + + return $next($request); + } +} diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php new file mode 100644 index 0000000..48c2d4d --- /dev/null +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -0,0 +1,84 @@ + + */ + public function share(Request $request): array + { + $u = $request->user(); + + // Fully externalized mode: do not load local Eloquent relations here. + // All feature data (stats, wallets, restrictions, etc.) must be fetched via + // the external API through proxy controllers/endpoints. + + return [ + ...parent::share($request), + 'name' => config('app.name'), + 'api_url' => config('app.api_url'), // Pass API URL to frontend + 'locale' => app()->getLocale(), + 'availableLocales' => [ + ['code' => 'en', 'label' => 'English', 'flag' => 'https://flagcdn.com/w20/gb.png'], + ['code' => 'de', 'label' => 'Deutsch', 'flag' => 'https://flagcdn.com/w20/de.png'], + ['code' => 'es', 'label' => 'Español', 'flag' => 'https://flagcdn.com/w20/es.png'], + ['code' => 'pt_BR', 'label' => 'Português (Brasil)', 'flag' => 'https://flagcdn.com/w20/br.png'], + ['code' => 'tr', 'label' => 'Türkçe', 'flag' => 'https://flagcdn.com/w20/tr.png'], + ['code' => 'pl', 'label' => 'Polski', 'flag' => 'https://flagcdn.com/w20/pl.png'], + ], + 'dir' => 'ltr', + 'auth' => [ + 'user' => $u ? [ + 'id' => $u->id, + 'name' => $u->name, + 'username' => $u->username, + 'email' => $u->email, + // Avatar: prefer uploaded DB avatar, fall back to OAuth avatar_url + 'avatar' => $u->avatar, + 'avatar_url' => $u->avatar_url, + 'role' => $u->role, + 'clan_tag' => $u->clan_tag, + 'vip_level' => (int) ($u->vip_level ?? 0), + 'balance' => (string) ($u->balance ?? '0'), + 'stats' => $u->stats ? [ + 'vip_level' => (int) ($u->stats->vip_level ?? 0), + 'vip_points' => (int) ($u->stats->vip_points ?? 0), + ] : null, + 'restrictions' => $u->restrictions() + ->where('active', true) + ->where(fn($q) => $q->whereNull('ends_at')->orWhere('ends_at', '>', now())) + ->get(['type', 'reason', 'ends_at', 'starts_at', 'active']), + ] : null, + ], + 'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true', + ]; + } +} diff --git a/app/Http/Middleware/MaintenanceModeMiddleware.php b/app/Http/Middleware/MaintenanceModeMiddleware.php new file mode 100644 index 0000000..0d79e4f --- /dev/null +++ b/app/Http/Middleware/MaintenanceModeMiddleware.php @@ -0,0 +1,50 @@ +role) === 'admin') { + return $next($request); + } + + // Skip API webhooks and bypass paths + if ($request->is('api/webhooks/*') || $this->shouldBypass($request)) { + return $next($request); + } + + // Skip admin routes for auth + if (str_starts_with($request->path(), 'admin')) { + return $next($request); + } + + $settings = AppSetting::get('site.settings', []); + + if (!empty($settings['maintenance_mode'])) { + return Inertia::render('Maintenance', [ + 'message' => 'Wir führen gerade Wartungsarbeiten durch. Bitte komm später zurück.', + ])->toResponse($request)->setStatusCode(503); + } + + return $next($request); + } + + private function shouldBypass(Request $request): bool + { + foreach (self::BYPASS_PATHS as $path) { + if ($request->is($path) || $request->is($path . '/*')) return true; + } + return false; + } +} diff --git a/app/Http/Middleware/SetLocale.php b/app/Http/Middleware/SetLocale.php new file mode 100644 index 0000000..274d992 --- /dev/null +++ b/app/Http/Middleware/SetLocale.php @@ -0,0 +1,81 @@ + */ + private array $available = [ + 'en','de','es','pt_BR','tr','pl', + // prepared, not yet enabled in UI + 'fr','it','ru','uk','vi','id','zh_CN','ja','ko','sv','no','fi','nl', + ]; + + public function handle(Request $request, Closure $next) + { + $locale = $this->resolveLocale($request); + + // Apply + app()->setLocale($locale); + + // Persist for guests as well (1 year) + $request->session()->put('locale', $locale); + Cookie::queue(cookie('locale', $locale, 60 * 24 * 365)); + + // If logged in and preference changed, consider persisting upstream (no local DB writes) + if ($user = $request->user()) { + if (($user->preferred_locale ?? null) !== $locale) { + // Defer persistence to external API; keep request-scoped preference only. + // Optionally, emit an event or queue a task to sync. + } + } + + return $next($request); + } + + private function resolveLocale(Request $request): string + { + // 1) explicit query param ?lang=xx + $q = $request->query('lang'); + if ($q && $this->isAllowed($q)) return $this->normalize($q); + + // 2) user preference + $u = $request->user(); + if ($u && $this->isAllowed($u->preferred_locale ?? null)) return $this->normalize($u->preferred_locale); + + // 3) session + $s = $request->session()->get('locale'); + if ($this->isAllowed($s)) return $this->normalize((string) $s); + + // 4) cookie + $c = $request->cookie('locale'); + if ($this->isAllowed($c)) return $this->normalize((string) $c); + + // 5) Accept-Language best effort + $preferred = $request->getPreferredLanguage($this->available); + if ($this->isAllowed($preferred)) return $this->normalize((string) $preferred); + + // 6) fallback + return config('app.locale', 'en'); + } + + private function isAllowed($code): bool + { + if (!$code) return false; + $norm = $this->normalize((string) $code); + return in_array($norm, $this->available, true); + } + + private function normalize(string $code): string + { + // Normalize e.g. pt-br → pt_BR, zh-cn → zh_CN; others lower-case + $code = str_replace([' ', '-'], ['','_'], trim($code)); + if (strtolower($code) === 'pt_br') return 'pt_BR'; + if (strtolower($code) === 'zh_cn') return 'zh_CN'; + return strtolower($code); + } +} diff --git a/app/Http/Middleware/ValidateLicenseKey.php b/app/Http/Middleware/ValidateLicenseKey.php new file mode 100644 index 0000000..add3c30 --- /dev/null +++ b/app/Http/Middleware/ValidateLicenseKey.php @@ -0,0 +1,46 @@ +header('X-License-Key') + ?? $request->input('license_key') + ?? $request->query('license_key'); + + if (!$key) { + return response()->json(['error' => 'License key required'], 401); + } + + $casino = OperatorCasino::findByKey((string) $key); + + if (!$casino) { + return response()->json(['error' => 'Invalid license key'], 401); + } + + if (!$casino->isActive()) { + return response()->json(['error' => 'Casino license inactive'], 403); + } + + // IP whitelist — skip when the list is empty + if (!empty($casino->ip_whitelist)) { + $ip = $request->ip(); + if (!in_array($ip, $casino->ip_whitelist, true)) { + return response()->json(['error' => "IP not authorized: {$ip}"], 403); + } + } + + // Attach casino to request so controllers can use it without re-querying + $request->attributes->set('operator_casino', $casino); + + return $next($request); + } +} diff --git a/app/Http/Requests/Settings/PasswordUpdateRequest.php b/app/Http/Requests/Settings/PasswordUpdateRequest.php new file mode 100644 index 0000000..0e5ff2f --- /dev/null +++ b/app/Http/Requests/Settings/PasswordUpdateRequest.php @@ -0,0 +1,25 @@ +|string> + */ + public function rules(): array + { + return [ + 'current_password' => $this->currentPasswordRules(), + 'password' => $this->passwordRules(), + ]; + } +} diff --git a/app/Http/Requests/Settings/ProfileDeleteRequest.php b/app/Http/Requests/Settings/ProfileDeleteRequest.php new file mode 100644 index 0000000..aec9174 --- /dev/null +++ b/app/Http/Requests/Settings/ProfileDeleteRequest.php @@ -0,0 +1,24 @@ +|string> + */ + public function rules(): array + { + return [ + 'password' => $this->currentPasswordRules(), + ]; + } +} diff --git a/app/Http/Requests/Settings/ProfileUpdateRequest.php b/app/Http/Requests/Settings/ProfileUpdateRequest.php new file mode 100644 index 0000000..e4eb8d8 --- /dev/null +++ b/app/Http/Requests/Settings/ProfileUpdateRequest.php @@ -0,0 +1,22 @@ +|string> + */ + public function rules(): array + { + return $this->profileRules($this->user()->id); + } +} diff --git a/app/Http/Requests/Settings/TwoFactorAuthenticationRequest.php b/app/Http/Requests/Settings/TwoFactorAuthenticationRequest.php new file mode 100644 index 0000000..9db81d2 --- /dev/null +++ b/app/Http/Requests/Settings/TwoFactorAuthenticationRequest.php @@ -0,0 +1,30 @@ +|string> + */ + public function rules(): array + { + return []; + } +} diff --git a/app/Mail/SystemNotificationMail.php b/app/Mail/SystemNotificationMail.php new file mode 100644 index 0000000..0df3ccf --- /dev/null +++ b/app/Mail/SystemNotificationMail.php @@ -0,0 +1,258 @@ + */ + public array $data; + + /** + * Create a new message instance. + * + * @param string $type Machine key for notification type (e.g. deposit, withdrawal, kyc_error) + * @param array $payload Arbitrary data to render inside the template + */ + public function __construct(public string $type, array $payload = []) + { + $this->data = $this->buildData($type, $payload); + $this->subject($this->data['subject'] ?? (config('app.name') . ' Notification')); + } + + /** + * Build the message. + */ + public function build() + { + return $this->view('emails.notification') + ->with($this->data); + } + + /** + * Default content map for supported notification types. + * + * @return array> + */ + protected function map(): array + { + $app = config('app.name'); + return [ + // Finance + 'deposit' => [ + 'subject' => "{$app}: Einzahlung eingegangen", + 'title' => 'Einzahlung bestätigt', + 'icon' => '💰', + 'message' => 'Deine Einzahlung wurde erfolgreich verbucht.', + 'cta' => ['label' => 'Wallet öffnen', 'url' => url('/wallet')], + ], + 'withdrawal' => [ + 'subject' => "{$app}: Auszahlung gestartet", + 'title' => 'Auszahlung in Bearbeitung', + 'icon' => '🏦', + 'message' => 'Wir bearbeiten aktuell deine Auszahlung. Du erhältst eine weitere Benachrichtigung, sobald sie abgeschlossen ist.', + 'cta' => ['label' => 'Verlauf ansehen', 'url' => url('/wallet')], + ], + 'bonus_available' => [ + 'subject' => "{$app}: Neuer Bonus verfügbar", + 'title' => 'Bonus verfügbar', + 'icon' => '🎁', + 'message' => 'Es wartet ein neuer Bonus auf dich. Sichere ihn dir, bevor er abläuft!', + 'cta' => ['label' => 'Bonus holen', 'url' => url('/bonuses')], + ], + + // Restrictions + 'banned' => [ + 'subject' => "{$app}: Konto gesperrt", + 'title' => 'Konto gesperrt', + 'icon' => '⛔', + 'message' => 'Dein Konto wurde vorübergehend gesperrt. Bitte kontaktiere den Support für weitere Informationen.', + 'cta' => ['label' => 'Support kontaktieren', 'url' => url('/support')], + ], + 'chat_banned' => [ + 'subject' => "{$app}: Chat eingeschränkt", + 'title' => 'Chat-Bann', + 'icon' => '🚫', + 'message' => 'Dein Chat-Zugang wurde eingeschränkt. Bitte beachte unsere Chat-Richtlinien.', + 'cta' => ['label' => 'Richtlinien lesen', 'url' => url('/legal/terms')], + ], + + // Progress + 'level_up' => [ + 'subject' => "{$app}: Level-Up!", + 'title' => 'Glückwunsch zum Level-Up', + 'icon' => '🚀', + 'message' => 'Du bist ein Level aufgestiegen. Weiter so!', + 'cta' => ['label' => 'VIP ansehen', 'url' => url('/vip-levels')], + ], + 'near_level_up' => [ + 'subject' => "{$app}: Kurz vor Level-Up", + 'title' => 'Fast geschafft', + 'icon' => '✨', + 'message' => 'Du bist kurz davor, das nächste Level zu erreichen. Ein paar Punkte fehlen noch!', + 'cta' => ['label' => 'Jetzt weiterspielen', 'url' => url('/')], + ], + 'inactivity_check' => [ + 'subject' => "{$app}: Vermissen dich! Bist du noch aktiv?", + 'title' => 'Wir vermissen dich', + 'icon' => '👋', + 'message' => 'Wir haben dich eine Weile nicht gesehen. Schau vorbei, es gibt Neuigkeiten und Aktionen!', + 'cta' => ['label' => 'Zurück zu ' . $app, 'url' => url('/')], + ], + 'friend_request' => [ + 'subject' => "{$app}: Neue Freundschaftsanfrage", + 'title' => 'Freundschaftsanfrage', + 'icon' => '🤝', + 'message' => 'Du hast eine neue Freundschaftsanfrage erhalten.', + 'cta' => ['label' => 'Anfragen ansehen', 'url' => url('/friends')], + ], + + // KYC + 'kyc_error' => [ + 'subject' => "{$app}: KYC Fehler", + 'title' => 'KYC nicht erfolgreich', + 'icon' => '⚠️', + 'message' => 'Leider konnte deine Identitätsprüfung nicht abgeschlossen werden. Bitte reiche die benötigten Dokumente erneut ein.', + 'cta' => ['label' => 'KYC erneut starten', 'url' => url('/settings/kyc')], + ], + 'kyc_accepted' => [ + 'subject' => "{$app}: KYC akzeptiert", + 'title' => 'KYC bestätigt', + 'icon' => '✅', + 'message' => 'Deine Identität wurde erfolgreich verifiziert. Vielen Dank!', + 'cta' => ['label' => 'Zum Dashboard', 'url' => url('/dashboard')], + ], + + // Security + 'email_2fa' => [ + 'subject' => "{$app}: 2FA Code", + 'title' => 'Dein 2FA-Code', + 'icon' => '🔐', + 'message' => 'Verwende den folgenden Code, um dich anzumelden oder eine Aktion zu bestätigen.', + ], + + // Policies updates + 'terms_updated' => [ + 'subject' => "{$app}: Nutzungsbedingungen aktualisiert", + 'title' => 'Terms & Conditions aktualisiert', + 'icon' => '📄', + 'message' => 'Wir haben unsere Nutzungsbedingungen aktualisiert.', + 'cta' => ['label' => 'Jetzt lesen', 'url' => url('/legal/terms')], + ], + 'cookie_policy_updated' => [ + 'subject' => "{$app}: Cookie-Richtlinie aktualisiert", + 'title' => 'Cookie Policy aktualisiert', + 'icon' => '🍪', + 'message' => 'Wir haben unsere Cookie-Richtlinie aktualisiert.', + 'cta' => ['label' => 'Details ansehen', 'url' => url('/legal/cookies')], + ], + 'privacy_policy_updated' => [ + 'subject' => "{$app}: Datenschutz aktualisiert", + 'title' => 'Privacy Policy aktualisiert', + 'icon' => '🔏', + 'message' => 'Wir haben unsere Datenschutzerklärung aktualisiert.', + 'cta' => ['label' => 'Details ansehen', 'url' => url('/legal/privacy')], + ], + 'bonus_policy_updated' => [ + 'subject' => "{$app}: Bonus-Richtlinie aktualisiert", + 'title' => 'Bonus Policy aktualisiert', + 'icon' => '🎁', + 'message' => 'Wir haben unsere Bonus-Richtlinie aktualisiert.', + 'cta' => ['label' => 'Details ansehen', 'url' => url('/legal/bonus-policy')], + ], + 'dispute_policy_updated' => [ + 'subject' => "{$app}: Streitbeilegung aktualisiert", + 'title' => 'Dispute Resolution aktualisiert', + 'icon' => '⚖️', + 'message' => 'Wir haben unsere Richtlinie zur Streitbeilegung aktualisiert.', + 'cta' => ['label' => 'Details ansehen', 'url' => url('/legal/disputes')], + ], + 'responsible_gaming_updated' => [ + 'subject' => "{$app}: Verantwortungsvolles Spielen", + 'title' => 'Responsible Gaming aktualisiert', + 'icon' => '🎯', + 'message' => 'Wir haben unsere Informationen zum verantwortungsvollen Spielen aktualisiert.', + 'cta' => ['label' => 'Mehr erfahren', 'url' => url('/legal/responsible-gaming')], + ], + 'aml_policy_updated' => [ + 'subject' => "{$app}: AML-Policy aktualisiert", + 'title' => 'AML Policy aktualisiert', + 'icon' => '🛡️', + 'message' => 'Wir haben unsere AML-Richtlinie aktualisiert.', + 'cta' => ['label' => 'Mehr erfahren', 'url' => url('/legal/aml')], + ], + 'risk_warnings_updated' => [ + 'subject' => "{$app}: Risikohinweise aktualisiert", + 'title' => 'Risk Warnings aktualisiert', + 'icon' => '⚠️', + 'message' => 'Wir haben unsere Risikohinweise aktualisiert.', + 'cta' => ['label' => 'Jetzt lesen', 'url' => url('/legal/risk-warnings')], + ], + + // Support / Misc + 'new_support_message' => [ + 'subject' => "{$app}: Neue Support-Nachricht", + 'title' => 'Neue Support-Nachricht', + 'icon' => '💬', + 'message' => 'Du hast eine neue Nachricht von unserem Support-Team erhalten.', + 'cta' => ['label' => 'Support-Chat öffnen', 'url' => url('/support')], + ], + 'casino_updated' => [ + 'subject' => "{$app}: Casino aktualisiert", + 'title' => 'Neue Spiele & Updates', + 'icon' => '🎲', + 'message' => 'Es gibt neue Inhalte, Spiele oder Aktionen im Casino. Schau rein! ', + 'cta' => ['label' => 'Jetzt entdecken', 'url' => url('/')], + ], + ]; + } + + /** + * Build final data array for the template. + * + * @param array $payload + * @return array + */ + protected function buildData(string $type, array $payload): array + { + $map = $this->map(); + $defaults = $map[$type] ?? [ + 'subject' => config('app.name') . ' Notification', + 'title' => Str::headline(str_replace('_', ' ', $type)), + 'icon' => '🔔', + 'message' => 'Es gibt Neuigkeiten zu deinem Konto.', + ]; + + // Merge with payload overrides + $data = array_merge($defaults, $payload); + + // Normalize CTA structure + $cta = $data['cta'] ?? null; + if (is_string($cta)) { + $cta = ['label' => 'Open', 'url' => $cta]; + } + $data['cta'] = is_array($cta) ? $cta : null; + + // Email 2FA code convenience + if ($type === 'email_2fa' && empty($data['code']) && !empty($payload['token'])) { + $data['code'] = (string) $payload['token']; + } + + // Allow bullet points list + if (!empty($data['bullets']) && is_array($data['bullets'])) { + $data['bullets'] = array_values(array_filter($data['bullets'], fn ($v) => filled($v))); + } + + $data['type'] = $type; + + return $data; + } +} diff --git a/app/Models/AppSetting.php b/app/Models/AppSetting.php new file mode 100644 index 0000000..328c5c1 --- /dev/null +++ b/app/Models/AppSetting.php @@ -0,0 +1,30 @@ + 'array', + ]; + + public static function get(string $key, $default = null) + { + $row = static::query()->where('key', $key)->first(); + return $row?->value ?? $default; + } + + public static function put(string $key, $value): void + { + static::updateOrCreate(['key' => $key], ['value' => $value]); + } +} diff --git a/app/Models/Bonus.php b/app/Models/Bonus.php new file mode 100644 index 0000000..9d8197c --- /dev/null +++ b/app/Models/Bonus.php @@ -0,0 +1,51 @@ + 'decimal:8', + 'min_deposit' => 'decimal:8', + 'max_amount' => 'decimal:8', + 'starts_at' => 'datetime', + 'expires_at' => 'datetime', + 'rules' => 'array', + ]; + + public function scopeActive($query) + { + $now = now(); + return $query->where('status', 'active') + ->when($now, function ($q) use ($now) { + $q->where(function ($qq) use ($now) { + $qq->whereNull('starts_at')->orWhere('starts_at', '<=', $now); + })->where(function ($qq) use ($now) { + $qq->whereNull('expires_at')->orWhere('expires_at', '>=', $now); + }); + }); + } +} diff --git a/app/Models/ChatMessage.php b/app/Models/ChatMessage.php new file mode 100644 index 0000000..83940ab --- /dev/null +++ b/app/Models/ChatMessage.php @@ -0,0 +1,54 @@ + 'encrypted', + ]; + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function replyTo(): BelongsTo + { + return $this->belongsTo(self::class, 'reply_to_id'); + } + + public function replies(): HasMany + { + return $this->hasMany(self::class, 'reply_to_id'); + } + + public function reactions(): HasMany + { + return $this->hasMany(ChatMessageReaction::class, 'message_id'); + } + + public function deletedByUser(): BelongsTo + { + return $this->belongsTo(User::class, 'deleted_by'); + } +} diff --git a/app/Models/ChatMessageReaction.php b/app/Models/ChatMessageReaction.php new file mode 100644 index 0000000..1b31867 --- /dev/null +++ b/app/Models/ChatMessageReaction.php @@ -0,0 +1,27 @@ +belongsTo(User::class); + } + + public function message() + { + return $this->belongsTo(ChatMessage::class); + } +} diff --git a/app/Models/ChatMessageReport.php b/app/Models/ChatMessageReport.php new file mode 100644 index 0000000..d628ae9 --- /dev/null +++ b/app/Models/ChatMessageReport.php @@ -0,0 +1,29 @@ + 'array', + ]; + + public function reporter() + { + return $this->belongsTo(User::class, 'reporter_id'); + } +} diff --git a/app/Models/CryptoPayment.php b/app/Models/CryptoPayment.php new file mode 100644 index 0000000..96f0921 --- /dev/null +++ b/app/Models/CryptoPayment.php @@ -0,0 +1,49 @@ + 'decimal:18', + 'actually_paid' => 'decimal:18', + 'price_amount' => 'decimal:8', + 'exchange_rate_at_payment' => 'decimal:12', + 'fee' => 'decimal:18', + 'tx_hash' => 'array', + 'raw_payload' => 'array', + 'credited_btx' => 'decimal:8', + 'credited_at' => 'datetime', + ]; + + public function user() + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/DirectMessage.php b/app/Models/DirectMessage.php new file mode 100644 index 0000000..833d8f7 --- /dev/null +++ b/app/Models/DirectMessage.php @@ -0,0 +1,38 @@ + 'boolean', + 'is_deleted' => 'boolean', + ]; + + public function sender() + { + return $this->belongsTo(User::class, 'sender_id'); + } + + public function receiver() + { + return $this->belongsTo(User::class, 'receiver_id'); + } + + public function replyTo() + { + return $this->belongsTo(DirectMessage::class, 'reply_to_id'); + } +} diff --git a/app/Models/DirectMessageReport.php b/app/Models/DirectMessageReport.php new file mode 100644 index 0000000..7b6be58 --- /dev/null +++ b/app/Models/DirectMessageReport.php @@ -0,0 +1,26 @@ +belongsTo(User::class, 'reporter_id'); + } + + public function message() + { + return $this->belongsTo(DirectMessage::class, 'message_id'); + } +} diff --git a/app/Models/Friend.php b/app/Models/Friend.php new file mode 100644 index 0000000..7e9391b --- /dev/null +++ b/app/Models/Friend.php @@ -0,0 +1,23 @@ +belongsTo(User::class); + } + + public function friend() + { + return $this->belongsTo(User::class, 'friend_id'); + } +} diff --git a/app/Models/GameBet.php b/app/Models/GameBet.php new file mode 100644 index 0000000..99d2c57 --- /dev/null +++ b/app/Models/GameBet.php @@ -0,0 +1,24 @@ + 'decimal:8', + 'payout_multiplier' => 'decimal:4', + 'payout_amount' => 'decimal:8', + ]; + + public function user() + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/Guild.php b/app/Models/Guild.php new file mode 100644 index 0000000..cf3ef04 --- /dev/null +++ b/app/Models/Guild.php @@ -0,0 +1,43 @@ +belongsTo(User::class, 'owner_id'); + } + + /** + * Die Mitglieder der Gilde (Verknüpfung zu Users über die Pivot-Tabelle). + * Dies ermöglicht die Nutzung von attach(), detach() und sync(). + */ + public function members(): BelongsToMany + { + return $this->belongsToMany(User::class, 'guild_members') + ->withPivot('role', 'joined_at') + ->withTimestamps(); + } +} diff --git a/app/Models/GuildMember.php b/app/Models/GuildMember.php new file mode 100644 index 0000000..9cfe0c2 --- /dev/null +++ b/app/Models/GuildMember.php @@ -0,0 +1,34 @@ + 'datetime', + 'wagered' => 'decimal:4', + ]; + + public function user() + { + return $this->belongsTo(User::class); + } + + public function guild() + { + return $this->belongsTo(Guild::class); + } +} diff --git a/app/Models/GuildMessage.php b/app/Models/GuildMessage.php new file mode 100644 index 0000000..dec4a7f --- /dev/null +++ b/app/Models/GuildMessage.php @@ -0,0 +1,36 @@ + 'boolean', + ]; + + public function user() + { + return $this->belongsTo(User::class); + } + + public function guild() + { + return $this->belongsTo(Guild::class); + } + + public function replyTo() + { + return $this->belongsTo(GuildMessage::class, 'reply_to_id'); + } +} diff --git a/app/Models/KycDocument.php b/app/Models/KycDocument.php new file mode 100644 index 0000000..eebb265 --- /dev/null +++ b/app/Models/KycDocument.php @@ -0,0 +1,37 @@ + 'datetime', + 'reviewed_at' => 'datetime', + ]; + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/OperatorCasino.php b/app/Models/OperatorCasino.php new file mode 100644 index 0000000..a4337be --- /dev/null +++ b/app/Models/OperatorCasino.php @@ -0,0 +1,40 @@ + 'array', + 'domain_whitelist' => 'array', + ]; + + /** + * Look up a casino by its plaintext license key. + * Hashes the key and queries by hash — plaintext is never stored. + */ + public static function findByKey(string $key): ?self + { + return static::where('license_key_hash', hash('sha256', $key))->first(); + } + + public function isActive(): bool + { + return $this->status === 'active'; + } + + public function sessions() + { + return $this->hasMany(OperatorSession::class); + } +} diff --git a/app/Models/OperatorSession.php b/app/Models/OperatorSession.php new file mode 100644 index 0000000..1f6ad50 --- /dev/null +++ b/app/Models/OperatorSession.php @@ -0,0 +1,52 @@ + 'decimal:4', + 'current_balance' => 'decimal:4', + 'expires_at' => 'datetime', + ]; + + public function casino() + { + return $this->belongsTo(OperatorCasino::class, 'operator_casino_id'); + } + + public function isExpired(): bool + { + return $this->expires_at->isPast() || $this->status !== 'active'; + } + + /** + * Mark expired session if its expiry timestamp has passed. + * Returns true if the status was just changed. + */ + public function expireIfNeeded(): bool + { + if ($this->status === 'active' && $this->expires_at->isPast()) { + $this->update(['status' => 'expired']); + return true; + } + return false; + } +} diff --git a/app/Models/ProfileComment.php b/app/Models/ProfileComment.php new file mode 100644 index 0000000..db2e0cd --- /dev/null +++ b/app/Models/ProfileComment.php @@ -0,0 +1,23 @@ +belongsTo(User::class); + } + + public function profile() + { + return $this->belongsTo(User::class, 'profile_id'); + } +} diff --git a/app/Models/ProfileLike.php b/app/Models/ProfileLike.php new file mode 100644 index 0000000..d96ff20 --- /dev/null +++ b/app/Models/ProfileLike.php @@ -0,0 +1,23 @@ +belongsTo(User::class); + } + + public function profile() + { + return $this->belongsTo(User::class, 'profile_id'); + } +} diff --git a/app/Models/ProfileReport.php b/app/Models/ProfileReport.php new file mode 100644 index 0000000..82a6b27 --- /dev/null +++ b/app/Models/ProfileReport.php @@ -0,0 +1,27 @@ + 'array', + ]; + + public function reporter() + { + return $this->belongsTo(User::class, 'reporter_id'); + } + + public function profile() + { + return $this->belongsTo(User::class, 'profile_id'); + } +} diff --git a/app/Models/Promo.php b/app/Models/Promo.php new file mode 100644 index 0000000..4c7a6a9 --- /dev/null +++ b/app/Models/Promo.php @@ -0,0 +1,48 @@ + 'decimal:4', + 'min_deposit' => 'decimal:4', + 'starts_at' => 'datetime', + 'ends_at' => 'datetime', + 'is_active' => 'boolean', + 'bonus_expires_days' => 'integer', + 'wager_multiplier' => 'integer', + 'per_user_limit' => 'integer', + 'global_limit' => 'integer', + ]; + + public function usages(): HasMany + { + return $this->hasMany(PromoUsage::class); + } + + public function userBonuses(): HasMany + { + return $this->hasMany(UserBonus::class); + } +} diff --git a/app/Models/PromoUsage.php b/app/Models/PromoUsage.php new file mode 100644 index 0000000..39acc7c --- /dev/null +++ b/app/Models/PromoUsage.php @@ -0,0 +1,34 @@ + 'datetime', + ]; + + public function promo(): BelongsTo + { + return $this->belongsTo(Promo::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/Tip.php b/app/Models/Tip.php new file mode 100644 index 0000000..f89ca47 --- /dev/null +++ b/app/Models/Tip.php @@ -0,0 +1,19 @@ + */ + use HasFactory, Notifiable, TwoFactorAuthenticatable; + + /** + * The attributes that are mass assignable. + * + * @var list + */ + protected $fillable = [ + 'name', + 'email', + 'email_index', // Blind Index for lookups + 'username', + 'username_index', // Blind Index for lookups + 'avatar_url', // Profile image for chat/header + 'role', // Role badge (e.g., Admin, Streamer, Co) + 'clan_tag', // Clan short tag badge + // Profile fields + 'first_name', + 'last_name', + 'gender', + 'birthdate', + 'phone', + 'country', + 'address_line1', + 'address_line2', + 'city', + 'state', + 'postal_code', + 'currency', + 'is_adult', + 'password', + 'last_login_at', + 'last_login_ip', + 'last_login_user_agent', + 'balance', // BTX Balance + 'vip_level', + 'vault_balance', + 'vault_balances', + 'preferred_locale', + // Social Profile Fields + 'is_public', + 'bio', + 'avatar', + 'banner', + 'is_banned', // Added + 'withdraw_cooldown_until', + 'registration_ip', + ]; + + /** + * The attributes that should be hidden for serialization. + * + * @var list + */ + protected $hidden = [ + 'password', + 'two_factor_secret', + 'two_factor_recovery_codes', + 'remember_token', + 'birthdate', // Hide sensitive data + 'phone', // Hide sensitive data + 'email_index', + 'username_index', + 'last_login_ip', + 'last_login_user_agent', + ]; + + /** + * Get the attributes that should be cast. + * + * @return array + */ + protected function casts(): array + { + return [ + 'email_verified_at' => 'datetime', + 'password' => 'hashed', + 'two_factor_confirmed_at' => 'datetime', + 'email' => SafeEncryptedString::class, + 'is_adult' => 'boolean', + 'last_login_at' => 'datetime', + 'balance' => 'decimal:4', + 'vip_level' => 'integer', + 'vault_balance' => 'decimal:4', // Store and handle as plaintext decimal now + 'vault_balances' => 'array', // JSON map: {"BTC":"0","ETH":"0","SOL":"0"} + 'is_public' => 'boolean', + 'is_banned' => 'boolean', // Added + // Vault PIN-related + 'vault_pin_set_at' => 'datetime', + 'vault_pin_locked_until' => 'datetime', + 'vault_pin_attempts' => 'integer', + 'withdraw_cooldown_until' => 'datetime', + ]; + } + + /** + * Automatically hash email and username for blind indexing on save. + */ + protected static function booted(): void + { + static::saving(function (User $user) { + if ($user->isDirty('email')) { + // Store email hash in lowercase for case-insensitive lookup + $user->email_index = hash('sha256', strtolower($user->email)); + } + if ($user->isDirty('username')) { + // Store username hash in lowercase for case-insensitive lookup + $user->username_index = hash('sha256', strtolower($user->username)); + } + }); + } + + /** + * Get the user's stats. + */ + public function stats() + { + return $this->hasOne(UserStats::class); + } + + /** + * Get the user's wallets. + */ + public function wallets() + { + return $this->hasMany(Wallet::class); + } + + /** + * Get the user's guild membership. + */ + public function guildMember() + { + return $this->hasOne(GuildMember::class); + } + + /** + * Get all restrictions for the user. + */ + public function restrictions() + { + return $this->hasMany(UserRestriction::class); + } + + /** + * Check if the user has an active account ban. + * + * @return \Illuminate\Database\Eloquent\Casts\Attribute + */ + protected function isBanned(): \Illuminate\Database\Eloquent\Casts\Attribute + { + return \Illuminate\Database\Eloquent\Casts\Attribute::make( + get: fn () => $this->restrictions()->where('type', 'account_ban')->where('active', true)->exists(), + ); + } + + /** + * Get the email address that should be used for password reset. + * + * @return string + */ + public function getEmailForPasswordReset() + { + return $this->email; // Use the actual email + } + + /** + * Send the email verification notification. + * + * @return void + */ + public function sendEmailVerificationNotification() + { + $this->notify(new VerifyEmail); + } + + /** + * Send the password reset notification. + * + * @param string $token + * @return void + */ + public function sendPasswordResetNotification($token) + { + $this->notify(new ResetPassword($token)); + } + + /** + * Ensure MailChannel gets a plaintext, RFC-compliant email address. + * If the stored value is still encrypted/malformed, try to decrypt + * using configured keys. If still invalid, log and return null to skip send. + * + * Returning null will prevent the Mail channel from sending and avoids + * Symfony RfcComplianceException while we fix upstream data/keys. + * + * @return string|array|null + */ + public function routeNotificationForMail(): string|array|null + { + // 1) Try the casted value first (should be plaintext if keys are aligned) + $candidates = []; + $casted = $this->email; + if (is_string($casted)) { + $candidates[] = $casted; + } + + // 2) Try decrypting the raw DB value explicitly if it looks encrypted + $raw = (string) $this->getRawOriginal('email'); + if ($raw !== '') { + $candidates[] = $raw; + $decrypted = $this->tryDecrypt($raw); + if ($decrypted !== null) { + $candidates[] = $decrypted; + } + } + + // 3) As last resort, try decrypting the casted value too (in case a layer re-wrapped it) + if (is_string($casted)) { + $dec2 = $this->tryDecrypt($casted); + if ($dec2 !== null) { + $candidates[] = $dec2; + } + } + + foreach ($candidates as $value) { + if (is_string($value) && $this->isValidEmail($value)) { + return $value; + } + } + + // No valid address could be obtained. Log once per user session/context. + Log::warning('Unable to derive plaintext email for mail routing; skipping send to avoid RFC error', [ + 'user_id' => $this->id, + 'env' => app()->environment(), + ]); + + // In local/dev, you may prefer to route to MAIL_FROM_ADDRESS to unblock flows: + if (app()->environment(['local', 'development'])) { + $fallback = (string) config('mail.from.address'); + if ($this->isValidEmail($fallback)) { + return $fallback; + } + } + + // Returning null prevents MailChannel from attempting a send + return null; + } + + private function tryDecrypt(?string $value): ?string + { + if (!is_string($value) || $value === '') { + return null; + } + if (!$this->looksEncrypted($value)) { + return null; + } + try { + return Crypt::decryptString($value); + } catch (\Throwable $e) { + return null; + } + } + + private function isValidEmail(string $email): bool + { + return filter_var($email, FILTER_VALIDATE_EMAIL) !== false; + } + + private function looksEncrypted(string $value): bool + { + $decoded = base64_decode($value, true); + if ($decoded === false) { + return false; + } + $json = json_decode($decoded, true); + if (!is_array($json)) { + return false; + } + return isset($json['iv'], $json['value'], $json['mac']); + } +} diff --git a/app/Models/UserAchievement.php b/app/Models/UserAchievement.php new file mode 100644 index 0000000..eab3881 --- /dev/null +++ b/app/Models/UserAchievement.php @@ -0,0 +1,23 @@ + 'datetime', + ]; + + public function user() + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/UserBonus.php b/app/Models/UserBonus.php new file mode 100644 index 0000000..e80a7e0 --- /dev/null +++ b/app/Models/UserBonus.php @@ -0,0 +1,42 @@ + 'decimal:4', + 'wager_required' => 'decimal:4', + 'wager_progress' => 'decimal:4', + 'expires_at' => 'datetime', + 'is_active' => 'boolean', + 'completed_at' => 'datetime', + ]; + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function promo(): BelongsTo + { + return $this->belongsTo(Promo::class); + } +} diff --git a/app/Models/UserFavorite.php b/app/Models/UserFavorite.php new file mode 100644 index 0000000..896c193 --- /dev/null +++ b/app/Models/UserFavorite.php @@ -0,0 +1,21 @@ +belongsTo(User::class); + } +} diff --git a/app/Models/UserFeedback.php b/app/Models/UserFeedback.php new file mode 100644 index 0000000..a9dfbeb --- /dev/null +++ b/app/Models/UserFeedback.php @@ -0,0 +1,32 @@ + 'boolean', + 'overall_rating' => 'integer', + 'ux_rating' => 'integer', + 'comfort_rating' => 'integer', + 'mobile_rating' => 'integer', + 'nps_score' => 'integer', + ]; + + public function user() + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/UserRestriction.php b/app/Models/UserRestriction.php new file mode 100644 index 0000000..7f97767 --- /dev/null +++ b/app/Models/UserRestriction.php @@ -0,0 +1,61 @@ + 'datetime', + 'ends_at' => 'datetime', + 'active' => 'boolean', + 'metadata' => 'array', + ]; + + public function user() + { + return $this->belongsTo(User::class); + } + + public function imposer() + { + return $this->belongsTo(User::class, 'imposed_by'); + } + + /** + * Scope a query to only include currently-active restrictions. + * Active means: + * - active flag is true AND + * - (starts_at is null OR starts_at <= now) AND + * - (ends_at is null OR ends_at > now) + */ + public function scopeActive($query) + { + $now = now(); + return $query->where('active', true) + ->where(function ($q) use ($now) { + $q->whereNull('starts_at')->orWhere('starts_at', '<=', $now); + }) + ->where(function ($q) use ($now) { + $q->whereNull('ends_at')->orWhere('ends_at', '>', $now); + }); + } +} diff --git a/app/Models/UserStats.php b/app/Models/UserStats.php new file mode 100644 index 0000000..a561348 --- /dev/null +++ b/app/Models/UserStats.php @@ -0,0 +1,40 @@ + 'encrypted', + 'total_won' => 'encrypted', + 'total_lost' => 'encrypted', + 'biggest_win' => 'encrypted', + 'total_wins' => 'integer', + 'vip_points' => 'integer', // not encrypted in migration + 'last_activity' => 'datetime', + ]; + + public function user() + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/VaultTransfer.php b/app/Models/VaultTransfer.php new file mode 100644 index 0000000..27a8d29 --- /dev/null +++ b/app/Models/VaultTransfer.php @@ -0,0 +1,51 @@ + EncryptedDecimal::class.':4', + 'main_balance_before' => EncryptedDecimal::class.':4', + 'main_balance_after' => EncryptedDecimal::class.':4', + 'vault_balance_before' => EncryptedDecimal::class.':4', + 'vault_balance_after' => EncryptedDecimal::class.':4', + 'metadata' => AsEncryptedArrayObject::class, + ]; + + public function user() + { + return $this->belongsTo(User::class); + } + + public function scopeForUser($query, int $userId) + { + return $query->where('user_id', $userId); + } + + public function scopeRecent($query) + { + return $query->orderByDesc('id'); + } +} diff --git a/app/Models/Wallet.php b/app/Models/Wallet.php new file mode 100644 index 0000000..716db7f --- /dev/null +++ b/app/Models/Wallet.php @@ -0,0 +1,24 @@ +belongsTo(User::class); + } +} diff --git a/app/Models/WalletTransfer.php b/app/Models/WalletTransfer.php new file mode 100644 index 0000000..4a32857 --- /dev/null +++ b/app/Models/WalletTransfer.php @@ -0,0 +1,38 @@ + 'decimal:4', + 'balance_before' => 'decimal:4', + 'balance_after' => 'decimal:4', + 'vault_before' => 'decimal:4', + 'vault_after' => 'decimal:4', + 'meta' => 'array', + ]; + + public function user() + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Notifications/FriendRequestNotification.php b/app/Notifications/FriendRequestNotification.php new file mode 100644 index 0000000..51512d6 --- /dev/null +++ b/app/Notifications/FriendRequestNotification.php @@ -0,0 +1,45 @@ +senderId = $senderId; + $this->senderUsername = $senderUsername; + $this->senderAvatar = $senderAvatar; + $this->friendRequestId = $friendRequestId; + } + + public function via(object $notifiable): array + { + return ['database']; + } + + public function toArray(object $notifiable): array + { + return [ + 'kind' => 'friend_request', + 'icon' => 'user-plus', + 'title' => 'Friend request', + 'desc' => $this->senderUsername.' sent you a friend request', + 'sender' => [ + 'id' => $this->senderId, + 'username' => $this->senderUsername, + 'avatar' => $this->senderAvatar, + ], + 'friend_request_id' => $this->friendRequestId, + ]; + } +} diff --git a/app/Notifications/NewLoginDetected.php b/app/Notifications/NewLoginDetected.php new file mode 100644 index 0000000..40745c9 --- /dev/null +++ b/app/Notifications/NewLoginDetected.php @@ -0,0 +1,44 @@ +ip = $ip; + $this->userAgent = $userAgent; + $this->loginAt = $loginAt; + } + + public function via(object $notifiable): array + { + return ['mail']; + } + + public function toMail(object $notifiable): MailMessage + { + return (new MailMessage) + ->subject('New Login Detected on Your Account') + ->view('emails.new-login', [ + 'user' => $notifiable, + 'ip' => $this->ip, + 'userAgent' => $this->userAgent, + 'loginAt' => $this->loginAt, + 'appName' => config('app.name'), + 'supportEmail' => config('mail.from.address'), + 'manageLink' => url('/settings/security'), + ]); + } +} diff --git a/app/Notifications/ResetPassword.php b/app/Notifications/ResetPassword.php new file mode 100644 index 0000000..007aca3 --- /dev/null +++ b/app/Notifications/ResetPassword.php @@ -0,0 +1,27 @@ + $this->token, + 'email' => $notifiable->getEmailForPasswordReset(), + ], false)); + + return (new MailMessage) + ->subject('Reset Password Notification') + ->view('emails.reset-password', ['url' => $url, 'user' => $notifiable]); + } +} diff --git a/app/Notifications/VerifyEmail.php b/app/Notifications/VerifyEmail.php new file mode 100644 index 0000000..fc97e19 --- /dev/null +++ b/app/Notifications/VerifyEmail.php @@ -0,0 +1,36 @@ +verificationUrl($notifiable); + + // Generate a 6-digit fallback verification code with same TTL as the link + $ttl = (int) Config::get('auth.verification.expire', 60); + $code = (string) random_int(100000, 999999); + Cache::put('email_verify_code:'.$notifiable->getKey(), $code, now()->addMinutes($ttl)); + + return (new MailMessage) + ->subject('Verify Your Email Address') + ->view('emails.verify-email', [ + 'url' => $verificationUrl, + 'user' => $notifiable, + 'code' => $code, + ]); + } +} diff --git a/app/Policies/ChatMessagePolicy.php b/app/Policies/ChatMessagePolicy.php new file mode 100644 index 0000000..b1d1485 --- /dev/null +++ b/app/Policies/ChatMessagePolicy.php @@ -0,0 +1,30 @@ +hasVerifiedEmail(); + } + return true; + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php new file mode 100644 index 0000000..adb65e3 --- /dev/null +++ b/app/Providers/AppServiceProvider.php @@ -0,0 +1,56 @@ +configureDefaults(); + + Auth::provider('encrypted', function ($app, array $config) { + return new EncryptedUserProvider($app['hash'], $config['model']); + }); + } + + /** + * Configure default behaviors for production-ready applications. + */ + protected function configureDefaults(): void + { + Date::use(CarbonImmutable::class); + + DB::prohibitDestructiveCommands( + app()->isProduction(), + ); + + Password::defaults(fn (): ?Password => app()->isProduction() + ? Password::min(8) + ->mixedCase() + ->letters() + ->numbers() + ->symbols() + ->uncompromised() + : null + ); + } +} diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php new file mode 100644 index 0000000..907e23e --- /dev/null +++ b/app/Providers/FortifyServiceProvider.php @@ -0,0 +1,188 @@ +input('email') ?? $request->input('login'); + if (!$rawLogin) { + return null; + } + Log::info("LOGIN: Attempting login for '$rawLogin'"); + + $lowerLogin = strtolower($rawLogin); + $loginHash = hash('sha256', $rawLogin); + $lowerLoginHash = hash('sha256', $lowerLogin); + + $user = User::where(function ($query) use ($rawLogin, $lowerLogin, $loginHash, $lowerLoginHash) { + $query->where('email', $rawLogin) // Case-sensitive match + ->orWhere('username', $rawLogin) // Case-sensitive match + ->orWhereRaw('LOWER(email) = ?', [$lowerLogin]) // Case-insensitive match + ->orWhereRaw('LOWER(username) = ?', [$lowerLogin]) // Case-insensitive match + ->orWhere('email_index', $loginHash) // Original hash match + ->orWhere('email_index', $lowerLoginHash) // Lowercase hash match + ->orWhere('username_index', $loginHash) // Original hash match + ->orWhere('username_index', $lowerLoginHash); // Lowercase hash match + })->first(); + + if ($user) { + Log::info("LOGIN: User found (ID: {$user->id}). Checking password..."); + $passwordMatches = false; + + // 1. Standard Bcrypt check + if (Hash::check($request->password, $user->password)) { + Log::info("LOGIN: Password matched using Bcrypt (Correct)."); + $passwordMatches = true; + } + // 2. Plaintext check (and upgrade) + elseif ($user->password === $request->password) { + Log::warning("LOGIN: Plaintext password matched for User ID {$user->id}. Upgrading hash now."); + $passwordMatches = true; + $user->forceFill(['password' => Hash::make($request->password)])->save(); + } + // 3. SHA256 check (and upgrade) + elseif (hash('sha256', $request->password) === $user->password) { + Log::warning("LOGIN: SHA256 password matched for User ID {$user->id}. This is insecure. Upgrading hash now."); + $passwordMatches = true; + $user->forceFill(['password' => Hash::make($request->password)])->save(); + } + // 4. MD5 check (and upgrade) + elseif (hash('md5', $request->password) === $user->password) { + Log::warning("LOGIN: MD5 password matched for User ID {$user->id}. This is insecure. Upgrading hash now."); + $passwordMatches = true; + $user->forceFill(['password' => Hash::make($request->password)])->save(); + } + + if ($passwordMatches) { + Log::info("LOGIN: Password check successful for User ID {$user->id}."); + + if (UserRestriction::where('user_id', $user->id)->where('type', 'account_ban')->active()->exists()) { + Log::warning("LOGIN: Login blocked for banned User ID {$user->id}."); + return null; + } + + $user->forceFill([ + 'last_login_at' => now(), + 'last_login_ip' => $request->ip(), + 'last_login_user_agent' => $request->userAgent() ?? '', + ])->save(); + + if ($user->stats) { + $user->stats()->increment('total_logins'); + } + + return $user; + } else { + Log::error("LOGIN: All password checks failed for User ID {$user->id}."); + } + } else { + Log::error("LOGIN: User with identifier '$rawLogin' not found in database."); + } + + return null; + }); + + $this->configureActions(); + $this->configureViews(); + $this->configureRateLimiting(); + } + + private function configureActions(): void + { + Fortify::resetUserPasswordsUsing(ResetUserPassword::class); + Fortify::createUsersUsing(CreateNewUser::class); + } + + private function configureViews(): void + { + Fortify::loginView(fn (Request $request) => Inertia::render('auth/Login', [ + 'canResetPassword' => Features::enabled(Features::resetPasswords()), + 'canRegister' => Features::enabled(Features::registration()), + 'status' => $request->session()->get('status'), + ])); + + Fortify::resetPasswordView(fn (Request $request) => Inertia::render('auth/ResetPassword', [ + 'email' => $request->email, + 'token' => $request->route('token'), + ])); + + Fortify::requestPasswordResetLinkView(fn (Request $request) => Inertia::render('auth/ForgotPassword', [ + 'status' => $request->session()->get('status'), + ])); + + Fortify::verifyEmailView(function (Request $request) { + $user = $request->user(); + $expire = (int) Config::get('auth.verification.expire', 60); + $verificationUrl = null; + if ($user) { + $verificationUrl = URL::temporarySignedRoute( + 'verification.verify', now()->addMinutes($expire), [ + 'id' => $user->getKey(), + 'hash' => sha1($user->getEmailForVerification()), + ] + ); + } + + $cacheKey = $user ? 'email_verify_code:'.$user->getKey() : null; + $hasCode = $cacheKey ? !is_null(Cache::get($cacheKey)) : false; + + return Inertia::render('auth/VerifyEmail', [ + 'status' => $request->session()->get('status'), + 'verificationUrl' => $verificationUrl, + 'hasCode' => $hasCode, + ]); + }); + + Fortify::registerView(fn () => Inertia::render('auth/Register')); + Fortify::twoFactorChallengeView(fn () => Inertia::render('auth/TwoFactorChallenge')); + Fortify::confirmPasswordView(fn () => Inertia::render('auth/ConfirmPassword')); + } + + private function configureRateLimiting(): void + { + RateLimiter::for('two-factor', function (Request $request) { + return Limit::perMinute(5)->by($request->session()->get('login.id')); + }); + + RateLimiter::for('login', function (Request $request) { + $username = (string) $request->input(Fortify::username()); + $throttleKey = Str::lower($username).'|'.$request->ip(); + return Limit::perMinute(5)->by($throttleKey); + }); + } +} diff --git a/app/Services/BackendHttpClient.php b/app/Services/BackendHttpClient.php new file mode 100644 index 0000000..59543dc --- /dev/null +++ b/app/Services/BackendHttpClient.php @@ -0,0 +1,107 @@ +environment('testing')) { + // In tests, prefer the services config so test fakes using config('services.backend.base') match + // IMPORTANT: If config('services.backend.base') contains /api at the end, test fakes often omit it. + // We'll strip /api from the end of the base URL to match standard Http::fake() patterns in this project. + $base = (string) ($serviceUrl ?: ($envUrl ?: ($appUrl ?: 'http://casinoapi.test'))); + $this->base = rtrim(preg_replace('/\/api\/?$/', '', $base), '/'); + } else { + // In non-testing, prefer explicit ENV override, then services config, then app config + $this->base = rtrim((string) ($envUrl ?: ($serviceUrl ?: ($appUrl ?: 'http://casinoapi.test/api'))), '/'); + } + + $this->token = (string) config('services.backend.token'); + $this->timeout = (float) config('services.backend.timeout', 8.0); + } + + /** + * Issue a GET request with standard headers and optional retry policy. + */ + public function get(HttpRequest $request, string $path, array $query = [], bool $retry = true) + { + $req = $this->baseRequest($request)->acceptJson(); + if ($retry) { + // Retry 2x on timeout/5xx + $req = $req->retry(2, 100, throw: false); + } + return $req->get($this->url($path), $query); + } + + /** + * Issue a POST request with standard headers (no retries by default). + */ + public function post(HttpRequest $request, string $path, array $data = []) + { + return $this->baseRequest($request)->acceptJson()->post($this->url($path), $data); + } + + public function patch(HttpRequest $request, string $path, array $data = []) + { + return $this->baseRequest($request)->acceptJson()->patch($this->url($path), $data); + } + + public function delete(HttpRequest $request, string $path, array $data = []) + { + return $this->baseRequest($request)->acceptJson()->asForm()->delete($this->url($path), $data); + } + + /** + * Multipart POST (file upload) with standard headers. + */ + public function postMultipart(HttpRequest $request, string $path, array $fields, $file, string $fileField = 'file', ?string $filename = null) + { + $pending = $this->baseRequest($request); + if ($file) { + $stream = fopen($file->getRealPath(), 'r'); + $pending = $pending->attach($fileField, $stream, $filename ?: $file->getClientOriginalName()); + } + foreach ($fields as $k => $v) { + if ($v === null) continue; + $pending = $pending->asMultipart(); + } + return $pending->post($this->url($path), $fields); + } + + private function url(string $path): string + { + return $this->base . '/' . ltrim($path, '/'); + } + + private function baseRequest(HttpRequest $request): PendingRequest + { + $user = $request->user(); + $userId = $user?->id ? (string) $user->id : ''; + $ua = (string) $request->header('User-Agent', ''); + $sid = (string) $request->session()->getId(); + $deviceHash = hash('sha256', $ua . '|' . $sid); + $ipHash = hash('sha256', (string) $request->ip()); + + return Http::withHeaders([ + 'Authorization' => 'Bearer ' . $this->token, + 'X-User-Id' => $userId, + 'X-User-Device' => $deviceHash, + 'X-User-IP' => $ipHash, + 'Accept' => 'application/json', + ])->timeout($this->timeout); + } +} diff --git a/app/Services/BetiXClient.php b/app/Services/BetiXClient.php new file mode 100644 index 0000000..e23d4bd --- /dev/null +++ b/app/Services/BetiXClient.php @@ -0,0 +1,116 @@ +apiKey = (string) config('services.betix.key'); + $this->baseUrl = rtrim((string) config('services.betix.url', 'https://originals.betix.io'), '/'); + $this->timeout = (float) config('services.betix.timeout', 10.0); + } + + // ── Public API methods ────────────────────────────────────────────────── + + public function games(): array + { + return $this->request('GET', '/operator/games'); + } + + public function launch(array $payload): array + { + return $this->request('POST', '/operator/launch', $payload); + } + + public function session(string $token): array + { + return $this->request('GET', "/operator/session/{$token}"); + } + + public function endSession(string $token): array + { + return $this->request('POST', "/operator/session/{$token}/end"); + } + + // ── HMAC signing ──────────────────────────────────────────────────────── + + /** + * Verify an inbound webhook from BetiX. + * Returns true if the X-Signature header matches the raw body. + */ + public function verifyWebhook(string $rawBody, string $signature): bool + { + $secret = (string) config('services.betix.webhook_secret'); + $expected = hash_hmac('sha256', $rawBody, $secret); + + return hash_equals($expected, $signature); + } + + // ── HTTP transport ─────────────────────────────────────────────────────── + + private function request(string $method, string $path, array $data = []): array + { + $body = $data ? json_encode($data) : ''; + $ts = (string) time(); + $nonce = bin2hex(random_bytes(8)); + $bodyHex = bin2hex($body); + $message = "{$ts}:{$nonce}:{$method}:{$path}:{$bodyHex}"; + $sig = hash_hmac('sha256', $message, $this->apiKey); + + $url = $this->baseUrl . $path; + + \Illuminate\Support\Facades\Log::debug('[BetiXClient] request', [ + 'method' => $method, + 'url' => $url, + 'body' => $body ?: '{}', + 'message' => $message, + ]); + + try { + $response = Http::withHeaders([ + 'X-API-Key' => $this->apiKey, + 'X-Timestamp' => $ts, + 'X-Nonce' => $nonce, + 'X-Signature' => $sig, + 'Accept' => 'application/json', + 'Origin' => rtrim((string) config('app.url'), '/'), + ]) + ->timeout($this->timeout) + ->withBody($body ?: '{}', 'application/json') + ->send($method, $url); + } catch (\Throwable $e) { + \Illuminate\Support\Facades\Log::error('[BetiXClient] connection failed', [ + 'url' => $url, + 'error' => $e->getMessage(), + 'exception' => get_class($e), + ]); + throw new RuntimeException("BetiX unreachable: {$e->getMessage()}", 0, $e); + } + + $json = $response->json() ?? []; + + \Illuminate\Support\Facades\Log::debug('[BetiXClient] response', [ + 'status' => $response->status(), + 'body' => $response->body(), + 'json' => $json, + ]); + + if ($response->failed()) { + \Illuminate\Support\Facades\Log::error('[BetiXClient] API error', [ + 'status' => $response->status(), + 'body' => $response->body(), + ]); + throw new RuntimeException($json['error'] ?? "BetiX API error {$response->status()}"); + } + + return $json; + } +} diff --git a/app/Services/BonusService.php b/app/Services/BonusService.php new file mode 100644 index 0000000..1663f1c --- /dev/null +++ b/app/Services/BonusService.php @@ -0,0 +1,62 @@ +id) + ->where('is_active', true) + ->whereNull('completed_at') + ->where(function($q) { + $q->whereNull('expires_at')->orWhere('expires_at', '>', now()); + }) + ->get(); + + foreach ($activeBonuses as $bonus) { + DB::transaction(function() use ($bonus, $wagerAmount) { + $bonus->refresh(); + if (!$bonus->is_active || $bonus->completed_at) return; + + $bonus->wager_progress += $wagerAmount; + + if ($bonus->wager_progress >= $bonus->wager_required) { + $bonus->completed_at = now(); + $bonus->is_active = false; + Log::info("BonusService: User {$bonus->user_id} completed bonus {$bonus->id}"); + // Logic to unlock real balance if needed + } + + $bonus->save(); + }); + } + } + + /** + * Expire outdated bonuses + */ + public function expireBonuses(): int + { + $expiredCount = UserBonus::where('is_active', true) + ->whereNotNull('expires_at') + ->where('expires_at', '<=', now()) + ->update([ + 'is_active' => false + ]); + + if ($expiredCount > 0) { + Log::info("BonusService: Expired $expiredCount user bonuses"); + } + + return $expiredCount; + } +} diff --git a/app/Services/DepositService.php b/app/Services/DepositService.php new file mode 100644 index 0000000..8064f87 --- /dev/null +++ b/app/Services/DepositService.php @@ -0,0 +1,273 @@ + config('services.nowpayments.mode', 'sandbox'), + 'enabled_currencies' => ['BTC','ETH','LTC','SOL','USDT_ERC20','USDT_TRC20'], // Fallback + 'global_min_usd' => 10.0, + 'global_max_usd' => 10000.0, + 'btx_per_usd' => 1.0, + 'per_currency_overrides' => [], + 'success_url' => url('/wallet?deposit=success'), + 'cancel_url' => url('/wallet?deposit=cancel'), + 'address_mode' => 'per_payment', // per_payment | per_user + ]; + $row = AppSetting::get('payments.nowpayments', []); + if (!is_array($row)) $row = []; + return array_replace($defaults, $row); + } + + public function currenciesForUser(): array + { + $s = $this->getSettings(); + + // Fetch the live list of enabled currencies from the NOWPayments API + $apiCoins = $this->np->getAvailableCoins(); + + // Use API coins if successful, otherwise fallback to local settings + $enabled = !empty($apiCoins) ? array_map('strtoupper', $apiCoins) : $s['enabled_currencies']; + + return [ + 'mode' => $s['mode'], + 'enabled' => $enabled, + 'limits' => [ + 'global_min_usd' => $s['global_min_usd'], + 'global_max_usd' => $s['global_max_usd'], + ], + 'overrides' => $s['per_currency_overrides'], + 'btx_per_usd' => $s['btx_per_usd'], + ]; + } + + public function startDeposit(User $user, string $payCurrency, float $priceAmountUsd): ?array + { + $s = $this->getSettings(); + $payCurrency = strtoupper($payCurrency); + $addressMode = (string) ($s['address_mode'] ?? 'per_payment'); + + // Fetch live allowed coins to validate + $apiCoins = $this->np->getAvailableCoins(); + $allowedCoins = !empty($apiCoins) ? array_map('strtoupper', $apiCoins) : $s['enabled_currencies']; + + // Validate currency against live list + if (!in_array($payCurrency, $allowedCoins, true)) { + return ['error' => 'currency_not_allowed']; + } + + // Validate limits (override per currency if provided) + $min = (float) ($s['per_currency_overrides'][$payCurrency]['min_usd'] ?? $s['global_min_usd']); + $max = (float) ($s['per_currency_overrides'][$payCurrency]['max_usd'] ?? $s['global_max_usd']); + if ($priceAmountUsd < $min || $priceAmountUsd > $max) { + return ['error' => 'amount_out_of_bounds', 'min_usd' => $min, 'max_usd' => $max]; + } + + $orderId = (string) Str::uuid(); + + // Get an estimate for the crypto amount + $estimate = $this->np->estimate($priceAmountUsd, 'usd', $payCurrency); + $payAmount = $estimate['estimated_amount'] ?? null; + + $payload = [ + 'price_amount' => $priceAmountUsd, + 'price_currency' => 'USD', + 'pay_currency' => $payCurrency, + 'order_id' => $orderId, // We use our local order ID here, so it ties back to the user + 'order_description' => 'Deposit for user #' . $user->id, + 'success_url' => $s['success_url'], + 'cancel_url' => $s['cancel_url'], + 'ipn_callback_url' => url('/api/webhooks/nowpayments'), + ]; + + $invoice = $this->np->createInvoice($payload); + if (!$invoice || empty($invoice['id'])) { + Log::error('Failed to create NOWPayments invoice', ['payload' => $payload, 'invoice' => $invoice]); + return ['error' => 'invoice_create_failed']; + } + + // Some responses embed payment_id/address at creation; store what we have + $paymentId = (string) ($invoice['payment_id'] ?? ($invoice['paymentId'] ?? '')); // resilient to casing + $payAddress = (string) ($invoice['pay_address'] ?? ($invoice['payAddress'] ?? '')); + + $cp = CryptoPayment::create([ + 'user_id' => $user->id, + 'order_id' => $orderId, + 'invoice_id' => (string) $invoice['id'], + 'payment_id' => $paymentId ?: ('inv_' . $invoice['id']), + 'pay_currency' => $payCurrency, + 'pay_amount' => $payAmount, + 'actually_paid' => null, + 'pay_address' => $payAddress ?: null, + 'price_amount' => $priceAmountUsd, + 'price_currency' => 'USD', + 'status' => 'waiting', + 'raw_payload' => $invoice, + ]); + + return [ + 'order_id' => $orderId, + 'invoice_id' => (string) $invoice['id'], + 'payment_id' => $cp->payment_id, + 'pay_currency' => $payCurrency, + 'price_amount' => $priceAmountUsd, + 'price_currency' => 'USD', + 'pay_address' => $payAddress ?: null, + 'redirect_url' => $invoice['invoice_url'] ?? ($invoice['url'] ?? null), + ]; + } + + public function getUserHistory(User $user, int $limit = 10): array + { + $payments = CryptoPayment::where('user_id', $user->id) + ->orderByDesc('created_at') + ->limit($limit) + ->get(); + + $history = []; + foreach ($payments as $p) { + $history[] = [ + 'order_id' => $p->order_id, + 'payment_id' => $p->payment_id, + 'status' => $p->status, + 'pay_currency' => strtoupper($p->pay_currency), + 'pay_amount' => $p->pay_amount, + 'price_amount' => $p->price_amount, + 'actually_paid' => $p->actually_paid, + 'credited_btx' => $p->credited_btx, + 'created_at' => $p->created_at?->toIso8601String(), + ]; + } + + return $history; + } + + public function handleIpn(Request $request): array + { + $cfg = config('services.nowpayments'); + $dbSettings = AppSetting::get('payments.nowpayments', []); + $secret = (string) ($dbSettings['ipn_secret'] ?? $cfg['ipn_secret'] ?? ''); + $body = $request->getContent(); + $sig = (string) $request->header('x-nowpayments-sig', ''); + + if (!$this->verifyHmac($body, $sig, $secret)) { + return ['ok' => false, 'status' => 401, 'message' => 'Invalid signature']; + } + + $data = $request->json()->all(); + // Identify by payment_id or order_id + $paymentId = (string) ($data['payment_id'] ?? ($data['paymentId'] ?? '')); + $orderId = (string) ($data['order_id'] ?? ($data['orderId'] ?? '')); + $status = (string) ($data['payment_status'] ?? ($data['status'] ?? '')); + + if (!$paymentId && !$orderId) { + return ['ok' => false, 'status' => 422, 'message' => 'Missing identifiers']; + } + + $cp = CryptoPayment::query() + ->when($paymentId, fn($q) => $q->where('payment_id', $paymentId)) + ->when(!$paymentId && $orderId, fn($q) => $q->where('order_id', $orderId)) + ->first(); + + if (!$cp) { + // Create minimal row if missing (idempotent handling) + Log::warning('IPN for unknown payment', ['payment_id' => $paymentId, 'order_id' => $orderId]); + return ['ok' => true, 'status' => 200, 'message' => 'Ignored']; + } + + // Update raw payload & status mirror + $cp->status = $status ?: ($cp->status ?? ''); + $cp->raw_payload = $data; + $cp->pay_amount = $data['pay_amount'] ?? $cp->pay_amount; + $cp->actually_paid = $data['actually_paid'] ?? $cp->actually_paid; + $cp->pay_address = $data['pay_address'] ?? $cp->pay_address; + $cp->confirmations = $data['confirmations'] ?? $cp->confirmations; + $cp->tx_hash = $data['payin_hash'] ?? ($data['tx_hash'] ?? $cp->tx_hash); + + // Idempotency: if already credited, do nothing + if ($cp->credited_btx !== null) { + $cp->save(); + return ['ok' => true, 'status' => 200, 'message' => 'Already processed']; + } + + // Only credit on finished + if (strtolower($cp->status) !== 'finished') { + $cp->save(); + return ['ok' => true, 'status' => 200, 'message' => 'No credit (status=' . $cp->status . ')']; + } + + // Compute BTX amount: use final USD if present + $settings = $this->getSettings(); + $btxPerUsd = (float) ($settings['per_currency_overrides'][$cp->pay_currency]['btx_per_usd'] ?? $settings['btx_per_usd']); + $usdFinal = (float) ($data['actually_paid_in_fiat'] ?? ($cp->price_amount ?? 0)); + if ($usdFinal <= 0) { + $usdFinal = (float) ($cp->price_amount ?? 0); + } + $creditBtx = round($usdFinal * $btxPerUsd, 8); + + DB::transaction(function () use ($cp, $creditBtx) { + // Row lock user + $user = User::query()->where('id', $cp->user_id)->lockForUpdate()->first(); + if (!$user) { + throw new \RuntimeException('User not found for payment'); + } + + $balanceBefore = (float) ($user->balance ?? 0); + $vaultBefore = (float) ($user->vault_balance ?? 0); + $balanceAfter = $balanceBefore + (float) $creditBtx; + $vaultAfter = $vaultBefore; // vault not affected by external deposit + + $user->balance = $balanceAfter; + $user->save(); + + // Log into wallet_transfers if available + if (class_exists(WalletTransfer::class)) { + WalletTransfer::create([ + 'user_id' => $user->id, + 'type' => 'deposit', + 'amount' => $creditBtx, + 'balance_before' => $balanceBefore, + 'balance_after' => $balanceAfter, + 'vault_before' => $vaultBefore, + 'vault_after' => $vaultAfter, + 'currency' => 'BTX', + 'idempotency_key' => 'np:' . $cp->payment_id, + 'meta' => [ + 'source' => 'nowpayments', + 'payment_id' => $cp->payment_id, + 'invoice_id' => $cp->invoice_id, + ], + ]); + } + + $cp->credited_btx = $creditBtx; + $cp->credited_at = now(); + $cp->save(); + }); + + return ['ok' => true, 'status' => 200, 'message' => 'Credited']; + } + + private function verifyHmac(string $rawBody, string $sigHeader, string $secret): bool + { + if ($secret === '' || $sigHeader === '') return false; + $calc = hash_hmac('sha512', $rawBody, $secret); + return hash_equals($calc, $sigHeader); + } +} diff --git a/app/Services/GameService.php b/app/Services/GameService.php new file mode 100644 index 0000000..b5b6f42 --- /dev/null +++ b/app/Services/GameService.php @@ -0,0 +1,63 @@ +refresh(); + $user->balance = $newBalance; + $user->save(); + }); + Log::info("GameService: Synced User {$user->id} balance to $newBalance"); + } + + // Track wagering if applicable + if ($wager > 0) { + $this->bonusService->trackWagering($user, (float) $wager); + } + + // Log bet if info is available + if (isset($data['bet']) || isset($data['win'])) { + $this->logBet($user, $data); + } + } + + /** + * Log a game bet into the database + */ + protected function logBet(User $user, array $data): void + { + try { + GameBet::create([ + 'user_id' => $user->id, + 'game_name' => $data['game'] ?? 'Unknown', + 'wager_amount' => $data['bet'] ?? 0, + 'payout_amount' => $data['win'] ?? 0, + 'payout_multiplier' => ($data['bet'] > 0) ? ($data['win'] / $data['bet']) : 0, + 'currency' => 'BTX' + ]); + } catch (\Exception $e) { + Log::error("GameService: Failed to log bet: " . $e->getMessage()); + } + } +} diff --git a/app/Services/NowPaymentsClient.php b/app/Services/NowPaymentsClient.php new file mode 100644 index 0000000..1400117 --- /dev/null +++ b/app/Services/NowPaymentsClient.php @@ -0,0 +1,213 @@ +baseUrl = rtrim((string) ($cfg['base_url'] ?? 'https://api.nowpayments.io/v1'), '/'); + $this->apiKey = (string) ($dbSettings['api_key'] ?? $cfg['api_key'] ?? ''); + $this->jwtToken = (string) ($dbSettings['jwt_token'] ?? $cfg['jwt_token'] ?? ''); + $this->timeout = (float) ($cfg['timeout'] ?? 10.0); + } + + private function client(bool $useJwt = false) + { + $headers = [ + 'x-api-key' => $this->apiKey, + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ]; + + if ($useJwt && $this->jwtToken) { + $headers['Authorization'] = 'Bearer ' . $this->jwtToken; + } + + return Http::timeout($this->timeout) + ->retry(2, 150, throw: false) + ->baseUrl($this->baseUrl) + ->withHeaders($headers); + } + + public function listCurrencies(): array + { + $res = $this->client()->get('/currencies'); + if (!$res->ok()) { + Log::warning('NOWPayments listCurrencies failed', ['status' => $res->status(), 'body' => $res->body()]); + return []; + } + $json = $res->json(); + return is_array($json) ? ($json['currencies'] ?? $json) : []; + } + + /** + * Get available checked currencies (merchant/coins) + * https://api.nowpayments.io/v1/merchant/coins + */ + public function getAvailableCoins(): array + { + $res = $this->client()->get('/merchant/coins'); + if (!$res->ok()) { + Log::warning('NOWPayments getAvailableCoins failed', ['status' => $res->status(), 'body' => $res->body()]); + return []; + } + $json = $res->json(); + return is_array($json) ? ($json['selectedCurrencies'] ?? $json) : []; + } + + public function estimate(float $priceAmount, string $priceCurrency, string $payCurrency): ?array + { + $payload = [ + 'amount' => $priceAmount, + 'currency_from' => strtoupper($priceCurrency), + 'currency_to' => strtoupper($payCurrency), + ]; + $res = $this->client()->post('/estimate', $payload); + if (!$res->ok()) { + Log::warning('NOWPayments estimate failed', ['status' => $res->status(), 'body' => $res->body(), 'payload' => $payload]); + return null; + } + return $res->json(); + } + + /** + * Get the minimum payment amount + * https://api.nowpayments.io/v1/min-amount?currency_from=eth¤cy_to=trx + */ + public function getMinAmount(string $currencyFrom, string $currencyTo, ?string $fiatEquivalent = null, bool $isFixedRate = false, bool $isFeePaidByUser = false): ?array + { + $query = [ + 'currency_from' => strtolower($currencyFrom), + 'currency_to' => strtolower($currencyTo), + 'is_fixed_rate' => $isFixedRate ? 'True' : 'False', + 'is_fee_paid_by_user' => $isFeePaidByUser ? 'True' : 'False', + ]; + + if ($fiatEquivalent) { + $query['fiat_equivalent'] = strtolower($fiatEquivalent); + } + + $res = $this->client()->get('/min-amount', $query); + if (!$res->ok()) { + Log::warning('NOWPayments getMinAmount failed', ['status' => $res->status(), 'body' => $res->body(), 'query' => $query]); + return null; + } + return $res->json(); + } + + public function createInvoice(array $data): ?array + { + // Expected keys: price_amount, price_currency, pay_currency, order_id, order_description, success_url, cancel_url, ipn_callback_url + $res = $this->client()->post('/invoice', $data); + if (!$res->ok()) { + Log::error('NOWPayments createInvoice failed', ['status' => $res->status(), 'body' => $res->body(), 'data' => $data]); + return null; + } + return $res->json(); + } + + /** + * Get payment status + * https://api.nowpayments.io/v1/payment/:payment_id + */ + public function getPayment(string $paymentId): ?array + { + $res = $this->client()->get('/payment/' . urlencode($paymentId)); + if (!$res->ok()) { + Log::warning('NOWPayments getPayment failed', ['status' => $res->status(), 'body' => $res->body(), 'payment_id' => $paymentId]); + return null; + } + return $res->json(); + } + + /** + * Get list of payments + * https://api.nowpayments.io/v1/payment/?limit=10&page=0&sortBy=created_at&orderBy=asc&dateFrom=2020-01-01&dateTo=2021-01-01&invoiceId=6200264890 + */ + public function listPayments(array $params = []): ?array + { + $res = $this->client()->get('/payment/', $params); + if (!$res->ok()) { + Log::warning('NOWPayments listPayments failed', ['status' => $res->status(), 'body' => $res->body(), 'params' => $params]); + return null; + } + return $res->json(); + } + + public function getInvoice(string $invoiceId): ?array + { + $res = $this->client()->get('/invoice/' . urlencode($invoiceId)); + if (!$res->ok()) { + Log::warning('NOWPayments getInvoice failed', ['status' => $res->status(), 'body' => $res->body(), 'invoice_id' => $invoiceId]); + return null; + } + return $res->json(); + } + + /** + * Validate address + * https://api.nowpayments.io/v1/payout/validate-address + */ + public function validateAddress(string $currency, string $address, ?string $extraId = null): ?array + { + $payload = [ + 'currency' => strtolower($currency), + 'address' => $address, + ]; + if ($extraId !== null) { + $payload['extra_id'] = $extraId; + } + + // According to NOWPayments docs, this uses the standard client + $res = $this->client()->post('/payout/validate-address', $payload); + if (!$res->ok()) { + Log::warning('NOWPayments validateAddress failed', ['status' => $res->status(), 'body' => $res->body(), 'payload' => $payload]); + return null; + } + return $res->json(); + } + + /** + * Verify payout + * https://api.nowpayments.io/v1/payout/:batch-withdrawal-id/verify + */ + public function verifyPayout(string $batchWithdrawalId, string $verificationCode): ?array + { + $payload = [ + 'verification_code' => $verificationCode + ]; + + $res = $this->client(true)->post('/payout/' . urlencode($batchWithdrawalId) . '/verify', $payload); + if (!$res->ok()) { + Log::warning('NOWPayments verifyPayout failed', ['status' => $res->status(), 'body' => $res->body(), 'batch_id' => $batchWithdrawalId]); + return null; + } + return $res->json(); + } + + /** + * List of payouts + * https://api.nowpayments.io/v1/payout + */ + public function listPayouts(array $params = []): ?array + { + $res = $this->client(true)->get('/payout', $params); + if (!$res->ok()) { + Log::warning('NOWPayments listPayouts failed', ['status' => $res->status(), 'body' => $res->body(), 'params' => $params]); + return null; + } + return $res->json(); + } +} diff --git a/app/Services/VaultService.php b/app/Services/VaultService.php new file mode 100644 index 0000000..13a6f53 --- /dev/null +++ b/app/Services/VaultService.php @@ -0,0 +1,213 @@ +scale; } + + public function depositToVault(User $user, string|float $amount, ?string $idempotencyKey = null, string $source = 'web', ?int $createdBy = null, array $metadata = []): VaultTransfer + { + return $this->move($user, $amount, 'to_vault', $idempotencyKey, $source, $createdBy, $metadata); + } + + public function withdrawFromVault(User $user, string|float $amount, ?string $idempotencyKey = null, string $source = 'web', ?int $createdBy = null, array $metadata = []): VaultTransfer + { + return $this->move($user, $amount, 'from_vault', $idempotencyKey, $source, $createdBy, $metadata); + } + + private function move(User $user, string|float $amount, string $direction, ?string $idempotencyKey, string $source, ?int $createdBy, array $metadata): VaultTransfer + { + $amountStr = $this->normalizeAmount($amount); + if ($this->cmp($amountStr, '0.0000') <= 0) { + throw ValidationException::withMessages(['amount' => 'Amount must be greater than 0.']); + } + + if ($idempotencyKey) { + $existing = VaultTransfer::where('user_id', $user->id) + ->where('idempotency_key', $idempotencyKey) + ->first(); + if ($existing) { + // If direction/amount mismatch, reject to avoid accidental replay with different params + if ($existing->direction !== $direction || $this->cmp($existing->amount, $amountStr) !== 0) { + throw ValidationException::withMessages(['idempotency_key' => 'Conflicting idempotent request.'])->status(409); + } + return $existing; + } + } + + return DB::transaction(function () use ($user, $amountStr, $direction, $idempotencyKey, $source, $createdBy, $metadata) { + // Lock user row + $u = User::whereKey($user->id)->lockForUpdate()->first(); + $mainBefore = $this->normalizeAmount($u->balance); + $vaultBefore = $this->normalizeAmount($u->vault_balance ?? '0'); + + if ($direction === 'to_vault') { + // move from main to vault + if ($this->cmp($mainBefore, $amountStr) < 0) { + throw ValidationException::withMessages(['amount' => 'Insufficient main balance.']); + } + $mainAfter = $this->sub($mainBefore, $amountStr); + $vaultAfter = $this->add($vaultBefore, $amountStr); + } else { // from_vault + if ($this->cmp($vaultBefore, $amountStr) < 0) { + throw ValidationException::withMessages(['amount' => 'Insufficient vault balance.']); + } + $mainAfter = $this->add($mainBefore, $amountStr); + $vaultAfter = $this->sub($vaultBefore, $amountStr); + } + + // Persist balances + $u->balance = $mainAfter; // existing cast is decimal:4 + $u->vault_balance = $vaultAfter; // encrypted decimal cast + $u->save(); + + $transfer = VaultTransfer::create([ + 'user_id' => $u->id, + 'direction' => $direction, + 'amount' => $amountStr, + 'main_balance_before' => $mainBefore, + 'main_balance_after' => $mainAfter, + 'vault_balance_before' => $vaultBefore, + 'vault_balance_after' => $vaultAfter, + 'idempotency_key' => $idempotencyKey, + 'source' => $source, + 'created_by' => $createdBy, + 'metadata' => $metadata, + ]); + + return $transfer; + }); + } + + // ---- Decimal helpers using integer arithmetic with fixed scale ---- + private function normalizeAmount(string|float|int $v): string + { + $s = is_string($v) ? trim($v) : (string) $v; + $s = str_replace([',', ' '], ['', ''], $s); + if ($s === '' || $s === '-') $s = '0'; + if (!preg_match('/^-?\d*(?:\.\d+)?$/', $s)) { + throw ValidationException::withMessages(['amount' => 'Invalid amount format.']); + } + $neg = str_starts_with($s, '-') ? '-' : ''; + if ($neg) $s = substr($s, 1); + [$int, $frac] = array_pad(explode('.', $s, 2), 2, ''); + $frac = substr($frac . str_repeat('0', $this->scale), 0, $this->scale); + $int = ltrim($int, '0'); + if ($int === '') $int = '0'; + return ($neg ? '-' : '') . $int . ($this->scale > 0 ? ('.' . $frac) : ''); + } + + private function toScaledInt(string $s): array + { + $neg = str_starts_with($s, '-') ? -1 : 1; + if ($neg < 0) $s = substr($s, 1); + [$int, $frac] = array_pad(explode('.', $s, 2), 2, ''); + $frac = substr($frac . str_repeat('0', $this->scale), 0, $this->scale); + $num = ltrim($int, '0'); + if ($num === '') $num = '0'; + $num .= $frac; + return [$neg, ltrim($num, '0') === '' ? '0' : ltrim($num, '0')]; + } + + private function fromScaledInt(int $sign, string $digits): string + { + // pad left to ensure at least scale digits + $digits = ltrim($digits, '0'); + if ($digits === '') $digits = '0'; + $pad = max(0, $this->scale - strlen($digits)); + $digits = str_repeat('0', $pad) . $digits; + $int = substr($digits, 0, strlen($digits) - $this->scale); + $frac = substr($digits, -$this->scale); + if ($int === '') $int = '0'; + $out = $int; + if ($this->scale > 0) $out .= '.' . $frac; + if ($sign < 0 && $out !== '0' . ($this->scale > 0 ? '.' . str_repeat('0', $this->scale) : '')) $out = '-' . $out; + return $out; + } + + private function add(string $a, string $b): string + { + [$sa, $ia] = $this->toScaledInt($a); + [$sb, $ib] = $this->toScaledInt($b); + if ($sa === $sb) { + $sum = $this->addDigits($ia, $ib); + return $this->fromScaledInt($sa, $sum); + } + // different signs -> subtraction + if ($this->cmpAbs($ia, $ib) >= 0) { + $diff = $this->subDigits($ia, $ib); + return $this->fromScaledInt($sa, $diff); + } else { + $diff = $this->subDigits($ib, $ia); + return $this->fromScaledInt($sb, $diff); + } + } + + private function sub(string $a, string $b): string + { + // a - b = a + (-b) + $negB = str_starts_with($b, '-') ? substr($b, 1) : ('-' . $b); + return $this->add($a, $negB); + } + + private function cmp(string $a, string $b): int + { + [$sa, $ia] = $this->toScaledInt($a); + [$sb, $ib] = $this->toScaledInt($b); + if ($sa !== $sb) return $sa <=> $sb; // -1 vs 1 + $c = $this->cmpAbs($ia, $ib); + return $sa * $c; + } + + private function addDigits(string $x, string $y): string + { + $cx = strrev($x); $cy = strrev($y); + $len = max(strlen($cx), strlen($cy)); + $carry = 0; $out = ''; + for ($i = 0; $i < $len; $i++) { + $dx = $i < strlen($cx) ? ord($cx[$i]) - 48 : 0; + $dy = $i < strlen($cy) ? ord($cy[$i]) - 48 : 0; + $s = $dx + $dy + $carry; + $out .= chr(($s % 10) + 48); + $carry = intdiv($s, 10); + } + if ($carry) $out .= chr($carry + 48); + return strrev($out); + } + + private function subDigits(string $x, string $y): string + { + // assumes x >= y in absolute + $cx = strrev($x); $cy = strrev($y); + $len = max(strlen($cx), strlen($cy)); + $borrow = 0; $out = ''; + for ($i = 0; $i < $len; $i++) { + $dx = $i < strlen($cx) ? ord($cx[$i]) - 48 : 0; + $dy = $i < strlen($cy) ? ord($cy[$i]) - 48 : 0; + $d = $dx - $borrow - $dy; + if ($d < 0) { $d += 10; $borrow = 1; } else { $borrow = 0; } + $out .= chr($d + 48); + } + // reverse back to normal order and normalize without stripping valid trailing zeros + $out = strrev($out); + // remove leading zeros only; keep at least one zero + $out = ltrim($out, '0'); + return $out === '' ? '0' : $out; + } + + private function cmpAbs(string $x, string $y): int + { + $x = ltrim($x, '0'); if ($x === '') $x = '0'; + $y = ltrim($y, '0'); if ($y === '') $y = '0'; + if (strlen($x) !== strlen($y)) return strlen($x) <=> strlen($y); + return strcmp($x, $y); + } +} diff --git a/app/Services/WalletService.php b/app/Services/WalletService.php new file mode 100644 index 0000000..1fecd8c --- /dev/null +++ b/app/Services/WalletService.php @@ -0,0 +1,220 @@ +vault_pin_locked_until) && now()->lessThan($user->vault_pin_locked_until)) { + return response()->json([ + 'message' => 'Vault PIN locked. Try again later.', + 'locked_until' => optional($user->vault_pin_locked_until)->toIso8601String(), + ], 423); + } + if (empty($user->vault_pin_hash)) { + return response()->json(['message' => 'Vault PIN not set'], 400); + } + if (!Hash::check($pin, $user->vault_pin_hash)) { + // increment attempts and maybe lock + $attempts = (int) ($user->vault_pin_attempts ?? 0) + 1; + $user->vault_pin_attempts = $attempts; + if ($attempts >= 5) { + $user->vault_pin_locked_until = now()->addMinutes(15); + $user->vault_pin_attempts = 0; // reset after lock + } + $user->save(); + return response()->json(['message' => 'Invalid PIN'], 423); + } + // success → reset attempts + if (!empty($user->vault_pin_attempts)) { + $user->vault_pin_attempts = 0; + $user->save(); + } + return null; + } + + /** + * Get the vault balance for a given currency. + */ + public function getVaultBalance(User $user, string $currency): float + { + if ($currency === 'BTX') { + return (float) ($user->vault_balance ?? 0); + } + $map = $user->vault_balances ?? []; + return (float) ($map[$currency] ?? 0); + } + + /** + * Get the wallet balance for a given currency. + */ + public function getWalletBalance(User $user, string $currency): float + { + if ($currency === 'BTX') { + return (float) ($user->balance ?? 0); + } + // For other currencies, check wallets relationship or JSON field if present + $map = $user->vault_balances ?? []; // re-use structure — wallet balance not stored here + // Non-BTX wallet balances are not tracked in the main user table yet + return 0.0; + } + + /** + * Deposit from main balance to vault. + */ + public function depositToVault(User $user, string $amountStr, ?string $idempotencyKey = null, string $currency = 'BTX'): array + { + $amount = (float) $amountStr; + if ($amount <= 0) { + abort(422, 'Amount must be greater than zero'); + } + + if ($idempotencyKey) { + $existing = WalletTransfer::where('user_id', $user->id) + ->where('idempotency_key', $idempotencyKey) + ->first(); + if ($existing) { + return $this->buildBalanceResponse($user, $currency); + } + } + + return DB::transaction(function () use ($user, $amount, $idempotencyKey, $currency) { + $u = User::where('id', $user->id)->lockForUpdate()->first(); + + if ($currency === 'BTX') { + $balanceBefore = (float) $u->balance; + $vaultBefore = (float) $u->vault_balance; + + if ($balanceBefore < $amount) abort(400, 'Insufficient balance'); + + $u->balance = $balanceBefore - $amount; + $u->vault_balance = $vaultBefore + $amount; + $u->save(); + + WalletTransfer::create([ + 'user_id' => $u->id, + 'type' => 'deposit', + 'amount' => $amount, + 'balance_before' => $balanceBefore, + 'balance_after' => (float) $u->balance, + 'vault_before' => $vaultBefore, + 'vault_after' => (float) $u->vault_balance, + 'currency' => $currency, + 'idempotency_key' => $idempotencyKey, + 'meta' => null, + ]); + } else { + // Non-BTX: use vault_balances JSON map + $map = $u->vault_balances ?? []; + $vaultBefore = (float) ($map[$currency] ?? 0); + // Wallet balance for non-BTX is not in DB yet — block if 0 + abort(400, "Deposits for {$currency} are not yet supported."); + } + + return $this->buildBalanceResponse($u, $currency); + }); + } + + /** + * Withdraw from vault to main balance. + */ + public function withdrawFromVault(User $user, string $amountStr, ?string $idempotencyKey = null, string $currency = 'BTX'): array + { + $amount = (float) $amountStr; + if ($amount <= 0) { + abort(422, 'Amount must be greater than zero'); + } + + if (!empty($user->withdraw_cooldown_until) && now()->lessThan($user->withdraw_cooldown_until)) { + abort(429, 'Withdraw cooldown active. Try again at ' . $user->withdraw_cooldown_until->toIso8601String()); + } + + if ($idempotencyKey) { + $existing = WalletTransfer::where('user_id', $user->id) + ->where('idempotency_key', $idempotencyKey) + ->first(); + if ($existing) { + return $this->buildBalanceResponse($user, $currency); + } + } + + return DB::transaction(function () use ($user, $amount, $idempotencyKey, $currency) { + $u = User::where('id', $user->id)->lockForUpdate()->first(); + + if ($currency === 'BTX') { + $balanceBefore = (float) $u->balance; + $vaultBefore = (float) $u->vault_balance; + + if ($vaultBefore < $amount) abort(400, 'Insufficient vault balance'); + + $u->vault_balance = $vaultBefore - $amount; + $u->balance = $balanceBefore + $amount; + $u->withdraw_cooldown_until = now()->addMinutes(30); + $u->save(); + + WalletTransfer::create([ + 'user_id' => $u->id, + 'type' => 'withdraw', + 'amount' => $amount, + 'balance_before' => $balanceBefore, + 'balance_after' => (float) $u->balance, + 'vault_before' => $vaultBefore, + 'vault_after' => (float) $u->vault_balance, + 'currency' => $currency, + 'idempotency_key' => $idempotencyKey, + 'meta' => null, + ]); + } else { + $map = $u->vault_balances ?? []; + $vaultBefore = (float) ($map[$currency] ?? 0); + + if ($vaultBefore < $amount) abort(400, 'Insufficient vault balance'); + + $map[$currency] = number_format($vaultBefore - $amount, 8, '.', ''); + $u->vault_balances = $map; + $u->withdraw_cooldown_until = now()->addMinutes(30); + $u->save(); + + WalletTransfer::create([ + 'user_id' => $u->id, + 'type' => 'withdraw', + 'amount' => $amount, + 'balance_before' => 0, + 'balance_after' => 0, + 'vault_before' => $vaultBefore, + 'vault_after' => (float) $map[$currency], + 'currency' => $currency, + 'idempotency_key' => $idempotencyKey, + 'meta' => null, + ]); + } + + return $this->buildBalanceResponse($u, $currency); + }); + } + + /** + * Build the balance response for a given currency. + */ + private function buildBalanceResponse(User $user, string $currency): array + { + $map = $user->vault_balances ?? []; + return [ + 'balance' => $currency === 'BTX' ? (string) $user->balance : '0', + 'vault_balance' => $currency === 'BTX' + ? (string) $user->vault_balance + : (string) ($map[$currency] ?? '0'), + 'vault_balances' => array_merge(['BTX' => (string) ($user->vault_balance ?? '0')], $map), + ]; + } +} diff --git a/artisan b/artisan new file mode 100644 index 0000000..c35e31d --- /dev/null +++ b/artisan @@ -0,0 +1,18 @@ +#!/usr/bin/env php +handleCommand(new ArgvInput); + +exit($status); diff --git a/auth_test_result.txt b/auth_test_result.txt new file mode 100644 index 0000000..d3ffb17 Binary files /dev/null and b/auth_test_result.txt differ diff --git a/bootstrap/app.php b/bootstrap/app.php new file mode 100644 index 0000000..4deb4ca --- /dev/null +++ b/bootstrap/app.php @@ -0,0 +1,120 @@ +withRouting( + web: __DIR__.'/../routes/web.php', + commands: __DIR__.'/../routes/console.php', + health: '/up', + then: function () { + // B2B operator routes — served at /operator/* (no /api prefix) + \Illuminate\Support\Facades\Route::middleware(['throttle:60,1']) + ->prefix('operator') + ->group(base_path('routes/operator.php')); + }, + ) + ->withSchedule(function (\Illuminate\Console\Scheduling\Schedule $schedule): void { + // Expire old user bonuses every hour + $schedule->call(function () { + app(\App\Services\BonusService::class)->expireBonuses(); + })->hourly()->name('bonus:expire')->withoutOverlapping(); + + // Recalculate VIP levels based on total wager – runs daily at 03:00 + $schedule->call(function () { + $thresholds = [5 => 10000, 4 => 2000, 3 => 500, 2 => 100, 1 => 0]; + \App\Models\User::chunk(200, function ($users) use ($thresholds) { + foreach ($users as $user) { + $totalWager = \App\Models\GameBet::where('user_id', $user->id)->sum('wager_amount'); + $newLevel = 1; + foreach ($thresholds as $level => $min) { + if ($totalWager >= $min) { $newLevel = $level; break; } + } + if ($user->vip_level !== $newLevel) { + $user->forceFill(['vip_level' => $newLevel])->save(); + } + } + }); + })->dailyAt('03:00')->name('vip:recalculate')->withoutOverlapping(); + + // Flag / notify inactive users (no login for 90 days) – runs weekly + $schedule->call(function () { + $cutoff = now()->subDays(90); + \App\Models\User::where('last_login_at', '<', $cutoff) + ->where('is_banned', false) + ->chunk(100, function ($users) { + foreach ($users as $user) { + \Illuminate\Support\Facades\Log::info('Inactive user flagged', [ + 'user_id' => $user->id, + 'last_login_at' => $user->last_login_at, + ]); + } + }); + })->weekly()->name('users:cleanup-inactive')->withoutOverlapping(); + }) + ->withMiddleware(function (Middleware $middleware): void { + $middleware->encryptCookies(except: ['appearance', 'sidebar_state', 'XSRF-TOKEN']); + + $middleware->validateCsrfTokens(except: [ + 'api/webhooks/nowpayments', + 'api/betix/*', + 'wallet/deposits', + 'locale', + 'api/wallet/vault/*', + 'api/wallet/vault', + // Operator B2B API — authenticated via license key, not CSRF + 'operator/*', + ]); + + $middleware->web(append: [ + HandleAppearance::class, + SetLocale::class, + HandleInertiaRequests::class, + AddLinkHeadersForPreloadedAssets::class, + CheckBanned::class, + DetectCiphertextInJson::class, + GeoBlockMiddleware::class, + MaintenanceModeMiddleware::class, + ]); + + // Route middleware aliases + $middleware->alias([ + 'restrict' => \App\Http\Middleware\EnforceRestriction::class, + 'license.key' => \App\Http\Middleware\ValidateLicenseKey::class, + ]); + }) + ->withExceptions(function (Exceptions $exceptions): void { + $exceptions->render(function (\Illuminate\Validation\ValidationException $e, $request) { + if ($request->is('api/*')) { + return response()->json([ + 'message' => 'The given data was invalid.', + 'errors' => $e->errors(), + ], 422); + } + }); + + $exceptions->render(function (\Illuminate\Auth\AuthenticationException $e, $request) { + if ($request->is('api/*')) { + return response()->json(['message' => 'Unauthenticated.'], 401); + } + }); + + $exceptions->render(function (\Symfony\Component\HttpKernel\Exception\HttpException $e, $request) { + if ($request->is('api/*')) { + return response()->json([ + 'message' => $e->getMessage() ?: 'An error occurred.', + 'status' => $e->getStatusCode(), + ], $e->getStatusCode()); + } + }); + })->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..0ad9c57 --- /dev/null +++ b/bootstrap/providers.php @@ -0,0 +1,6 @@ +=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": "dasprid/enum", + "version": "1.0.7", + "source": { + "type": "git", + "url": "https://github.com/DASPRiD/Enum.git", + "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/b5874fa9ed0043116c72162ec7f4fb50e02e7cce", + "reference": "b5874fa9ed0043116c72162ec7f4fb50e02e7cce", + "shasum": "" + }, + "require": { + "php": ">=7.1 <9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "DASPRiD\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "PHP 7.1 enum implementation", + "keywords": [ + "enum", + "map" + ], + "support": { + "issues": "https://github.com/DASPRiD/Enum/issues", + "source": "https://github.com/DASPRiD/Enum/tree/1.0.7" + }, + "time": "2025-09-16T12:23:56+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": "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.10.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "reference": "b51ac707cfa420b7bfd4e4d5e510ba8008e822b4", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^2.3", + "guzzlehttp/psr7": "^2.8", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "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.10.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2025-08-23T22:36:01+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "481557b130ef3790cf82b713667b43030dc9c957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/481557b130ef3790cf82b713667b43030dc9c957", + "reference": "481557b130ef3790cf82b713667b43030dc9c957", + "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" + }, + "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.3.0" + }, + "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": "2025-08-22T14:34:08+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/7d0ed42f28e42d61352a7a79de682e5e67fec884", + "reference": "7d0ed42f28e42d61352a7a79de682e5e67fec884", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "jshttp/mime-db": "1.54.0.1", + "phpunit/phpunit": "^8.5.44 || ^9.6.25" + }, + "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.9.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-03-10T16:41:02+00:00" + }, + { + "name": "guzzlehttp/uri-template", + "version": "v1.0.5", + "source": { + "type": "git", + "url": "https://github.com/guzzle/uri-template.git", + "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/4f4bbd4e7172148801e76e3decc1e559bdee34e1", + "reference": "4f4bbd4e7172148801e76e3decc1e559bdee34e1", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-php80": "^1.24" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25", + "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.5" + }, + "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": "2025-08-22T14:27:06+00:00" + }, + { + "name": "inertiajs/inertia-laravel", + "version": "v2.0.21", + "source": { + "type": "git", + "url": "https://github.com/inertiajs/inertia-laravel.git", + "reference": "fabf202a29023de5f2d59f1474271fa35f84f1d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/inertiajs/inertia-laravel/zipball/fabf202a29023de5f2d59f1474271fa35f84f1d4", + "reference": "fabf202a29023de5f2d59f1474271fa35f84f1d4", + "shasum": "" + }, + "require": { + "ext-json": "*", + "laravel/framework": "^10.0|^11.0|^12.0|^13.0", + "php": "^8.1.0", + "symfony/console": "^6.2|^7.0|^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.2", + "larastan/larastan": "^3.0", + "laravel/pint": "^1.16", + "mockery/mockery": "^1.3.3", + "orchestra/testbench": "^8.0|^9.2|^10.0|^11.0", + "phpunit/phpunit": "^10.4|^11.5|^12.0", + "roave/security-advisories": "dev-master" + }, + "suggest": { + "ext-pcntl": "Recommended when running the Inertia SSR server via the `inertia:start-ssr` artisan command." + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Inertia\\ServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "./helpers.php" + ], + "psr-4": { + "Inertia\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jonathan Reinink", + "email": "jonathan@reinink.ca", + "homepage": "https://reinink.ca" + } + ], + "description": "The Laravel adapter for Inertia.js.", + "keywords": [ + "inertia", + "laravel" + ], + "support": { + "issues": "https://github.com/inertiajs/inertia-laravel/issues", + "source": "https://github.com/inertiajs/inertia-laravel/tree/v2.0.21" + }, + "time": "2026-02-24T20:21:28+00:00" + }, + { + "name": "laravel/fortify", + "version": "v1.36.2", + "source": { + "type": "git", + "url": "https://github.com/laravel/fortify.git", + "reference": "b36e0782e6f5f6cfbab34327895a63b7c4c031f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/fortify/zipball/b36e0782e6f5f6cfbab34327895a63b7c4c031f9", + "reference": "b36e0782e6f5f6cfbab34327895a63b7c4c031f9", + "shasum": "" + }, + "require": { + "bacon/bacon-qr-code": "^3.0", + "ext-json": "*", + "illuminate/console": "^10.0|^11.0|^12.0|^13.0", + "illuminate/support": "^10.0|^11.0|^12.0|^13.0", + "php": "^8.1", + "pragmarx/google2fa": "^9.0" + }, + "require-dev": { + "orchestra/testbench": "^8.36|^9.15|^10.8|^11.0", + "phpstan/phpstan": "^1.10" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Fortify\\FortifyServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Fortify\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Backend controllers and scaffolding for Laravel authentication.", + "keywords": [ + "auth", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/fortify/issues", + "source": "https://github.com/laravel/fortify" + }, + "time": "2026-03-20T20:13:51+00:00" + }, + { + "name": "laravel/framework", + "version": "v12.56.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "dac16d424b59debb2273910dde88eb7050a2a709" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/dac16d424b59debb2273910dde88eb7050a2a709", + "reference": "dac16d424b59debb2273910dde88eb7050a2a709", + "shasum": "" + }, + "require": { + "brick/math": "^0.11|^0.12|^0.13|^0.14", + "composer-runtime-api": "^2.2", + "doctrine/inflector": "^2.0.5", + "dragonmantank/cron-expression": "^3.4", + "egulias/email-validator": "^3.2.1|^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/uri-template": "^1.0", + "laravel/prompts": "^0.3.0", + "laravel/serializable-closure": "^1.3|^2.0", + "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.2", + "psr/container": "^1.1.1|^2.0.1", + "psr/log": "^1.0|^2.0|^3.0", + "psr/simple-cache": "^1.0|^2.0|^3.0", + "ramsey/uuid": "^4.7", + "symfony/console": "^7.2.0", + "symfony/error-handler": "^7.2.0", + "symfony/finder": "^7.2.0", + "symfony/http-foundation": "^7.2.0", + "symfony/http-kernel": "^7.2.0", + "symfony/mailer": "^7.2.0", + "symfony/mime": "^7.2.0", + "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/polyfill-php85": "^1.33", + "symfony/process": "^7.2.0", + "symfony/routing": "^7.2.0", + "symfony/uid": "^7.2.0", + "symfony/var-dumper": "^7.2.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/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/promises": "^2.0.3", + "guzzlehttp/psr7": "^2.4", + "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": "^10.9.0", + "pda/pheanstalk": "^5.0.6|^7.0.0", + "php-http/discovery": "^1.15", + "phpstan/phpstan": "^2.1.41", + "phpunit/phpunit": "^10.5.35|^11.5.3|^12.0.1", + "predis/predis": "^2.3|^3.0", + "resend/resend-php": "^0.10.0|^1.0", + "symfony/cache": "^7.2.0", + "symfony/http-client": "^7.2.0", + "symfony/psr-http-message-bridge": "^7.2.0", + "symfony/translation": "^7.2.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).", + "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 (^5.0).", + "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", + "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.5.3|^12.0.1).", + "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).", + "symfony/cache": "Required to PSR-6 cache bridge (^7.2).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.2).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.2).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.2).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.2).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.2)." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "12.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-03-26T14:51:54+00:00" + }, + { + "name": "laravel/prompts", + "version": "v0.3.16", + "source": { + "type": "git", + "url": "https://github.com/laravel/prompts.git", + "reference": "11e7d5f93803a2190b00e145142cb00a33d17ad2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/prompts/zipball/11e7d5f93803a2190b00e145142cb00a33d17ad2", + "reference": "11e7d5f93803a2190b00e145142cb00a33d17ad2", + "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.16" + }, + "time": "2026-03-23T14:35:33+00:00" + }, + { + "name": "laravel/sanctum", + "version": "v4.3.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/sanctum.git", + "reference": "e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76", + "reference": "e3b85d6e36ad00e5db2d1dcc27c81ffdf15cbf76", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/console": "^11.0|^12.0|^13.0", + "illuminate/contracts": "^11.0|^12.0|^13.0", + "illuminate/database": "^11.0|^12.0|^13.0", + "illuminate/support": "^11.0|^12.0|^13.0", + "php": "^8.2", + "symfony/console": "^7.0|^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.6", + "orchestra/testbench": "^9.15|^10.8|^11.0", + "phpstan/phpstan": "^1.10" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Sanctum\\SanctumServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sanctum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.", + "keywords": [ + "auth", + "laravel", + "sanctum" + ], + "support": { + "issues": "https://github.com/laravel/sanctum/issues", + "source": "https://github.com/laravel/sanctum" + }, + "time": "2026-02-07T17:19:31+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v2.0.10", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/870fc81d2f879903dfc5b60bf8a0f94a1609e669", + "reference": "870fc81d2f879903dfc5b60bf8a0f94a1609e669", + "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-02-20T19:59:49+00:00" + }, + { + "name": "laravel/tinker", + "version": "v2.11.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/tinker.git", + "reference": "c9f80cc835649b5c1842898fb043f8cc098dd741" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/tinker/zipball/c9f80cc835649b5c1842898fb043f8cc098dd741", + "reference": "c9f80cc835649b5c1842898fb043f8cc098dd741", + "shasum": "" + }, + "require": { + "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", + "php": "^7.2.5|^8.0", + "psy/psysh": "^0.11.1|^0.12.0", + "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0|^8.0" + }, + "require-dev": { + "mockery/mockery": "~1.3.3|^1.4.2", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8.5.8|^9.3.3|^10.0" + }, + "suggest": { + "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0)." + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Tinker\\TinkerServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Tinker\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Powerful REPL for the Laravel framework.", + "keywords": [ + "REPL", + "Tinker", + "laravel", + "psysh" + ], + "support": { + "issues": "https://github.com/laravel/tinker/issues", + "source": "https://github.com/laravel/tinker/tree/v2.11.1" + }, + "time": "2026-02-06T14:12:35+00:00" + }, + { + "name": "league/commonmark", + "version": "2.8.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "59fb075d2101740c337c7216e3f32b36c204218b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/59fb075d2101740c337c7216e3f32b36c204218b", + "reference": "59fb075d2101740c337c7216e3f32b36c204218b", + "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": "^1.8.2", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.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-03-19T13:16:38+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/flysystem", + "version": "3.33.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "570b8871e0ce693764434b29154c54b434905350" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/570b8871e0ce693764434b29154c54b434905350", + "reference": "570b8871e0ce693764434b29154c54b434905350", + "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.33.0" + }, + "time": "2026-03-25T07:59:30+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.16.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/2d6702ff215bf922936ccc1ad31007edc76451b9", + "reference": "2d6702ff215bf922936ccc1ad31007edc76451b9", + "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" + }, + "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.16.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2024-09-21T08:32:55+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.11.3", + "source": { + "type": "git", + "url": "https://github.com/CarbonPHP/carbon.git", + "reference": "6a7e652845bb018c668220c2a545aded8594fbbf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/6a7e652845bb018c668220c2a545aded8594fbbf", + "reference": "6a7e652845bb018c668220c2a545aded8594fbbf", + "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-03-11T17:23:39+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.3", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/bb3ea637e3d131d72acc033cfc2746ee893349fe", + "reference": "bb3ea637e3d131d72acc033cfc2746ee893349fe", + "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::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "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.3" + }, + "time": "2026-02-13T03:05:33+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.7.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "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.7.0" + }, + "time": "2025-12-06T11:56:16+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": "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": "pragmarx/google2fa", + "version": "v9.0.0", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/google2fa.git", + "reference": "e6bc62dd6ae83acc475f57912e27466019a1f2cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/e6bc62dd6ae83acc475f57912e27466019a1f2cf", + "reference": "e6bc62dd6ae83acc475f57912e27466019a1f2cf", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "^1.0|^2.0|^3.0", + "php": "^7.1|^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.9", + "phpunit/phpunit": "^7.5.15|^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PragmaRX\\Google2FA\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "email": "acr@antoniocarlosribeiro.com", + "role": "Creator & Designer" + } + ], + "description": "A One Time Password Authentication package, compatible with Google Authenticator.", + "keywords": [ + "2fa", + "Authentication", + "Two Factor Authentication", + "google2fa" + ], + "support": { + "issues": "https://github.com/antonioribeiro/google2fa/issues", + "source": "https://github.com/antonioribeiro/google2fa/tree/v9.0.0" + }, + "time": "2025-09-19T22:51:08+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/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.22", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "3be75d5b9244936dd4ac62ade2bfb004d13acf0f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/3be75d5b9244936dd4ac62ade2bfb004d13acf0f", + "reference": "3be75d5b9244936dd4ac62ade2bfb004d13acf0f", + "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.22" + }, + "time": "2026-03-22T23:03:24+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.2", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "8429c78ca35a09f27565311b98101e2826affde0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/8429c78ca35a09f27565311b98101e2826affde0", + "reference": "8429c78ca35a09f27565311b98101e2826affde0", + "shasum": "" + }, + "require": { + "brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14", + "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.2" + }, + "time": "2025-12-14T04:43:48+00:00" + }, + { + "name": "symfony/clock", + "version": "v8.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/clock.git", + "reference": "832119f9b8dbc6c8e6f65f30c5969eca1e88764f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/clock/zipball/832119f9b8dbc6c8e6f65f30c5969eca1e88764f", + "reference": "832119f9b8dbc6c8e6f65f30c5969eca1e88764f", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "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.0.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": "2025-11-12T15:46:48+00:00" + }, + { + "name": "symfony/console", + "version": "v7.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/e1e6770440fb9c9b0cf725f81d1361ad1835329d", + "reference": "e1e6770440fb9c9b0cf725f81d1361ad1835329d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2|^8.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/lock": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^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/v7.4.7" + }, + "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-03-06T14:06:20+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v8.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "2a178bf80f05dbbe469a337730eba79d61315262" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/2a178bf80f05dbbe469a337730eba79d61315262", + "reference": "2a178bf80f05dbbe469a337730eba79d61315262", + "shasum": "" + }, + "require": { + "php": ">=8.4" + }, + "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.0.6" + }, + "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-02-17T13:07:04+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-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.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/error-handler", + "version": "v7.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/error-handler.git", + "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/8da531f364ddfee53e36092a7eebbbd0b775f6b8", + "reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/polyfill-php85": "^1.32", + "symfony/var-dumper": "^6.4|^7.0|^8.0" + }, + "conflict": { + "symfony/deprecation-contracts": "<2.5", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4|^7.0|^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/v7.4.4" + }, + "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-01-20T16:42:42+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v8.0.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "99301401da182b6cfaa4700dbe9987bb75474b47" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/99301401da182b6cfaa4700dbe9987bb75474b47", + "reference": "99301401da182b6cfaa4700dbe9987bb75474b47", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "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.0.4" + }, + "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-01-05T11:45:55+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", + "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.6-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.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/8655bf1076b7a3a346cb11413ffdabff50c7ffcf", + "reference": "8655bf1076b7a3a346cb11413ffdabff50c7ffcf", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0|^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/v7.4.6" + }, + "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-01-29T09:40:50+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v7.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f94b3e7b7dafd40e666f0c9ff2084133bae41e81", + "reference": "f94b3e7b7dafd40e666f0c9ff2084133bae41e81", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "^1.1" + }, + "conflict": { + "doctrine/dbal": "<3.6", + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" + }, + "require-dev": { + "doctrine/dbal": "^3.6|^4", + "predis/predis": "^1.1|^2.0", + "symfony/cache": "^6.4.12|^7.1.5|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/mime": "^6.4|^7.0|^8.0", + "symfony/rate-limiter": "^6.4|^7.0|^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/v7.4.7" + }, + "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-03-06T13:15:18+00:00" + }, + { + "name": "symfony/http-kernel", + "version": "v7.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/3b3fcf386c809be990c922e10e4c620d6367cab1", + "reference": "3b3fcf386c809be990c922e10e4c620d6367cab1", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/error-handler": "^6.4|^7.0|^8.0", + "symfony/event-dispatcher": "^7.3|^8.0", + "symfony/http-foundation": "^7.4|^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/browser-kit": "<6.4", + "symfony/cache": "<6.4", + "symfony/config": "<6.4", + "symfony/console": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/doctrine-bridge": "<6.4", + "symfony/flex": "<2.10", + "symfony/form": "<6.4", + "symfony/http-client": "<6.4", + "symfony/http-client-contracts": "<2.5", + "symfony/mailer": "<6.4", + "symfony/messenger": "<6.4", + "symfony/translation": "<6.4", + "symfony/translation-contracts": "<2.5", + "symfony/twig-bridge": "<6.4", + "symfony/validator": "<6.4", + "symfony/var-dumper": "<6.4", + "twig/twig": "<3.12" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "symfony/browser-kit": "^6.4|^7.0|^8.0", + "symfony/clock": "^6.4|^7.0|^8.0", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/css-selector": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4.1|^7.0.1|^8.0", + "symfony/dom-crawler": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/finder": "^6.4|^7.0|^8.0", + "symfony/http-client-contracts": "^2.5|^3", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^7.1|^8.0", + "symfony/routing": "^6.4|^7.0|^8.0", + "symfony/serializer": "^7.1|^8.0", + "symfony/stopwatch": "^6.4|^7.0|^8.0", + "symfony/translation": "^6.4|^7.0|^8.0", + "symfony/translation-contracts": "^2.5|^3", + "symfony/uid": "^6.4|^7.0|^8.0", + "symfony/validator": "^6.4|^7.0|^8.0", + "symfony/var-dumper": "^6.4|^7.0|^8.0", + "symfony/var-exporter": "^6.4|^7.0|^8.0", + "twig/twig": "^3.12" + }, + "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/v7.4.7" + }, + "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-03-06T16:33:18+00:00" + }, + { + "name": "symfony/mailer", + "version": "v7.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/mailer.git", + "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mailer/zipball/b02726f39a20bc65e30364f5c750c4ddbf1f58e9", + "reference": "b02726f39a20bc65e30364f5c750c4ddbf1f58e9", + "shasum": "" + }, + "require": { + "egulias/email-validator": "^2.1.10|^3|^4", + "php": ">=8.2", + "psr/event-dispatcher": "^1", + "psr/log": "^1|^2|^3", + "symfony/event-dispatcher": "^6.4|^7.0|^8.0", + "symfony/mime": "^7.2|^8.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<6.4", + "symfony/messenger": "<6.4", + "symfony/mime": "<6.4", + "symfony/twig-bridge": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-client": "^6.4|^7.0|^8.0", + "symfony/messenger": "^6.4|^7.0|^8.0", + "symfony/twig-bridge": "^6.4|^7.0|^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/v7.4.6" + }, + "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-02-25T16:50:00+00:00" + }, + { + "name": "symfony/mime", + "version": "v7.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/mime.git", + "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/mime/zipball/da5ab4fde3f6c88ab06e96185b9922f48b677cd1", + "reference": "da5ab4fde3f6c88ab06e96185b9922f48b677cd1", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "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", + "symfony/mailer": "<6.4", + "symfony/serializer": "<6.4.3|>7.0,<7.0.3" + }, + "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": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/property-access": "^6.4|^7.0|^8.0", + "symfony/property-info": "^6.4|^7.0|^8.0", + "symfony/serializer": "^6.4.3|^7.0.3|^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/v7.4.7" + }, + "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-03-05T15:24:09+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "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.33.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": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", + "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", + "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.33.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": "2025-06-27T09:58:17+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3", + "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.33.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": "2024-09-10T14:38:51+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "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.33.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": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "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.33.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": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "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.33.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": "2025-01-02T08:10:11+00:00" + }, + { + "name": "symfony/polyfill-php83", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "reference": "17f6f9a6b1735c0f163024d959f700cfbc5155e5", + "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\\Php83\\": "" + }, + "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.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.33.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": "2025-07-08T02:45:35+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "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.33.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": "2025-06-24T13:30:11+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "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.33.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": "2025-06-23T16:12:55+00:00" + }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2", + "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.33.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": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/process", + "version": "v7.4.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "608476f4604102976d687c483ac63a79ba18cc97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/608476f4604102976d687c483ac63a79ba18cc97", + "reference": "608476f4604102976d687c483ac63a79ba18cc97", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "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/v7.4.5" + }, + "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-01-26T15:07:59+00:00" + }, + { + "name": "symfony/routing", + "version": "v7.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "238d749c56b804b31a9bf3e26519d93b65a60938" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/238d749c56b804b31a9bf3e26519d93b65a60938", + "reference": "238d749c56b804b31a9bf3e26519d93b65a60938", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0|^8.0", + "symfony/dependency-injection": "^6.4|^7.0|^8.0", + "symfony/expression-language": "^6.4|^7.0|^8.0", + "symfony/http-foundation": "^6.4|^7.0|^8.0", + "symfony/yaml": "^6.4|^7.0|^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/v7.4.6" + }, + "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-02-25T16:50:00+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "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.6-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.6.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": "2025-07-15T11:30:57+00:00" + }, + { + "name": "symfony/string", + "version": "v8.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/6c9e1108041b5dce21a9a4984b531c4923aa9ec4", + "reference": "6c9e1108041b5dce21a9a4984b531c4923aa9ec4", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "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.0.6" + }, + "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-02-09T10:14:57+00:00" + }, + { + "name": "symfony/translation", + "version": "v8.0.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "13ff19bcf2bea492d3c2fbeaa194dd6f4599ce1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/13ff19bcf2bea492d3c2fbeaa194dd6f4599ce1b", + "reference": "13ff19bcf2bea492d3c2fbeaa194dd6f4599ce1b", + "shasum": "" + }, + "require": { + "php": ">=8.4", + "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.0.6" + }, + "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-02-17T13:07:04+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v3.6.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-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.6.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": "2025-07-15T13:41:35+00:00" + }, + { + "name": "symfony/uid", + "version": "v7.4.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "7719ce8aba76be93dfe249192f1fbfa52c588e36" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/7719ce8aba76be93dfe249192f1fbfa52c588e36", + "reference": "7719ce8aba76be93dfe249192f1fbfa52c588e36", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^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/v7.4.4" + }, + "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-01-03T23:30:35+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v7.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/045321c440ac18347b136c63d2e9bf28a2dc0291", + "reference": "045321c440ac18347b136c63d2e9bf28a2dc0291", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0", + "symfony/http-kernel": "^6.4|^7.0|^8.0", + "symfony/process": "^6.4|^7.0|^8.0", + "symfony/uid": "^6.4|^7.0|^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/v7.4.6" + }, + "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-02-15T10:53:20+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.3", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "955e7815d677a3eaa7075231212f2110983adecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc", + "reference": "955e7815d677a3eaa7075231212f2110983adecc", + "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.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:49:13+00:00" + }, + { + "name": "voku/portable-ascii", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/voku/portable-ascii.git", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "shasum": "" + }, + "require": { + "php": ">=7.0.0" + }, + "require-dev": { + "phpunit/phpunit": "~6.0 || ~7.0 || ~9.0" + }, + "suggest": { + "ext-intl": "Use Intl for transliterator_transliterate() support" + }, + "type": "library", + "autoload": { + "psr-4": { + "voku\\": "src/voku/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Lars Moelleken", + "homepage": "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.0.3" + }, + "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": "2024-11-21T01:49:47+00:00" + } + ], + "packages-dev": [ + { + "name": "brianium/paratest", + "version": "v7.20.0", + "source": { + "type": "git", + "url": "https://github.com/paratestphp/paratest.git", + "reference": "81c80677c9ec0ed4ef16b246167f11dec81a6e3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/81c80677c9ec0ed4ef16b246167f11dec81a6e3d", + "reference": "81c80677c9ec0ed4ef16b246167f11dec81a6e3d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-simplexml": "*", + "fidry/cpu-core-counter": "^1.3.0", + "jean85/pretty-package-versions": "^2.1.1", + "php": "~8.3.0 || ~8.4.0 || ~8.5.0", + "phpunit/php-code-coverage": "^12.5.3 || ^13.0.1", + "phpunit/php-file-iterator": "^6.0.1 || ^7", + "phpunit/php-timer": "^8 || ^9", + "phpunit/phpunit": "^12.5.14 || ^13.0.5", + "sebastian/environment": "^8.0.3 || ^9", + "symfony/console": "^7.4.7 || ^8.0.7", + "symfony/process": "^7.4.5 || ^8.0.5" + }, + "require-dev": { + "doctrine/coding-standard": "^14.0.0", + "ext-pcntl": "*", + "ext-pcov": "*", + "ext-posix": "*", + "phpstan/phpstan": "^2.1.44", + "phpstan/phpstan-deprecation-rules": "^2.0.4", + "phpstan/phpstan-phpunit": "^2.0.16", + "phpstan/phpstan-strict-rules": "^2.0.10", + "symfony/filesystem": "^7.4.6 || ^8.0.6" + }, + "bin": [ + "bin/paratest", + "bin/paratest_for_phpstorm" + ], + "type": "library", + "autoload": { + "psr-4": { + "ParaTest\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Scaturro", + "email": "scaturrob@gmail.com", + "role": "Developer" + }, + { + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com", + "role": "Developer" + } + ], + "description": "Parallel testing for PHP", + "homepage": "https://github.com/paratestphp/paratest", + "keywords": [ + "concurrent", + "parallel", + "phpunit", + "testing" + ], + "support": { + "issues": "https://github.com/paratestphp/paratest/issues", + "source": "https://github.com/paratestphp/paratest/tree/v7.20.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/Slamdunk", + "type": "github" + }, + { + "url": "https://paypal.me/filippotessarotto", + "type": "paypal" + } + ], + "time": "2026-03-29T15:46:14+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.6", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "reference": "d4fe3e6fd9bb9e72557a19674f44d8ac7db4c6ca", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=14" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^14", + "phpstan/phpstan": "1.4.10 || 2.1.30", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12.4 || ^13.0", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.6" + }, + "time": "2026-02-07T07:09:04+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": "fidry/cpu-core-counter", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", + "reference": "db9508f7b1474469d9d3c53b86f817e344732678", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2025-08-14T07:29:31+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": "jean85/pretty-package-versions", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/4d7aa5dab42e2a76d99559706022885de0e18e1a", + "reference": "4d7aa5dab42e2a76d99559706022885de0e18e1a", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.1.0", + "php": "^7.4|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "jean85/composer-provided-replaced-stub-package": "^1.0", + "phpstan/phpstan": "^2.0", + "phpunit/phpunit": "^7.5|^8.5|^9.6", + "rector/rector": "^2.0", + "vimeo/psalm": "^4.3 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A library to get pretty versions strings of installed dependencies", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "support": { + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.1" + }, + "time": "2025-03-19T14:43:43+00:00" + }, + { + "name": "laravel/boost", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/boost.git", + "reference": "775801e5c81bfe387c90aee33e44d2882d049ee7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/boost/zipball/775801e5c81bfe387c90aee33e44d2882d049ee7", + "reference": "775801e5c81bfe387c90aee33e44d2882d049ee7", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^7.9", + "illuminate/console": "^11.45.3|^12.41.1", + "illuminate/contracts": "^11.45.3|^12.41.1", + "illuminate/routing": "^11.45.3|^12.41.1", + "illuminate/support": "^11.45.3|^12.41.1", + "laravel/mcp": "^0.5.1", + "laravel/prompts": "^0.3.10", + "laravel/roster": "^0.2.9", + "php": "^8.2" + }, + "require-dev": { + "laravel/pint": "^1.27.0", + "mockery/mockery": "^1.6.12", + "orchestra/testbench": "^9.15.0|^10.6", + "pestphp/pest": "^2.36.0|^3.8.4|^4.1.5", + "phpstan/phpstan": "^2.1.27", + "rector/rector": "^2.1" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Boost\\BoostServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Boost\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Laravel Boost accelerates AI-assisted development by providing the essential context and structure that AI needs to generate high-quality, Laravel-specific code.", + "homepage": "https://github.com/laravel/boost", + "keywords": [ + "ai", + "dev", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/boost/issues", + "source": "https://github.com/laravel/boost" + }, + "time": "2026-01-24T11:34:22+00:00" + }, + { + "name": "laravel/mcp", + "version": "v0.5.9", + "source": { + "type": "git", + "url": "https://github.com/laravel/mcp.git", + "reference": "39e8da60eb7bce4737c5d868d35a3fe78938c129" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/mcp/zipball/39e8da60eb7bce4737c5d868d35a3fe78938c129", + "reference": "39e8da60eb7bce4737c5d868d35a3fe78938c129", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "illuminate/console": "^11.45.3|^12.41.1|^13.0", + "illuminate/container": "^11.45.3|^12.41.1|^13.0", + "illuminate/contracts": "^11.45.3|^12.41.1|^13.0", + "illuminate/http": "^11.45.3|^12.41.1|^13.0", + "illuminate/json-schema": "^12.41.1|^13.0", + "illuminate/routing": "^11.45.3|^12.41.1|^13.0", + "illuminate/support": "^11.45.3|^12.41.1|^13.0", + "illuminate/validation": "^11.45.3|^12.41.1|^13.0", + "php": "^8.2" + }, + "require-dev": { + "laravel/pint": "^1.20", + "orchestra/testbench": "^9.15|^10.8|^11.0", + "pestphp/pest": "^3.8.5|^4.3.2", + "phpstan/phpstan": "^2.1.27", + "rector/rector": "^2.2.4" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Mcp": "Laravel\\Mcp\\Server\\Facades\\Mcp" + }, + "providers": [ + "Laravel\\Mcp\\Server\\McpServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Mcp\\": "src/", + "Laravel\\Mcp\\Server\\": "src/Server/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Rapidly build MCP servers for your Laravel applications.", + "homepage": "https://github.com/laravel/mcp", + "keywords": [ + "laravel", + "mcp" + ], + "support": { + "issues": "https://github.com/laravel/mcp/issues", + "source": "https://github.com/laravel/mcp" + }, + "time": "2026-02-17T19:05:53+00:00" + }, + { + "name": "laravel/pail", + "version": "v1.2.6", + "source": { + "type": "git", + "url": "https://github.com/laravel/pail.git", + "reference": "aa71a01c309e7f66bc2ec4fb1a59291b82eb4abf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/pail/zipball/aa71a01c309e7f66bc2ec4fb1a59291b82eb4abf", + "reference": "aa71a01c309e7f66bc2ec4fb1a59291b82eb4abf", + "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-02-09T13:44:54+00:00" + }, + { + "name": "laravel/pint", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/pint.git", + "reference": "bdec963f53172c5e36330f3a400604c69bf02d39" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/pint/zipball/bdec963f53172c5e36330f3a400604c69bf02d39", + "reference": "bdec963f53172c5e36330f3a400604c69bf02d39", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "ext-tokenizer": "*", + "ext-xml": "*", + "php": "^8.2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.94.2", + "illuminate/view": "^12.54.1", + "larastan/larastan": "^3.9.3", + "laravel-zero/framework": "^12.0.5", + "mockery/mockery": "^1.6.12", + "nunomaduro/termwind": "^2.4.0", + "pestphp/pest": "^3.8.6", + "shipfastlabs/agent-detector": "^1.1.0" + }, + "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-03-12T15:51:39+00:00" + }, + { + "name": "laravel/roster", + "version": "v0.2.9", + "source": { + "type": "git", + "url": "https://github.com/laravel/roster.git", + "reference": "82bbd0e2de614906811aebdf16b4305956816fa6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/roster/zipball/82bbd0e2de614906811aebdf16b4305956816fa6", + "reference": "82bbd0e2de614906811aebdf16b4305956816fa6", + "shasum": "" + }, + "require": { + "illuminate/console": "^10.0|^11.0|^12.0", + "illuminate/contracts": "^10.0|^11.0|^12.0", + "illuminate/routing": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", + "php": "^8.1|^8.2", + "symfony/yaml": "^6.4|^7.2" + }, + "require-dev": { + "laravel/pint": "^1.14", + "mockery/mockery": "^1.6", + "orchestra/testbench": "^8.22.0|^9.0|^10.0", + "pestphp/pest": "^2.0|^3.0", + "phpstan/phpstan": "^2.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Roster\\RosterServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Roster\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Detect packages & approaches in use within a Laravel project", + "homepage": "https://github.com/laravel/roster", + "keywords": [ + "dev", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/roster/issues", + "source": "https://github.com/laravel/roster" + }, + "time": "2025-10-20T09:56:46+00:00" + }, + { + "name": "laravel/sail", + "version": "v1.55.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/sail.git", + "reference": "67dc1b72da4e066a2fb54c1c7582fd2f140ea191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sail/zipball/67dc1b72da4e066a2fb54c1c7582fd2f140ea191", + "reference": "67dc1b72da4e066a2fb54c1c7582fd2f140ea191", + "shasum": "" + }, + "require": { + "illuminate/console": "^9.52.16|^10.0|^11.0|^12.0|^13.0", + "illuminate/contracts": "^9.52.16|^10.0|^11.0|^12.0|^13.0", + "illuminate/support": "^9.52.16|^10.0|^11.0|^12.0|^13.0", + "php": "^8.0", + "symfony/console": "^6.0|^7.0|^8.0", + "symfony/yaml": "^6.0|^7.0|^8.0" + }, + "require-dev": { + "orchestra/testbench": "^7.0|^8.0|^9.0|^10.0|^11.0", + "phpstan/phpstan": "^2.0" + }, + "bin": [ + "bin/sail" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Sail\\SailServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sail\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Docker files for running a basic Laravel application.", + "keywords": [ + "docker", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/sail/issues", + "source": "https://github.com/laravel/sail" + }, + "time": "2026-03-23T15:56:34+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.1", + "source": { + "type": "git", + "url": "https://github.com/nunomaduro/collision.git", + "reference": "a1ed3fa530fd60bc515f9303e8520fcb7d4bd935" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/a1ed3fa530fd60bc515f9303e8520fcb7d4bd935", + "reference": "a1ed3fa530fd60bc515f9303e8520fcb7d4bd935", + "shasum": "" + }, + "require": { + "filp/whoops": "^2.18.4", + "nunomaduro/termwind": "^2.4.0", + "php": "^8.2.0", + "symfony/console": "^7.4.4 || ^8.0.4" + }, + "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.9.2", + "laravel/framework": "^11.48.0 || ^12.52.0", + "laravel/pint": "^1.27.1", + "orchestra/testbench-core": "^9.12.0 || ^10.9.0", + "pestphp/pest": "^3.8.5 || ^4.4.1 || ^5.0.0", + "sebastian/environment": "^7.2.1 || ^8.0.3 || ^9.0.0" + }, + "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-02-17T17:33:08+00:00" + }, + { + "name": "pestphp/pest", + "version": "v4.4.3", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest.git", + "reference": "e6ab897594312728ef2e32d586cb4f6780b1b495" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest/zipball/e6ab897594312728ef2e32d586cb4f6780b1b495", + "reference": "e6ab897594312728ef2e32d586cb4f6780b1b495", + "shasum": "" + }, + "require": { + "brianium/paratest": "^7.19.2", + "nunomaduro/collision": "^8.9.1", + "nunomaduro/termwind": "^2.4.0", + "pestphp/pest-plugin": "^4.0.0", + "pestphp/pest-plugin-arch": "^4.0.0", + "pestphp/pest-plugin-mutate": "^4.0.1", + "pestphp/pest-plugin-profanity": "^4.2.1", + "php": "^8.3.0", + "phpunit/phpunit": "^12.5.14", + "symfony/process": "^7.4.5|^8.0.5" + }, + "conflict": { + "filp/whoops": "<2.18.3", + "phpunit/phpunit": ">12.5.14", + "sebastian/exporter": "<7.0.0", + "webmozart/assert": "<1.11.0" + }, + "require-dev": { + "pestphp/pest-dev-tools": "^4.1.0", + "pestphp/pest-plugin-browser": "^4.3.0", + "pestphp/pest-plugin-type-coverage": "^4.0.3", + "psy/psysh": "^0.12.21" + }, + "bin": [ + "bin/pest" + ], + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Mutate\\Plugins\\Mutate", + "Pest\\Plugins\\Configuration", + "Pest\\Plugins\\Bail", + "Pest\\Plugins\\Cache", + "Pest\\Plugins\\Coverage", + "Pest\\Plugins\\Init", + "Pest\\Plugins\\Environment", + "Pest\\Plugins\\Help", + "Pest\\Plugins\\Memory", + "Pest\\Plugins\\Only", + "Pest\\Plugins\\Printer", + "Pest\\Plugins\\ProcessIsolation", + "Pest\\Plugins\\Profile", + "Pest\\Plugins\\Retry", + "Pest\\Plugins\\Snapshot", + "Pest\\Plugins\\Verbose", + "Pest\\Plugins\\Version", + "Pest\\Plugins\\Shard", + "Pest\\Plugins\\Parallel" + ] + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "files": [ + "src/Functions.php", + "src/Pest.php" + ], + "psr-4": { + "Pest\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "The elegant PHP Testing Framework.", + "keywords": [ + "framework", + "pest", + "php", + "test", + "testing", + "unit" + ], + "support": { + "issues": "https://github.com/pestphp/pest/issues", + "source": "https://github.com/pestphp/pest/tree/v4.4.3" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2026-03-21T13:14:39+00:00" + }, + { + "name": "pestphp/pest-plugin", + "version": "v4.0.0", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin.git", + "reference": "9d4b93d7f73d3f9c3189bb22c220fef271cdf568" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin/zipball/9d4b93d7f73d3f9c3189bb22c220fef271cdf568", + "reference": "9d4b93d7f73d3f9c3189bb22c220fef271cdf568", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^2.0.0", + "composer-runtime-api": "^2.2.2", + "php": "^8.3" + }, + "conflict": { + "pestphp/pest": "<4.0.0" + }, + "require-dev": { + "composer/composer": "^2.8.10", + "pestphp/pest": "^4.0.0", + "pestphp/pest-dev-tools": "^4.0.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Pest\\Plugin\\Manager" + }, + "autoload": { + "psr-4": { + "Pest\\Plugin\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Pest plugin manager", + "keywords": [ + "framework", + "manager", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin/tree/v4.0.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=66BYDWAT92N6L", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + }, + { + "url": "https://www.patreon.com/nunomaduro", + "type": "patreon" + } + ], + "time": "2025-08-20T12:35:58+00:00" + }, + { + "name": "pestphp/pest-plugin-arch", + "version": "v4.0.0", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-arch.git", + "reference": "25bb17e37920ccc35cbbcda3b00d596aadf3e58d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/25bb17e37920ccc35cbbcda3b00d596aadf3e58d", + "reference": "25bb17e37920ccc35cbbcda3b00d596aadf3e58d", + "shasum": "" + }, + "require": { + "pestphp/pest-plugin": "^4.0.0", + "php": "^8.3", + "ta-tikoma/phpunit-architecture-test": "^0.8.5" + }, + "require-dev": { + "pestphp/pest": "^4.0.0", + "pestphp/pest-dev-tools": "^4.0.0" + }, + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Arch\\Plugin" + ] + } + }, + "autoload": { + "files": [ + "src/Autoload.php" + ], + "psr-4": { + "Pest\\Arch\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Arch plugin for Pest PHP.", + "keywords": [ + "arch", + "architecture", + "framework", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-arch/tree/v4.0.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2025-08-20T13:10:51+00:00" + }, + { + "name": "pestphp/pest-plugin-laravel", + "version": "v4.1.0", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-laravel.git", + "reference": "3057a36669ff11416cc0dc2b521b3aec58c488d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-laravel/zipball/3057a36669ff11416cc0dc2b521b3aec58c488d0", + "reference": "3057a36669ff11416cc0dc2b521b3aec58c488d0", + "shasum": "" + }, + "require": { + "laravel/framework": "^11.45.2|^12.52.0|^13.0", + "pestphp/pest": "^4.4.1", + "php": "^8.3.0" + }, + "require-dev": { + "laravel/dusk": "^8.3.6", + "orchestra/testbench": "^9.13.0|^10.9.0|^11.0", + "pestphp/pest-dev-tools": "^4.1.0" + }, + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Laravel\\Plugin" + ] + }, + "laravel": { + "providers": [ + "Pest\\Laravel\\PestServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/Autoload.php" + ], + "psr-4": { + "Pest\\Laravel\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Pest Laravel Plugin", + "keywords": [ + "framework", + "laravel", + "pest", + "php", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-laravel/tree/v4.1.0" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2026-02-21T00:29:45+00:00" + }, + { + "name": "pestphp/pest-plugin-mutate", + "version": "v4.0.1", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-mutate.git", + "reference": "d9b32b60b2385e1688a68cc227594738ec26d96c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-mutate/zipball/d9b32b60b2385e1688a68cc227594738ec26d96c", + "reference": "d9b32b60b2385e1688a68cc227594738ec26d96c", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.6.1", + "pestphp/pest-plugin": "^4.0.0", + "php": "^8.3", + "psr/simple-cache": "^3.0.0" + }, + "require-dev": { + "pestphp/pest": "^4.0.0", + "pestphp/pest-dev-tools": "^4.0.0", + "pestphp/pest-plugin-type-coverage": "^4.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Pest\\Mutate\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + }, + { + "name": "Sandro Gehri", + "email": "sandrogehri@gmail.com" + } + ], + "description": "Mutates your code to find untested cases", + "keywords": [ + "framework", + "mutate", + "mutation", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-mutate/tree/v4.0.1" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/gehrisandro", + "type": "github" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2025-08-21T20:19:25+00:00" + }, + { + "name": "pestphp/pest-plugin-profanity", + "version": "v4.2.1", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-profanity.git", + "reference": "343cfa6f3564b7e35df0ebb77b7fa97039f72b27" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-profanity/zipball/343cfa6f3564b7e35df0ebb77b7fa97039f72b27", + "reference": "343cfa6f3564b7e35df0ebb77b7fa97039f72b27", + "shasum": "" + }, + "require": { + "pestphp/pest-plugin": "^4.0.0", + "php": "^8.3" + }, + "require-dev": { + "faissaloux/pest-plugin-inside": "^1.9", + "pestphp/pest": "^4.0.0", + "pestphp/pest-dev-tools": "^4.0.0" + }, + "type": "library", + "extra": { + "pest": { + "plugins": [ + "Pest\\Profanity\\Plugin" + ] + } + }, + "autoload": { + "psr-4": { + "Pest\\Profanity\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "The Pest Profanity Plugin", + "keywords": [ + "framework", + "pest", + "php", + "plugin", + "profanity", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-profanity/tree/v4.2.1" + }, + "time": "2025-12-08T00:13:17+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": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "6.0.3", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/7bae67520aa9f5ecc506d646810bd40d9da54582", + "reference": "7bae67520aa9f5ecc506d646810bd40d9da54582", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^2.0", + "phpstan/phpdoc-parser": "^2.0", + "webmozart/assert": "^1.9.1 || ^2" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26", + "shipmonk/dead-code-detector": "^0.5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/6.0.3" + }, + "time": "2026-03-18T20:49:53+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/327a05bbee54120d4786a0dc67aad30226ad4cf9", + "reference": "327a05bbee54120d4786a0dc67aad30226ad4cf9", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev", + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/2.0.0" + }, + "time": "2026-01-06T21:53:42+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "2.3.2", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/a004701b11273a26cd7955a61d67a7f1e525a45a", + "reference": "a004701b11273a26cd7955a61d67a7f1e525a45a", + "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.2" + }, + "time": "2026-01-25T14:56:51+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "12.5.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "b015312f28dd75b75d3422ca37dff2cd1a565e8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b015312f28dd75b75d3422ca37dff2cd1a565e8d", + "reference": "b015312f28dd75b75d3422ca37dff2cd1a565e8d", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.7.0", + "php": ">=8.3", + "phpunit/php-file-iterator": "^6.0", + "phpunit/php-text-template": "^5.0", + "sebastian/complexity": "^5.0", + "sebastian/environment": "^8.0.3", + "sebastian/lines-of-code": "^4.0", + "sebastian/version": "^6.0", + "theseer/tokenizer": "^2.0.1" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.1" + }, + "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.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/phpunit/php-code-coverage", + "type": "tidelift" + } + ], + "time": "2026-02-06T06:01:44+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.14", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "47283cfd98d553edcb1353591f4e255dc1bb61f0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/47283cfd98d553edcb1353591f4e255dc1bb61f0", + "reference": "47283cfd98d553edcb1353591f4e255dc1bb61f0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "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.3", + "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.0", + "sebastian/comparator": "^7.1.4", + "sebastian/diff": "^7.0.0", + "sebastian/environment": "^8.0.3", + "sebastian/exporter": "^7.0.2", + "sebastian/global-state": "^8.0.2", + "sebastian/object-enumerator": "^7.0.0", + "sebastian/recursion-context": "^7.0.1", + "sebastian/type": "^6.0.3", + "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.14" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "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/phpunit", + "type": "tidelift" + } + ], + "time": "2026-02-18T12:38:40+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "4.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/90f41072d220e5c40df6e8635f5dafba2d9d4d04", + "reference": "90f41072d220e5c40df6e8635f5dafba2d9d4d04", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "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.0" + }, + "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": "2025-09-14T09:36:45+00:00" + }, + { + "name": "sebastian/comparator", + "version": "7.1.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "6a7de5df2e094f9a80b40a522391a7e6022df5f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/6a7de5df2e094f9a80b40a522391a7e6022df5f6", + "reference": "6a7de5df2e094f9a80b40a522391a7e6022df5f6", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/diff": "^7.0", + "sebastian/exporter": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^12.2" + }, + "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.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/comparator", + "type": "tidelift" + } + ], + "time": "2026-01-24T09:28:48+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.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "7b8842c2d8e85d0c3a5831236bf5869af6ab2a11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/7b8842c2d8e85d0c3a5831236bf5869af6ab2a11", + "reference": "7b8842c2d8e85d0c3a5831236bf5869af6ab2a11", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "suggest": { + "ext-posix": "*" + }, + "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": "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.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/environment", + "type": "tidelift" + } + ], + "time": "2026-03-15T07:05:40+00:00" + }, + { + "name": "sebastian/exporter", + "version": "7.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "016951ae10980765e4e7aee491eb288c64e505b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/016951ae10980765e4e7aee491eb288c64e505b7", + "reference": "016951ae10980765e4e7aee491eb288c64e505b7", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.3", + "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" + }, + { + "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.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/exporter", + "type": "tidelift" + } + ], + "time": "2025-09-24T06:16:11+00:00" + }, + { + "name": "sebastian/global-state", + "version": "8.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "ef1377171613d09edd25b7816f05be8313f9115d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/ef1377171613d09edd25b7816f05be8313f9115d", + "reference": "ef1377171613d09edd25b7816f05be8313f9115d", + "shasum": "" + }, + "require": { + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" + }, + "require-dev": { + "ext-dom": "*", + "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" + } + ], + "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.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/global-state", + "type": "tidelift" + } + ], + "time": "2025-08-29T11:29:25+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/97ffee3bcfb5805568d6af7f0f893678fc076d2f", + "reference": "97ffee3bcfb5805568d6af7f0f893678fc076d2f", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "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.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:57:28+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.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/e549163b9760b8f71f191651d22acf32d56d6d4d", + "reference": "e549163b9760b8f71f191651d22acf32d56d6d4d", + "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": "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.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/type", + "type": "tidelift" + } + ], + "time": "2025-08-09T06:57:12+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": "symfony/yaml", + "version": "v7.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "58751048de17bae71c5aa0d13cb19d79bca26391" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/58751048de17bae71c5aa0d13cb19d79bca26391", + "reference": "58751048de17bae71c5aa0d13cb19d79bca26391", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0|^8.0" + }, + "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/v7.4.6" + }, + "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-02-09T09:33:46+00:00" + }, + { + "name": "ta-tikoma/phpunit-architecture-test", + "version": "0.8.7", + "source": { + "type": "git", + "url": "https://github.com/ta-tikoma/phpunit-architecture-test.git", + "reference": "1248f3f506ca9641d4f68cebcd538fa489754db8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ta-tikoma/phpunit-architecture-test/zipball/1248f3f506ca9641d4f68cebcd538fa489754db8", + "reference": "1248f3f506ca9641d4f68cebcd538fa489754db8", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18.0 || ^5.0.0", + "php": "^8.1.0", + "phpdocumentor/reflection-docblock": "^5.3.0 || ^6.0.0", + "phpunit/phpunit": "^10.5.5 || ^11.0.0 || ^12.0.0 || ^13.0.0", + "symfony/finder": "^6.4.0 || ^7.0.0 || ^8.0.0" + }, + "require-dev": { + "laravel/pint": "^1.13.7", + "phpstan/phpstan": "^1.10.52" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPUnit\\Architecture\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ni Shi", + "email": "futik0ma011@gmail.com" + }, + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Methods for testing application architecture", + "keywords": [ + "architecture", + "phpunit", + "stucture", + "test", + "testing" + ], + "support": { + "issues": "https://github.com/ta-tikoma/phpunit-architecture-test/issues", + "source": "https://github.com/ta-tikoma/phpunit-architecture-test/tree/0.8.7" + }, + "time": "2026-02-17T17:25:14+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" + }, + { + "name": "webmozart/assert", + "version": "2.1.6", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "ff31ad6efc62e66e518fbab1cde3453d389bcdc8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/ff31ad6efc62e66e518fbab1cde3453d389bcdc8", + "reference": "ff31ad6efc62e66e518fbab1cde3453d389bcdc8", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^8.2" + }, + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-feature/2-0": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + }, + { + "name": "Woody Gilk", + "email": "woody.gilk@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/2.1.6" + }, + "time": "2026-02-27T10:28:38+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": "^8.2" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/config/app.php b/config/app.php new file mode 100644 index 0000000..5def0e1 --- /dev/null +++ b/config/app.php @@ -0,0 +1,137 @@ + 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'), + + /* + |-------------------------------------------------------------------------- + | API Base URL + |-------------------------------------------------------------------------- + | + | This URL is used by the frontend to communicate with the external API server. + | + */ + + 'api_url' => env('API_BASE_URL', '/api'), + + /* + |-------------------------------------------------------------------------- + | 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..d0cbe2e --- /dev/null +++ b/config/auth.php @@ -0,0 +1,110 @@ + [ + 'guard' => env('AUTH_GUARD', 'web'), + 'passwords' => env('AUTH_PASSWORD_BROKER', 'users'), + ], + + /* + |-------------------------------------------------------------------------- + | Authentication Guards + |-------------------------------------------------------------------------- + | + | Next, you may define every authentication guard for your application. + | Of course, a great default configuration has been defined for you + | which utilizes session storage plus the Eloquent user provider. + | + | All authentication guards have a user provider, which defines how the + | users are actually retrieved out of your database or other storage + | system used by the application. Typically, Eloquent is utilized. + | + | Supported: "session" + | + */ + + 'guards' => [ + 'web' => [ + 'driver' => 'session', + 'provider' => 'users', + ], + ], + + /* + |-------------------------------------------------------------------------- + | User Providers + |-------------------------------------------------------------------------- + | + | All authentication guards have a user provider, which defines how the + | users are actually retrieved out of your database or other storage + | system used by the application. Typically, Eloquent is utilized. + | + | If you have multiple user tables or models you may configure multiple + | providers to represent the model / table. These providers may then + | be assigned to any extra authentication guards you have defined. + | + | Supported: "database", "eloquent" + | + */ + + 'providers' => [ + 'users' => [ + 'driver' => 'encrypted', // Custom provider for encrypted fields + 'model' => env('AUTH_MODEL', App\Models\User::class), + ], + ], + + /* + |-------------------------------------------------------------------------- + | 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..b32aead --- /dev/null +++ b/config/cache.php @@ -0,0 +1,117 @@ + env('CACHE_STORE', 'database'), + + /* + |-------------------------------------------------------------------------- + | Cache Stores + |-------------------------------------------------------------------------- + | + | Here you may define all of the cache "stores" for your application as + | well as their drivers. You may even define multiple stores for the + | same cache driver to group types of items stored in your caches. + | + | Supported drivers: "array", "database", "file", "memcached", + | "redis", "dynamodb", "octane", + | "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'), + ], + + '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-'), + +]; diff --git a/config/database.php b/config/database.php new file mode 100644 index 0000000..df933e7 --- /dev/null +++ b/config/database.php @@ -0,0 +1,183 @@ + 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([ + (PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + 'mariadb' => [ + 'driver' => 'mariadb', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '3306'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'unix_socket' => env('DB_SOCKET', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + (PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + + 'pgsql' => [ + 'driver' => 'pgsql', + 'url' => env('DB_URL'), + 'host' => env('DB_HOST', '127.0.0.1'), + 'port' => env('DB_PORT', '5432'), + 'database' => env('DB_DATABASE', 'laravel'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => env('DB_CHARSET', 'utf8'), + 'prefix' => '', + 'prefix_indexes' => true, + 'search_path' => 'public', + 'sslmode' => 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/fortify.php b/config/fortify.php new file mode 100644 index 0000000..bc33c69 --- /dev/null +++ b/config/fortify.php @@ -0,0 +1,157 @@ + 'web', + + /* + |-------------------------------------------------------------------------- + | Fortify Password Broker + |-------------------------------------------------------------------------- + | + | Here you may specify which password broker Fortify can use when a user + | is resetting their password. This configured value should match one + | of your password brokers setup in your "auth" configuration file. + | + */ + + 'passwords' => 'users', + + /* + |-------------------------------------------------------------------------- + | Username / Email + |-------------------------------------------------------------------------- + | + | This value defines which model attribute should be considered as your + | application's "username" field. Typically, this might be the email + | address of the users but you are free to change this value here. + | + | Out of the box, Fortify expects forgot password and reset password + | requests to have a field named 'email'. If the application uses + | another name for the field you may define it below as needed. + | + */ + + 'username' => 'email', // Use email for Fortify username to satisfy tests; login field still supported in authenticateUsing() + + 'email' => 'email', + + /* + |-------------------------------------------------------------------------- + | Lowercase Usernames + |-------------------------------------------------------------------------- + | + | This value defines whether usernames should be lowercased before saving + | them in the database, as some database system string fields are case + | sensitive. You may disable this for your application if necessary. + | + */ + + 'lowercase_usernames' => true, + + /* + |-------------------------------------------------------------------------- + | Home Path + |-------------------------------------------------------------------------- + | + | Here you may configure the path where users will get redirected during + | authentication or password reset when the operations are successful + | and the user is authenticated. You are free to change this value. + | + */ + + 'home' => '/dashboard', + + /* + |-------------------------------------------------------------------------- + | Fortify Routes Prefix / Subdomain + |-------------------------------------------------------------------------- + | + | Here you may specify which prefix Fortify will assign to all the routes + | that it registers with the application. If necessary, you may change + | subdomain under which all of the Fortify routes will be available. + | + */ + + 'prefix' => '', + + 'domain' => null, + + /* + |-------------------------------------------------------------------------- + | Fortify Routes Middleware + |-------------------------------------------------------------------------- + | + | Here you may specify which middleware Fortify will assign to the routes + | that it registers with the application. If necessary, you may change + | these middleware but typically this provided default is preferred. + | + */ + + 'middleware' => ['web'], + + /* + |-------------------------------------------------------------------------- + | Rate Limiting + |-------------------------------------------------------------------------- + | + | By default, Fortify will throttle logins to five requests per minute for + | every email and IP address combination. However, if you would like to + | specify a custom rate limiter to call then you may specify it here. + | + */ + + 'limiters' => [ + 'login' => 'login', + 'two-factor' => 'two-factor', + ], + + /* + |-------------------------------------------------------------------------- + | Register View Routes + |-------------------------------------------------------------------------- + | + | Here you may specify if the routes returning views should be disabled as + | you may not need them when building your own application. This may be + | especially true if you're writing a custom single-page application. + | + */ + + 'views' => true, + + /* + |-------------------------------------------------------------------------- + | Features + |-------------------------------------------------------------------------- + | + | Some of the Fortify features are optional. You may disable the features + | by removing them from this array. You're free to only remove some of + | these features, or you can even remove all of these if you need to. + | + */ + + 'features' => [ + Features::registration(), + Features::resetPasswords(), + Features::emailVerification(), + Features::twoFactorAuthentication([ + 'confirm' => true, + 'confirmPassword' => true, + // 'window' => 0 + ]), + ], + +]; diff --git a/config/games.php b/config/games.php new file mode 100644 index 0000000..01515b8 --- /dev/null +++ b/config/games.php @@ -0,0 +1,70 @@ + [ + 'local' => [ + // Base URL for local HTML games provider. Example: + // http://localhost:3001/games + 'base_url' => env('LOCAL_PROVIDER_BASE_URL', 'http://localhost:3001/games'), + ], + ], + + /* + |-------------------------------------------------------------------------- + | Game Base URL (Operator / B2B) + |-------------------------------------------------------------------------- + | + | Public-facing base URL used for generating launch_url and thumbnail_url + | in the operator API. In production: https://originals.betix.io + | + */ + 'game_base_url' => env('GAME_BASE_URL', env('APP_URL', 'http://localhost')), + + /* + |-------------------------------------------------------------------------- + | Game Catalog + |-------------------------------------------------------------------------- + | + | Static list of available games. thumbnail_url and launch_path are + | appended dynamically in OperatorController using game_base_url. + | + */ + 'catalog' => [ + [ + 'id' => 'c3a1f2b4-0001-4000-8000-000000000001', + 'name' => 'Dice', + 'slug' => 'dice', + 'rtp' => 97.0, + 'volatility' => 'low', + 'min_bet' => 0.01, + 'max_bet' => 1000.00, + ], + [ + 'id' => 'd4b2c3a5-0002-4000-8000-000000000002', + 'name' => 'Crash', + 'slug' => 'crash', + 'rtp' => 97.0, + 'volatility' => 'high', + 'min_bet' => 0.01, + 'max_bet' => 500.00, + ], + [ + 'id' => 'e5c3d4b6-0003-4000-8000-000000000003', + 'name' => 'Mines', + 'slug' => 'mines', + 'rtp' => 97.0, + 'volatility' => 'medium', + 'min_bet' => 0.01, + 'max_bet' => 250.00, + ], + [ + 'id' => 'f6d4e5c7-0004-4000-8000-000000000004', + 'name' => 'Plinko', + 'slug' => 'plinko', + 'rtp' => 97.0, + 'volatility' => 'medium', + 'min_bet' => 0.01, + 'max_bet' => 500.00, + ], + ], +]; diff --git a/config/inertia.php b/config/inertia.php new file mode 100644 index 0000000..f75e7b8 --- /dev/null +++ b/config/inertia.php @@ -0,0 +1,55 @@ + [ + 'enabled' => true, + 'url' => 'http://127.0.0.1:13714', + // 'bundle' => base_path('bootstrap/ssr/ssr.mjs'), + + ], + + /* + |-------------------------------------------------------------------------- + | Testing + |-------------------------------------------------------------------------- + | + | The values described here are used to locate Inertia components on the + | filesystem. For instance, when using `assertInertia`, the assertion + | attempts to locate the component as a file relative to the paths. + | + */ + + 'testing' => [ + + 'ensure_pages_exist' => true, + + 'page_paths' => [ + resource_path('js/pages'), + ], + + 'page_extensions' => [ + 'js', + 'jsx', + 'svelte', + 'ts', + 'tsx', + 'vue', + ], + + ], + +]; diff --git a/config/logging.php b/config/logging.php new file mode 100644 index 0000000..9e998a4 --- /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', 'Laravel Log'), + 'emoji' => env('LOG_SLACK_EMOJI', ':boom:'), + 'level' => env('LOG_LEVEL', 'critical'), + 'replace_placeholders' => true, + ], + + 'papertrail' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class), + 'handler_with' => [ + 'host' => env('PAPERTRAIL_URL'), + 'port' => env('PAPERTRAIL_PORT'), + 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'), + ], + 'processors' => [PsrLogMessageProcessor::class], + ], + + 'stderr' => [ + 'driver' => 'monolog', + 'level' => env('LOG_LEVEL', 'debug'), + 'handler' => StreamHandler::class, + '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..522b284 --- /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', 'Example'), + ], + +]; 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/sanctum.php b/config/sanctum.php new file mode 100644 index 0000000..44527d6 --- /dev/null +++ b/config/sanctum.php @@ -0,0 +1,84 @@ + explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( + '%s%s', + 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', + Sanctum::currentApplicationUrlWithPort(), + // Sanctum::currentRequestHost(), + ))), + + /* + |-------------------------------------------------------------------------- + | Sanctum Guards + |-------------------------------------------------------------------------- + | + | This array contains the authentication guards that will be checked when + | Sanctum is trying to authenticate a request. If none of these guards + | are able to authenticate the request, Sanctum will use the bearer + | token that's present on an incoming request for authentication. + | + */ + + 'guard' => ['web'], + + /* + |-------------------------------------------------------------------------- + | Expiration Minutes + |-------------------------------------------------------------------------- + | + | This value controls the number of minutes until an issued token will be + | considered expired. This will override any values set in the token's + | "expires_at" attribute, but first-party sessions are not affected. + | + */ + + 'expiration' => null, + + /* + |-------------------------------------------------------------------------- + | Token Prefix + |-------------------------------------------------------------------------- + | + | Sanctum can prefix new tokens in order to take advantage of numerous + | security scanning initiatives maintained by open source platforms + | that notify developers if they commit tokens into repositories. + | + | See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning + | + */ + + 'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''), + + /* + |-------------------------------------------------------------------------- + | Sanctum Middleware + |-------------------------------------------------------------------------- + | + | When authenticating your first-party SPA with Sanctum you may need to + | customize some of the middleware Sanctum uses while processing the + | request. You may change the middleware listed below as required. + | + */ + + 'middleware' => [ + 'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class, + 'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class, + 'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, + ], + +]; diff --git a/config/services.php b/config/services.php new file mode 100644 index 0000000..c1109d1 --- /dev/null +++ b/config/services.php @@ -0,0 +1,76 @@ + [ + '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'), + ], + ], + + 'bonus_api' => [ + 'token' => env('BONUS_API_TOKEN'), + ], + + 'moderation_api' => [ + 'token' => env('MODERATION_API_TOKEN'), + ], + + 'vault_api' => [ + 'token' => env('VAULT_API_TOKEN'), + ], + + // External backend API that this app will proxy to + 'backend' => [ + 'base' => env('BACKEND_API_BASE', 'http://casinoapi.test'), + 'token' => env('BACKEND_API_TOKEN', ''), + 'timeout' => (float) env('BACKEND_API_TIMEOUT', 8.0), + ], + + // BetiX Originals API + 'betix' => [ + 'key' => env('BETIX_API_KEY', ''), + 'url' => env('BETIX_API_URL', 'https://originals.betix.io'), + 'webhook_secret' => env('BETIX_WEBHOOK_SECRET', ''), + 'timeout' => (float) env('BETIX_TIMEOUT', 10.0), + ], + + // NOWPayments configuration (deposits/payouts) + 'nowpayments' => [ + // Base URL can differ for sandbox vs. live; allow override via env + 'base_url' => env('NOWPAYMENTS_BASE_URL', 'https://api.nowpayments.io/v1'), + 'api_key' => env('NOWPAYMENTS_API_KEY', ''), + 'public_key' => env('NOWPAYMENTS_PUBLIC_KEY', ''), + 'ipn_secret' => env('NOWPAYMENTS_IPN_SECRET', ''), + 'mode' => env('NOWPAYMENTS_ENV', 'sandbox'), // sandbox|live (can be overridden by admin settings) + 'timeout' => (float) env('NOWPAYMENTS_TIMEOUT', 10.0), + ], + +]; diff --git a/config/session.php b/config/session.php new file mode 100644 index 0000000..5b541b7 --- /dev/null +++ b/config/session.php @@ -0,0 +1,217 @@ + 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), + +]; 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..08c50e0 --- /dev/null +++ b/database/factories/UserFactory.php @@ -0,0 +1,60 @@ + + */ +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(), + 'username' => fake()->unique()->userName(), + 'email' => fake()->unique()->safeEmail(), + 'email_verified_at' => now(), + 'password' => static::$password ??= Hash::make('password'), + 'remember_token' => Str::random(10), + 'two_factor_secret' => null, + 'two_factor_recovery_codes' => null, + 'two_factor_confirmed_at' => null, + ]; + } + + /** + * Indicate that the model's email address should be unverified. + */ + public function unverified(): static + { + return $this->state(fn (array $attributes) => [ + 'email_verified_at' => null, + ]); + } + + /** + * Indicate that the model has two-factor authentication configured. + */ + public function withTwoFactor(): static + { + return $this->state(fn (array $attributes) => [ + 'two_factor_secret' => encrypt('secret'), + 'two_factor_recovery_codes' => encrypt(json_encode(['recovery-code-1'])), + 'two_factor_confirmed_at' => now(), + ]); + } +} 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..ed758bd --- /dev/null +++ b/database/migrations/0001_01_01_000001_create_cache_table.php @@ -0,0 +1,35 @@ +string('key')->primary(); + $table->mediumText('value'); + $table->integer('expiration')->index(); + }); + + Schema::create('cache_locks', function (Blueprint $table) { + $table->string('key')->primary(); + $table->string('owner'); + $table->integer('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..425e705 --- /dev/null +++ b/database/migrations/0001_01_01_000002_create_jobs_table.php @@ -0,0 +1,57 @@ +id(); + $table->string('queue')->index(); + $table->longText('payload'); + $table->unsignedTinyInteger('attempts'); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + }); + + Schema::create('job_batches', function (Blueprint $table) { + $table->string('id')->primary(); + $table->string('name'); + $table->integer('total_jobs'); + $table->integer('pending_jobs'); + $table->integer('failed_jobs'); + $table->longText('failed_job_ids'); + $table->mediumText('options')->nullable(); + $table->integer('cancelled_at')->nullable(); + $table->integer('created_at'); + $table->integer('finished_at')->nullable(); + }); + + Schema::create('failed_jobs', function (Blueprint $table) { + $table->id(); + $table->string('uuid')->unique(); + $table->text('connection'); + $table->text('queue'); + $table->longText('payload'); + $table->longText('exception'); + $table->timestamp('failed_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('jobs'); + Schema::dropIfExists('job_batches'); + Schema::dropIfExists('failed_jobs'); + } +}; diff --git a/database/migrations/2026_01_01_000001_create_users_table.php b/database/migrations/2026_01_01_000001_create_users_table.php new file mode 100644 index 0000000..21ce855 --- /dev/null +++ b/database/migrations/2026_01_01_000001_create_users_table.php @@ -0,0 +1,112 @@ +id(); + + // --- Public Identity --- + $table->string('username')->unique(); + $table->string('username_index')->unique(); // Blind index for case-insensitive lookups + $table->string('name'); + + // --- Private Identity (Encrypted) --- + $table->text('email'); // Encrypted via SafeEncryptedString cast + $table->string('email_index')->unique(); // Blind index for login lookups + $table->timestamp('email_verified_at')->nullable(); + $table->string('password'); + + // --- Role & Status --- + $table->string('role')->default('user'); // admin | mod | streamer | user + $table->string('clan_tag')->nullable(); + $table->string('avatar_url')->nullable(); // OAuth / external avatar + $table->string('avatar')->nullable(); // Uploaded avatar + $table->string('banner')->nullable(); // Uploaded banner + $table->text('bio')->nullable(); + $table->boolean('is_public')->default(false); + $table->boolean('is_adult')->default(false); + $table->boolean('is_banned')->default(false); + + // --- VIP & Balance --- + $table->integer('vip_level')->default(0); + $table->string('preferred_locale', 8)->nullable(); + $table->decimal('balance', 16, 4)->default(0); // BTX main balance + $table->string('currency', 3)->default('EUR'); + + // --- Vault --- + $table->decimal('vault_balance', 16, 4)->default(0); // BTX vault balance (plaintext decimal) + $table->text('vault_balances')->nullable(); // JSON map for multi-currency vault + $table->string('vault_pin_hash')->nullable(); + $table->timestamp('vault_pin_set_at')->nullable(); + $table->unsignedSmallInteger('vault_pin_attempts')->default(0); + $table->timestamp('vault_pin_locked_until')->nullable(); + $table->timestamp('withdraw_cooldown_until')->nullable(); + + // --- Personal Details (Encrypted PII) --- + $table->text('first_name')->nullable(); + $table->text('last_name')->nullable(); + $table->text('birthdate')->nullable(); + $table->text('gender')->nullable(); + $table->text('phone')->nullable(); + + // --- Address (Encrypted PII) --- + $table->text('country')->nullable(); + $table->text('address_line1')->nullable(); + $table->text('address_line2')->nullable(); + $table->text('city')->nullable(); + $table->text('state')->nullable(); + $table->text('postal_code')->nullable(); + + // --- 2FA --- + $table->text('two_factor_secret')->nullable(); + $table->text('two_factor_recovery_codes')->nullable(); + $table->timestamp('two_factor_confirmed_at')->nullable(); + + // --- Security & Tracking --- + $table->timestamp('last_login_at')->nullable(); + $table->text('last_login_ip')->nullable(); + $table->text('last_login_user_agent')->nullable(); + $table->string('registration_ip', 45)->nullable(); + + $table->rememberToken(); + $table->timestamps(); + }); + + // --------------------------------------------------------------- + // PASSWORD RESET TOKENS + // --------------------------------------------------------------- + Schema::create('password_reset_tokens', function (Blueprint $table) { + $table->string('email')->primary(); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + }); + + // --------------------------------------------------------------- + // SESSIONS + // --------------------------------------------------------------- + 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(); + }); + } + + public function down(): void + { + Schema::dropIfExists('sessions'); + Schema::dropIfExists('password_reset_tokens'); + Schema::dropIfExists('users'); + } +}; diff --git a/database/migrations/2026_01_01_000002_create_user_stats_table.php b/database/migrations/2026_01_01_000002_create_user_stats_table.php new file mode 100644 index 0000000..90df7d7 --- /dev/null +++ b/database/migrations/2026_01_01_000002_create_user_stats_table.php @@ -0,0 +1,39 @@ +id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + + $table->unsignedBigInteger('total_logins')->default(0); + + // Stored as TEXT to support Laravel encryption cast + $table->text('total_wagered')->nullable(); + $table->text('total_won')->nullable(); + $table->text('total_lost')->nullable(); + $table->text('biggest_win')->nullable(); + $table->string('biggest_win_game')->nullable(); + + $table->unsignedInteger('total_wins')->default(0); + $table->unsignedInteger('vip_level')->default(1); + $table->unsignedBigInteger('vip_points')->default(0); + $table->timestamp('last_activity')->nullable(); + + $table->timestamps(); + + $table->index('user_id'); + }); + } + + public function down(): void + { + Schema::dropIfExists('user_stats'); + } +}; diff --git a/database/migrations/2026_01_01_000003_create_social_tables.php b/database/migrations/2026_01_01_000003_create_social_tables.php new file mode 100644 index 0000000..42436a0 --- /dev/null +++ b/database/migrations/2026_01_01_000003_create_social_tables.php @@ -0,0 +1,78 @@ +id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->foreignId('friend_id')->constrained('users')->cascadeOnDelete(); + $table->enum('status', ['pending', 'accepted', 'blocked'])->default('pending'); + $table->timestamps(); + + $table->unique(['user_id', 'friend_id']); + $table->index(['friend_id', 'status']); + }); + + // --------------------------------------------------------------- + // PROFILE LIKES + // --------------------------------------------------------------- + Schema::create('profile_likes', function (Blueprint $table) { + $table->id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); // The liker + $table->foreignId('profile_id')->constrained('users')->cascadeOnDelete(); // The profile owner + $table->timestamps(); + + $table->unique(['user_id', 'profile_id']); + $table->index('profile_id'); + }); + + // --------------------------------------------------------------- + // PROFILE COMMENTS + // --------------------------------------------------------------- + Schema::create('profile_comments', function (Blueprint $table) { + $table->id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); // The commenter + $table->foreignId('profile_id')->constrained('users')->cascadeOnDelete(); // The profile owner + $table->text('content'); + $table->timestamps(); + + $table->index(['profile_id', 'created_at']); + }); + + // --------------------------------------------------------------- + // PROFILE REPORTS + // --------------------------------------------------------------- + Schema::create('profile_reports', function (Blueprint $table) { + $table->id(); + $table->foreignId('reporter_id')->constrained('users')->cascadeOnDelete(); + $table->foreignId('profile_id')->constrained('users')->cascadeOnDelete(); + $table->string('reason'); + $table->text('details')->nullable(); + $table->json('snapshot')->nullable(); + $table->string('screenshot_path', 500)->nullable(); + $table->enum('status', ['pending', 'reviewed', 'dismissed'])->default('pending'); + $table->text('admin_note')->nullable(); + $table->timestamps(); + + $table->index(['profile_id', 'status']); + $table->index('reporter_id'); + }); + } + + public function down(): void + { + Schema::dropIfExists('profile_reports'); + Schema::dropIfExists('profile_comments'); + Schema::dropIfExists('profile_likes'); + Schema::dropIfExists('friends'); + } +}; diff --git a/database/migrations/2026_01_01_000004_create_chat_tables.php b/database/migrations/2026_01_01_000004_create_chat_tables.php new file mode 100644 index 0000000..08ea496 --- /dev/null +++ b/database/migrations/2026_01_01_000004_create_chat_tables.php @@ -0,0 +1,69 @@ +id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->foreignId('reply_to_id')->nullable()->constrained('chat_messages')->nullOnDelete(); + $table->text('message'); // Stored encrypted + $table->boolean('is_deleted')->default(false); + $table->unsignedBigInteger('deleted_by')->nullable(); // user_id of moderator/admin + $table->timestamps(); + + $table->index(['created_at']); + $table->index('reply_to_id'); + $table->index(['user_id', 'created_at']); + }); + + // --------------------------------------------------------------- + // CHAT MESSAGE REACTIONS + // --------------------------------------------------------------- + Schema::create('chat_message_reactions', function (Blueprint $table) { + $table->id(); + $table->foreignId('message_id')->constrained('chat_messages')->cascadeOnDelete(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->string('emoji', 16); + $table->timestamps(); + + $table->unique(['message_id', 'user_id', 'emoji'], 'uniq_reaction'); + $table->index(['message_id', 'user_id']); + }); + + // --------------------------------------------------------------- + // CHAT MESSAGE REPORTS + // --------------------------------------------------------------- + Schema::create('chat_message_reports', function (Blueprint $table) { + $table->id(); + $table->foreignId('reporter_id')->constrained('users')->cascadeOnDelete(); + $table->string('message_id'); + $table->text('message_text'); + $table->unsignedBigInteger('sender_id')->nullable(); + $table->string('sender_username')->nullable(); + $table->string('reason')->nullable(); + $table->json('context_messages')->nullable(); // Previous messages for context + $table->enum('status', ['pending', 'reviewed', 'dismissed'])->default('pending'); + $table->text('admin_note')->nullable(); + $table->timestamps(); + + $table->index('reporter_id'); + $table->index('status'); + }); + } + + public function down(): void + { + Schema::dropIfExists('chat_message_reports'); + Schema::dropIfExists('chat_message_reactions'); + Schema::dropIfExists('chat_messages'); + } +}; diff --git a/database/migrations/2026_01_01_000005_create_guild_tables.php b/database/migrations/2026_01_01_000005_create_guild_tables.php new file mode 100644 index 0000000..2918701 --- /dev/null +++ b/database/migrations/2026_01_01_000005_create_guild_tables.php @@ -0,0 +1,51 @@ +id(); + $table->foreignId('owner_id')->constrained('users')->cascadeOnDelete(); + $table->string('name', 64)->unique(); + $table->string('tag', 6)->unique(); + $table->string('logo_url')->nullable(); + $table->string('invite_code', 16)->unique(); + $table->string('description', 500)->nullable(); + $table->unsignedInteger('points')->default(0); + $table->unsignedInteger('members_count')->default(0); + $table->timestamps(); + + $table->index(['owner_id']); + $table->index(['points', 'members_count']); + }); + + // --------------------------------------------------------------- + // GUILD MEMBERS + // --------------------------------------------------------------- + Schema::create('guild_members', function (Blueprint $table) { + $table->id(); + $table->foreignId('guild_id')->constrained('guilds')->cascadeOnDelete(); + $table->foreignId('user_id')->constrained('users')->cascadeOnDelete(); + $table->string('role', 16)->default('member'); // owner | officer | member + $table->timestamp('joined_at')->nullable(); + $table->timestamps(); + + $table->unique(['guild_id', 'user_id']); + $table->index('user_id'); + }); + } + + public function down(): void + { + Schema::dropIfExists('guild_members'); + Schema::dropIfExists('guilds'); + } +}; diff --git a/database/migrations/2026_01_01_000006_create_kyc_table.php b/database/migrations/2026_01_01_000006_create_kyc_table.php new file mode 100644 index 0000000..e5f1e13 --- /dev/null +++ b/database/migrations/2026_01_01_000006_create_kyc_table.php @@ -0,0 +1,36 @@ +id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->string('category', 24); // identity | address | payment + $table->string('type', 32); // passport | driver_license | id_card | bank_statement | utility_bill | other + $table->string('status', 16)->default('pending'); // pending | approved | rejected + $table->string('rejection_reason', 255)->nullable(); + $table->string('file_path'); + $table->string('mime', 100); + $table->unsignedBigInteger('size'); + $table->timestamp('submitted_at')->nullable(); + $table->timestamp('reviewed_at')->nullable(); + $table->foreignId('reviewed_by')->nullable()->constrained('users')->nullOnDelete(); + $table->softDeletes(); + $table->timestamps(); + + $table->index(['user_id', 'status']); + $table->index('category'); + }); + } + + public function down(): void + { + Schema::dropIfExists('kyc_documents'); + } +}; diff --git a/database/migrations/2026_01_01_000007_create_wallet_tables.php b/database/migrations/2026_01_01_000007_create_wallet_tables.php new file mode 100644 index 0000000..3bdd495 --- /dev/null +++ b/database/migrations/2026_01_01_000007_create_wallet_tables.php @@ -0,0 +1,122 @@ +id(); + $table->morphs('tokenable'); + $table->text('name'); + $table->string('token', 64)->unique(); + $table->text('abilities')->nullable(); + $table->timestamp('last_used_at')->nullable(); + $table->timestamp('expires_at')->nullable()->index(); + $table->timestamps(); + }); + + // --------------------------------------------------------------- + // WALLETS (per-currency balance per user) + // --------------------------------------------------------------- + Schema::create('wallets', function (Blueprint $table) { + $table->id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->string('currency', 10); // BTC | ETH | SOL | BTX | EUR + $table->decimal('balance', 20, 8)->default(0); + $table->string('deposit_address')->nullable(); + $table->timestamps(); + + $table->unique(['user_id', 'currency']); + $table->index(['user_id', 'currency']); + }); + + // --------------------------------------------------------------- + // WALLET TRANSFERS (main balance <-> vault, audit log) + // --------------------------------------------------------------- + Schema::create('wallet_transfers', function (Blueprint $table) { + $table->id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->enum('type', ['deposit', 'withdraw']); // deposit = main->vault, withdraw = vault->main + $table->decimal('amount', 20, 4); + $table->decimal('balance_before', 20, 4); + $table->decimal('balance_after', 20, 4); + $table->decimal('vault_before', 20, 4); + $table->decimal('vault_after', 20, 4); + $table->string('currency', 10)->default('BTX'); + $table->string('idempotency_key', 64)->nullable(); + $table->json('meta')->nullable(); + $table->timestamps(); + + $table->index(['user_id', 'created_at']); + $table->index(['user_id', 'type']); + $table->unique(['user_id', 'idempotency_key']); + }); + + // --------------------------------------------------------------- + // VAULT TRANSFERS (encrypted audit trail) + // --------------------------------------------------------------- + Schema::create('vault_transfers', function (Blueprint $table) { + $table->id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->string('direction', 16); // to_vault | from_vault + $table->text('amount'); // Encrypted + $table->text('main_balance_before'); // Encrypted + $table->text('main_balance_after'); // Encrypted + $table->text('vault_balance_before'); // Encrypted + $table->text('vault_balance_after'); // Encrypted + $table->string('idempotency_key', 64)->nullable(); + $table->string('source', 16)->default('web'); // web | api + $table->unsignedBigInteger('created_by')->nullable(); + $table->text('metadata')->nullable(); // Encrypted + $table->timestamps(); + + $table->index(['user_id', 'created_at']); + $table->index(['user_id', 'direction']); + $table->unique(['user_id', 'idempotency_key']); + }); + + // --------------------------------------------------------------- + // CRYPTO PAYMENTS (NOWPayments / on-chain deposits) + // --------------------------------------------------------------- + Schema::create('crypto_payments', function (Blueprint $table) { + $table->id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->uuid('order_id')->unique(); + $table->string('invoice_id')->unique(); + $table->string('payment_id')->unique(); + $table->string('pay_currency', 20); + $table->decimal('pay_amount', 36, 18)->nullable(); + $table->decimal('actually_paid', 36, 18)->nullable(); + $table->string('pay_address')->nullable(); + $table->decimal('price_amount', 20, 8)->nullable(); + $table->string('price_currency', 10)->default('USD'); + $table->decimal('exchange_rate_at_payment', 28, 12)->nullable(); + $table->string('status', 40)->index(); // waiting | confirming | finished | failed | expired | partially_paid + $table->unsignedInteger('confirmations')->nullable(); + $table->json('tx_hash')->nullable(); + $table->decimal('fee', 36, 18)->nullable(); + $table->json('raw_payload')->nullable(); + $table->decimal('credited_btx', 20, 8)->nullable(); + $table->timestamp('credited_at')->nullable(); + $table->timestamps(); + + $table->index(['user_id', 'status']); + }); + } + + public function down(): void + { + Schema::dropIfExists('crypto_payments'); + Schema::dropIfExists('vault_transfers'); + Schema::dropIfExists('wallet_transfers'); + Schema::dropIfExists('wallets'); + Schema::dropIfExists('personal_access_tokens'); + } +}; diff --git a/database/migrations/2026_01_01_000008_create_bonus_tables.php b/database/migrations/2026_01_01_000008_create_bonus_tables.php new file mode 100644 index 0000000..fba2d95 --- /dev/null +++ b/database/migrations/2026_01_01_000008_create_bonus_tables.php @@ -0,0 +1,129 @@ +id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->integer('level'); + $table->decimal('amount', 10, 2); + $table->timestamp('claimed_at'); + $table->timestamps(); + + $table->unique(['user_id', 'level']); // Prevent double-claiming + }); + + // --------------------------------------------------------------- + // BONUSES (admin-created bonus campaigns) + // --------------------------------------------------------------- + Schema::create('bonuses', function (Blueprint $table) { + $table->id(); + $table->string('title'); + $table->string('type')->nullable(); // welcome | reload | spins + $table->decimal('amount_value', 18, 8)->nullable(); + $table->string('amount_unit', 32)->nullable(); // USD | BTC | PERCENT | SPINS + $table->decimal('min_deposit', 18, 8)->nullable(); + $table->decimal('max_amount', 18, 8)->nullable(); + $table->string('currency', 16)->nullable(); + $table->string('code')->nullable(); + $table->string('status')->default('draft'); // draft | active | paused | expired + $table->timestamp('starts_at')->nullable(); + $table->timestamp('expires_at')->nullable(); + $table->json('rules')->nullable(); + $table->text('description')->nullable(); + $table->foreignId('created_by')->nullable()->constrained('users')->nullOnDelete(); + $table->timestamps(); + $table->softDeletes(); + + $table->index(['status', 'starts_at', 'expires_at']); + }); + + // --------------------------------------------------------------- + // PROMOS (promo code system) + // --------------------------------------------------------------- + Schema::create('promos', function (Blueprint $table) { + $table->id(); + $table->string('code')->unique(); // Uppercase normalized + $table->string('description')->nullable(); + $table->decimal('bonus_amount', 16, 4)->default(0); + $table->unsignedInteger('wager_multiplier')->default(0); + $table->unsignedInteger('per_user_limit')->default(1); + $table->unsignedInteger('global_limit')->nullable(); + $table->timestamp('starts_at')->nullable(); + $table->timestamp('ends_at')->nullable(); + $table->decimal('min_deposit', 16, 4)->nullable(); + $table->unsignedInteger('bonus_expires_days')->nullable(); + $table->boolean('is_active')->default(true); + $table->timestamps(); + + $table->index(['is_active', 'starts_at', 'ends_at']); + }); + + // --------------------------------------------------------------- + // PROMO USAGES (per-use audit) + // --------------------------------------------------------------- + Schema::create('promo_usages', function (Blueprint $table) { + $table->id(); + $table->foreignId('promo_id')->constrained('promos')->cascadeOnDelete(); + $table->foreignId('user_id')->constrained('users')->cascadeOnDelete(); + $table->timestamp('used_at'); + $table->string('ip', 45)->nullable(); + $table->text('user_agent')->nullable(); + $table->timestamps(); + + $table->unique(['promo_id', 'user_id', 'used_at']); + $table->index(['user_id', 'promo_id']); + }); + + // --------------------------------------------------------------- + // PROMO CLAIMS (anti-abuse: one entry per user per promo) + // --------------------------------------------------------------- + Schema::create('promo_claims', function (Blueprint $table) { + $table->id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->foreignId('promo_id')->constrained()->cascadeOnDelete(); + $table->string('ip_address')->nullable(); + $table->string('device_hash')->nullable(); + $table->timestamps(); + + $table->index(['user_id', 'promo_id']); + }); + + // --------------------------------------------------------------- + // USER BONUSES (active bonus progress per user) + // --------------------------------------------------------------- + Schema::create('user_bonuses', function (Blueprint $table) { + $table->id(); + $table->foreignId('user_id')->constrained('users')->cascadeOnDelete(); + $table->foreignId('promo_id')->nullable()->constrained('promos')->nullOnDelete(); + $table->decimal('amount', 16, 4); + $table->decimal('wager_required', 16, 4)->default(0); + $table->decimal('wager_progress', 16, 4)->default(0); + $table->timestamp('expires_at')->nullable(); + $table->boolean('is_active')->default(true); + $table->timestamp('completed_at')->nullable(); + $table->timestamps(); + + $table->index(['user_id', 'is_active']); + }); + } + + public function down(): void + { + Schema::dropIfExists('user_bonuses'); + Schema::dropIfExists('promo_claims'); + Schema::dropIfExists('promo_usages'); + Schema::dropIfExists('promos'); + Schema::dropIfExists('bonuses'); + Schema::dropIfExists('vip_rewards'); + } +}; diff --git a/database/migrations/2026_01_01_000009_create_game_tables.php b/database/migrations/2026_01_01_000009_create_game_tables.php new file mode 100644 index 0000000..e6008f7 --- /dev/null +++ b/database/migrations/2026_01_01_000009_create_game_tables.php @@ -0,0 +1,91 @@ +id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->string('game_name'); + $table->decimal('wager_amount', 16, 8)->default(0); + $table->decimal('payout_multiplier', 8, 4)->default(0); + $table->decimal('payout_amount', 16, 8)->default(0); + $table->string('currency', 32)->default('BTX'); + $table->timestamps(); + + $table->index(['user_id', 'created_at']); + }); + + // --------------------------------------------------------------- + // USER FAVORITES (saved / bookmarked games) + // --------------------------------------------------------------- + Schema::create('user_favorites', function (Blueprint $table) { + $table->id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->string('game_slug', 128); + $table->string('game_name', 255)->nullable(); + $table->string('game_image', 512)->nullable(); + $table->string('game_provider', 100)->nullable(); + $table->timestamps(); + + $table->unique(['user_id', 'game_slug']); + $table->index('user_id'); + }); + + // --------------------------------------------------------------- + // USER ACHIEVEMENTS + // --------------------------------------------------------------- + Schema::create('user_achievements', function (Blueprint $table) { + $table->id(); + $table->foreignId('user_id')->constrained()->cascadeOnDelete(); + $table->string('achievement_key', 64); // e.g. first_bet | big_winner + $table->timestamp('unlocked_at'); + $table->timestamps(); + + $table->unique(['user_id', 'achievement_key']); + $table->index('user_id'); + }); + + // --------------------------------------------------------------- + // USER FEEDBACK (UX ratings & comments) + // --------------------------------------------------------------- + Schema::create('user_feedback', function (Blueprint $table) { + $table->id(); + $table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete(); + $table->string('category', 30)->default('general'); // general | ux | mobile | feature | complaint + $table->unsignedTinyInteger('overall_rating')->nullable(); // 1-5 + $table->unsignedTinyInteger('ux_rating')->nullable(); // 1-5 + $table->unsignedTinyInteger('comfort_rating')->nullable(); // 1-5 + $table->unsignedTinyInteger('mobile_rating')->nullable(); // 1-5 + $table->boolean('uses_mobile')->nullable(); + $table->unsignedTinyInteger('nps_score')->nullable(); // 1-10 + $table->text('ux_comment')->nullable(); + $table->text('mobile_comment')->nullable(); + $table->text('feature_request')->nullable(); + $table->text('improvements')->nullable(); + $table->text('general_comment')->nullable(); + $table->enum('status', ['new', 'read'])->default('new'); + $table->text('admin_note')->nullable(); + $table->timestamps(); + + $table->index(['status', 'created_at']); + $table->index('user_id'); + }); + } + + public function down(): void + { + Schema::dropIfExists('user_feedback'); + Schema::dropIfExists('user_achievements'); + Schema::dropIfExists('user_favorites'); + Schema::dropIfExists('game_bets'); + } +}; diff --git a/database/migrations/2026_01_01_000010_create_admin_tables.php b/database/migrations/2026_01_01_000010_create_admin_tables.php new file mode 100644 index 0000000..d067a77 --- /dev/null +++ b/database/migrations/2026_01_01_000010_create_admin_tables.php @@ -0,0 +1,79 @@ +id(); + $table->string('key')->unique(); + $table->json('value'); + $table->timestamps(); + }); + + // --------------------------------------------------------------- + // USER RESTRICTIONS (bans, chat bans, etc.) + // --------------------------------------------------------------- + Schema::create('user_restrictions', function (Blueprint $table) { + $table->id(); + $table->foreignId('user_id')->constrained('users')->cascadeOnDelete(); + $table->string('type', 64); // account_ban | chat_ban | deposit_block | withdrawal_block | support_block + $table->string('reason', 255)->nullable(); + $table->text('notes')->nullable(); + $table->foreignId('imposed_by')->nullable()->constrained('users')->nullOnDelete(); + $table->timestamp('starts_at')->nullable(); + $table->timestamp('ends_at')->nullable(); + $table->boolean('active')->default(true); + $table->string('source', 64)->nullable(); // api | admin_panel | system + $table->json('metadata')->nullable(); + $table->timestamps(); + $table->softDeletes(); + + $table->index(['user_id', 'type', 'active']); + $table->index(['user_id', 'active']); + $table->index('ends_at'); + }); + + // --------------------------------------------------------------- + // TIPS (peer-to-peer balance transfers) + // --------------------------------------------------------------- + Schema::create('tips', function (Blueprint $table) { + $table->id(); + $table->foreignId('from_user_id')->constrained('users')->cascadeOnDelete(); + $table->foreignId('to_user_id')->constrained('users')->cascadeOnDelete(); + $table->string('currency', 10); + $table->decimal('amount', 20, 8); + $table->string('note', 140)->nullable(); + $table->timestamps(); + + $table->index(['from_user_id', 'to_user_id']); + }); + + // --------------------------------------------------------------- + // NOTIFICATIONS (Laravel default notification table) + // --------------------------------------------------------------- + Schema::create('notifications', function (Blueprint $table) { + $table->uuid('id')->primary(); + $table->string('type'); + $table->morphs('notifiable'); + $table->text('data'); + $table->timestamp('read_at')->nullable(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('notifications'); + Schema::dropIfExists('tips'); + Schema::dropIfExists('user_restrictions'); + Schema::dropIfExists('app_settings'); + } +}; diff --git a/database/migrations/2026_01_01_000011_create_operator_tables.php b/database/migrations/2026_01_01_000011_create_operator_tables.php new file mode 100644 index 0000000..45af118 --- /dev/null +++ b/database/migrations/2026_01_01_000011_create_operator_tables.php @@ -0,0 +1,56 @@ +id(); + $table->string('name'); + $table->string('license_key_hash', 64)->unique(); // SHA-256 of plaintext key (never store plaintext) + $table->string('status', 20)->default('active'); // active | inactive | suspended + $table->json('ip_whitelist')->nullable(); + $table->json('domain_whitelist')->nullable(); + $table->timestamps(); + + $table->index('status'); + }); + + // --------------------------------------------------------------- + // OPERATOR SESSIONS (provably-fair game sessions) + // --------------------------------------------------------------- + Schema::create('operator_sessions', function (Blueprint $table) { + $table->id(); + $table->uuid('session_token')->unique(); + $table->foreignId('operator_casino_id')->constrained()->cascadeOnDelete(); + $table->string('player_id', 255); // Casino's internal player ID + $table->string('game_slug', 50); + $table->string('currency', 10)->default('EUR'); + $table->decimal('start_balance', 20, 4)->default(0); + $table->decimal('current_balance', 20, 4)->default(0); + $table->text('server_seed'); // Encrypted via Laravel encrypt() + $table->string('server_seed_hash', 64); // SHA-256 shown to player for provably-fair + $table->string('client_seed', 255)->nullable(); + $table->string('status', 20)->default('active'); // active | expired | ended + $table->timestamp('expires_at'); + $table->timestamps(); + + $table->index(['operator_casino_id', 'player_id']); + $table->index('status'); + $table->index('expires_at'); + }); + } + + public function down(): void + { + Schema::dropIfExists('operator_sessions'); + Schema::dropIfExists('operator_casinos'); + } +}; diff --git a/database/migrations/2026_01_01_000012_create_direct_messages_table.php b/database/migrations/2026_01_01_000012_create_direct_messages_table.php new file mode 100644 index 0000000..694d836 --- /dev/null +++ b/database/migrations/2026_01_01_000012_create_direct_messages_table.php @@ -0,0 +1,45 @@ +id(); + $table->foreignId('sender_id')->constrained('users')->cascadeOnDelete(); + $table->foreignId('receiver_id')->constrained('users')->cascadeOnDelete(); + $table->text('message'); + $table->boolean('is_read')->default(false); + $table->boolean('is_deleted')->default(false); + $table->foreignId('deleted_by')->nullable()->constrained('users')->nullOnDelete(); + $table->foreignId('reply_to_id')->nullable()->constrained('direct_messages')->nullOnDelete(); + $table->timestamps(); + + $table->index(['sender_id', 'receiver_id', 'created_at']); + $table->index(['receiver_id', 'is_read']); + }); + + Schema::create('direct_message_reports', function (Blueprint $table) { + $table->id(); + $table->foreignId('reporter_id')->constrained('users')->cascadeOnDelete(); + $table->foreignId('message_id')->constrained('direct_messages')->cascadeOnDelete(); + $table->string('reason', 64); + $table->text('details')->nullable(); + $table->enum('status', ['pending', 'reviewed', 'dismissed'])->default('pending'); + $table->timestamps(); + + $table->unique(['reporter_id', 'message_id']); + $table->index('status'); + }); + } + + public function down(): void + { + Schema::dropIfExists('direct_message_reports'); + Schema::dropIfExists('direct_messages'); + } +}; diff --git a/database/migrations/2026_01_01_000013_create_guild_messages_table.php b/database/migrations/2026_01_01_000013_create_guild_messages_table.php new file mode 100644 index 0000000..ebd7bc9 --- /dev/null +++ b/database/migrations/2026_01_01_000013_create_guild_messages_table.php @@ -0,0 +1,30 @@ +id(); + $table->unsignedBigInteger('guild_id'); + $table->unsignedBigInteger('user_id'); + $table->string('type')->default('message'); // 'message' | 'system' + $table->text('message'); + $table->unsignedBigInteger('reply_to_id')->nullable(); + $table->boolean('is_deleted')->default(false); + $table->timestamps(); + + $table->foreign('guild_id')->references('id')->on('guilds')->onDelete('cascade'); + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $table->index(['guild_id', 'created_at']); + }); + } + + public function down(): void + { + Schema::dropIfExists('guild_messages'); + } +}; diff --git a/database/migrations/2026_04_04_000001_add_player_game_status_index_to_operator_sessions.php b/database/migrations/2026_04_04_000001_add_player_game_status_index_to_operator_sessions.php new file mode 100644 index 0000000..0f9dfc4 --- /dev/null +++ b/database/migrations/2026_04_04_000001_add_player_game_status_index_to_operator_sessions.php @@ -0,0 +1,24 @@ + ? + $table->index(['player_id', 'game_slug', 'status', 'expires_at'], 'os_player_game_status_expires'); + }); + } + + public function down(): void + { + Schema::table('operator_sessions', function (Blueprint $table) { + $table->dropIndex('os_player_game_status_expires'); + }); + } +}; diff --git a/database/migrations/2026_04_04_000002_add_round_fields_to_game_bets.php b/database/migrations/2026_04_04_000002_add_round_fields_to_game_bets.php new file mode 100644 index 0000000..3b850f2 --- /dev/null +++ b/database/migrations/2026_04_04_000002_add_round_fields_to_game_bets.php @@ -0,0 +1,27 @@ +string('session_token', 36)->nullable()->after('currency'); + $table->unsignedInteger('round_number')->nullable()->after('session_token'); + $table->string('server_seed_hash', 64)->nullable()->after('round_number'); + + $table->index('session_token'); + }); + } + + public function down(): void + { + Schema::table('game_bets', function (Blueprint $table) { + $table->dropIndex(['session_token']); + $table->dropColumn(['session_token', 'round_number', 'server_seed_hash']); + }); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php new file mode 100644 index 0000000..d01a0ef --- /dev/null +++ b/database/seeders/DatabaseSeeder.php @@ -0,0 +1,23 @@ +create(); + + User::factory()->create([ + 'name' => 'Test User', + 'email' => 'test@example.com', + ]); + } +} diff --git a/docs/gateway.md b/docs/gateway.md new file mode 100644 index 0000000..b6918a0 --- /dev/null +++ b/docs/gateway.md @@ -0,0 +1,93 @@ +# API Gateway in this Laravel App + +This app acts as a thin API Gateway in front of an external backend API. All feature controllers proxy to the external API and no longer access a local database. + +## Configuration + +Set these env vars (see `config/services.php`): + +- `BACKEND_API_BASE` (default: `http://casinoapi.test`) +- `BACKEND_API_TOKEN` (required, Bearer service token) +- `BACKEND_API_TIMEOUT` (float seconds, default `8.0`) + +## Standard Upstream Headers + +Every upstream call includes: + +- `Authorization: Bearer {BACKEND_API_TOKEN}` +- `X-User-Id: ` +- `X-User-Device: ` +- `X-User-IP: ` +- `Accept: application/json` (SSE endpoints use `text/event-stream`) + +## Timeouts & Retries + +- Timeout: `8` seconds (configurable via `BACKEND_API_TIMEOUT`). +- Retries: GET requests retry 2x on timeout/5xx with 100ms backoff. Mutations (POST/PATCH/DELETE) do not retry. + +## Error Mapping (Gateway → Frontend) + +- Upstream 4xx → same 4xx, body: + ```json + { "error": "client_error", "message": "Invalid request" } + ``` +- Upstream 5xx (reachable server) → `503`, body: + ```json + { "error": "service_unavailable", "message": "Internal server error" } + ``` +- Network/Timeout/Unreachable → `502`, body: + ```json + { "error": "bad_gateway", "message": "API server not reachable" } + ``` + +## Shared Building Blocks + +- `App\Services\BackendHttpClient` centralizes Base URL, token, timeout, GET retry policy, and user-context headers. +- `App\Http\Controllers\Concerns\ProxiesBackend` provides the error mapping helpers used by all controllers. + +## Domains proxied + +- Chat: `GET/POST /api/chat`, `POST /api/chat/{id}/react` → upstream `/chat` +- Support: `/api/support/*` → upstream `/support/*` (SSE passthrough for `/stream`) +- Vault: `/api/wallet/vault*` → upstream `/wallet/vault*` +- Social/Profile/Friends: profile page & actions → upstream `/users/*`, `/social/*`, `/friends/*` +- Bonuses/Promos: `/api/bonuses/app`, `/api/user/bonuses`, `/api/promos/apply` → upstream `/bonuses/app`, `/users/me/bonuses`, `/promos/apply` +- Guilds: pages & actions → upstream `/guilds*` +- Wallet/VIP: pages/actions → upstream `/wallet`, `/vip-levels*` +- Admin: pages/actions → upstream `/admin/*` + +## Frontend Contract Notes + +- Internal routes (`/api/...` or page routes) remain stable. Controllers may reshape upstream JSON minimally to the shapes already expected by Vue components. +- Example: Chat GET maps upstream `{ messages: [...] }` to `{ data: [...], last_id }` and flattens user + reactions. + +## Example: Faking Upstream in Tests + +All proxy feature tests use `Http::fake()` to simulate the external API. + +```php +use Illuminate\Support\Facades\Http; + +Http::fake([ + config('services.backend.base') . '/chat*' => Http::response([ + 'messages' => [ ['id'=>1,'user_id'=>2,'username'=>'dolo','avatar'=>null,'message'=>'hi','reactions'=>[],'created_at'=>now()->toIso8601String()] ], + ], 200), +]); + +$res = $this->actingAs($user)->get('/api/chat?limit=50'); +$res->assertStatus(200)->assertJsonStructure(['data','last_id']); +``` + +## Running Tests + +- Ensure `.env.testing` sets `BACKEND_API_BASE` (any value is fine) and no real external calls are required (we fake all with `Http::fake()`). +- Run: `php artisan test` or `vendor\bin\phpunit`. + +## SSE Support (Support Chat) + +- `GET /api/support/stream` passes through `text/event-stream` from upstream. If upstream is not OK or stream fails to start, the Gateway returns `503 service_unavailable` JSON. + +## Extending + +- Add new upstream endpoints by injecting `BackendHttpClient` into your controller and using `get/post/patch/delete/postMultipart`. +- Always use `ProxiesBackend` helpers to map errors consistently. diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..9ab748d --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,47 @@ +import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'; +import prettier from 'eslint-config-prettier'; +import importPlugin from 'eslint-plugin-import'; +import vue from 'eslint-plugin-vue'; + +export default defineConfigWithVueTs( + vue.configs['flat/essential'], + vueTsConfigs.recommended, + { + ignores: ['vendor', 'node_modules', 'public', 'bootstrap/ssr', 'tailwind.config.js', 'vite.config.ts', 'resources/js/components/ui/*'], + }, + { + plugins: { + import: importPlugin, + }, + settings: { + 'import/resolver': { + typescript: { + alwaysTryTypes: true, + project: './tsconfig.json', + }, + }, + }, + rules: { + 'vue/multi-word-component-names': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/consistent-type-imports': [ + 'error', + { + prefer: 'type-imports', + fixStyle: 'separate-type-imports', + }, + ], + 'import/order': [ + 'error', + { + groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'], + alphabetize: { + order: 'asc', + caseInsensitive: true, + }, + }, + ], + }, + }, + prettier, +); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..75b9900 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,7742 @@ +{ + "name": "casino", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@emoji-mart/data": "^1.2.1", + "@inertiajs/vue3": "^2.3.7", + "@vueuse/core": "^12.8.2", + "chart.js": "^4.5.1", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "emoji-mart": "^5.6.0", + "html2canvas": "^1.4.1", + "jspdf": "^4.2.0", + "laravel-vite-plugin": "^2.0.0", + "lucide-vue-next": "^0.468.0", + "reka-ui": "^2.6.1", + "tailwind-merge": "^3.2.0", + "tailwindcss": "^4.1.1", + "tw-animate-css": "^1.2.5", + "vue": "^3.5.13", + "vue-i18n": "^9.14.0", + "vue-input-otp": "^0.3.2" + }, + "devDependencies": { + "@eslint/js": "^9.19.0", + "@tailwindcss/vite": "^4.1.11", + "@types/node": "^22.13.5", + "@vitejs/plugin-vue": "^6.0.0", + "@vue/eslint-config-typescript": "^14.3.0", + "concurrently": "^9.0.1", + "eslint": "^9.17.0", + "eslint-config-prettier": "^10.0.1", + "eslint-import-resolver-typescript": "^4.4.4", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-vue": "^9.32.0", + "prettier": "^3.4.2", + "prettier-plugin-tailwindcss": "^0.6.11", + "typescript": "^5.2.2", + "typescript-eslint": "^8.23.0", + "vite": "^7.0.4", + "vite-plugin-compression": "^0.5.1", + "vue-tsc": "^2.2.4" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "4.9.5", + "@rollup/rollup-win32-x64-msvc": "4.9.5", + "@tailwindcss/oxide-linux-x64-gnu": "^4.0.1", + "@tailwindcss/oxide-win32-x64-msvc": "^4.0.1", + "lightningcss-linux-x64-gnu": "^1.29.1", + "lightningcss-win32-x64-msvc": "^1.29.1" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emoji-mart/data": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emoji-mart/data/-/data-1.2.1.tgz", + "integrity": "sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.4.tgz", + "integrity": "sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.4.tgz", + "integrity": "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.3", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.4.tgz", + "integrity": "sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", + "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", + "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", + "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.4", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@floating-ui/vue": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.1.10.tgz", + "integrity": "sha512-vdf8f6rHnFPPLRsmL4p12wYl+Ux4mOJOkjzKEMYVnwdf7UFdvBtHlLvQyx8iKG5vhPRbDRgZxdtpmyigDPjzYg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.5", + "@floating-ui/utils": "^0.2.10", + "vue-demi": ">=0.13.0" + } + }, + "node_modules/@floating-ui/vue/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@inertiajs/core": { + "version": "2.3.16", + "resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-2.3.16.tgz", + "integrity": "sha512-yshF85m7Ge3IhCdwpiWRTZ5+1fzo7BK/32XWonGKsCHmHUouy2/jUasNqaQX/FnkuL0UDrstnMIZjrFQ1hscaQ==", + "license": "MIT", + "dependencies": { + "@types/lodash-es": "^4.17.12", + "axios": "^1.13.5", + "laravel-precognition": "^1.0.2", + "lodash-es": "^4.17.23", + "qs": "^6.14.2" + } + }, + "node_modules/@inertiajs/vue3": { + "version": "2.3.16", + "resolved": "https://registry.npmjs.org/@inertiajs/vue3/-/vue3-2.3.16.tgz", + "integrity": "sha512-R0FgIFtVQX7PwHlXUR0iesol/uk32g2NanGqqEx5L5DSd+3mcX5K7CqUOBIjCP/7ti/8K9YzihtEWaAgSh/yQg==", + "license": "MIT", + "dependencies": { + "@inertiajs/core": "2.3.16", + "@types/lodash-es": "^4.17.12", + "laravel-precognition": "^1.0.2", + "lodash-es": "^4.17.23" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/@internationalized/date": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.11.0.tgz", + "integrity": "sha512-BOx5huLAWhicM9/ZFs84CzP+V3gBW6vlpM02yzsdYC7TGlZJX1OJiEEHcSayF00Z+3jLlm4w79amvSt6RqKN3Q==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.5.tgz", + "integrity": "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@intlify/core-base": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.5.tgz", + "integrity": "sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==", + "license": "MIT", + "dependencies": { + "@intlify/message-compiler": "9.14.5", + "@intlify/shared": "9.14.5" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/message-compiler": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.5.tgz", + "integrity": "sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==", + "license": "MIT", + "dependencies": { + "@intlify/shared": "9.14.5", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/shared": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.5.tgz", + "integrity": "sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", + "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.5.tgz", + "integrity": "sha512-Dq1bqBdLaZ1Gb/l2e5/+o3B18+8TI9ANlA1SkejZqDgdU/jK/ThYaMPMJpVMMXy2uRHvGKbkz9vheVGdq3cJfA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.5.tgz", + "integrity": "sha512-1q+mykKE3Vot1kaFJIDoUFv5TuW+QQVaf2FmTT9krg86pQrGStOSJJ0Zil7CFagyxDuouTepzt5Y5TVzyajOdQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.19.tgz", + "integrity": "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", + "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.31.1", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.1" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", + "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-x64": "4.2.1", + "@tailwindcss/oxide-freebsd-x64": "4.2.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-x64-musl": "4.2.1", + "@tailwindcss/oxide-wasm32-wasi": "4.2.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", + "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", + "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", + "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", + "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", + "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", + "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", + "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", + "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", + "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", + "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", + "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", + "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz", + "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.2.1", + "@tailwindcss/oxide": "4.2.1", + "tailwindcss": "4.2.1" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.13.19", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.19.tgz", + "integrity": "sha512-/BMP7kNhzKOd7wnDeB8NrIRNLwkf5AhCYCvtfZV2GXWbBieFm/el0n6LOAXlTi6ZwHICSNnQcIxRCWHrLzDY+g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/vue-virtual": { + "version": "3.13.19", + "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.19.tgz", + "integrity": "sha512-07Fp1TYuIziB4zIDA/moeDKHODePy3K1fN4c4VIAGnkxo1+uOvBJP7m54CoxKiQX6Q9a1dZnznrwOg9C86yvvA==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.13.19" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.0.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "22.19.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz", + "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", + "license": "MIT" + }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", + "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/type-utils": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.56.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", + "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", + "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.56.1", + "@typescript-eslint/types": "^8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", + "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", + "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", + "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", + "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.56.1", + "@typescript-eslint/tsconfig-utils": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", + "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", + "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.4.tgz", + "integrity": "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-rc.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz", + "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.15" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.15.tgz", + "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.15.tgz", + "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.29.tgz", + "integrity": "sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/shared": "3.5.29", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.29.tgz", + "integrity": "sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.29", + "@vue/shared": "3.5.29" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.29.tgz", + "integrity": "sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/compiler-core": "3.5.29", + "@vue/compiler-dom": "3.5.29", + "@vue/compiler-ssr": "3.5.29", + "@vue/shared": "3.5.29", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.29.tgz", + "integrity": "sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.29", + "@vue/shared": "3.5.29" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "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/eslint-config-typescript": { + "version": "14.7.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-14.7.0.tgz", + "integrity": "sha512-iegbMINVc+seZ/QxtzWiOBozctrHiF2WvGedruu2EbLujg9VuU0FQiNcN2z1ycuaoKKpF4m2qzB5HDEMKbxtIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.56.0", + "fast-glob": "^3.3.3", + "typescript-eslint": "^8.56.0", + "vue-eslint-parser": "^10.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.10.0 || ^10.0.0", + "eslint-plugin-vue": "^9.28.0 || ^10.0.0", + "typescript": ">=4.8.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.12.tgz", + "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^1.0.3", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core/node_modules/minimatch": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.7.tgz", + "integrity": "sha512-MOwgjc8tfrpn5QQEvjijjmDVtMw2oL88ugTevzxQnzRLm6l3fVEF2gzU0kYeYYKD8C66+IdGX6peJ4MyUlUnPg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.29.tgz", + "integrity": "sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.29" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.29.tgz", + "integrity": "sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.29", + "@vue/shared": "3.5.29" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.29.tgz", + "integrity": "sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.29", + "@vue/runtime-core": "3.5.29", + "@vue/shared": "3.5.29", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.29.tgz", + "integrity": "sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.29", + "@vue/shared": "3.5.29" + }, + "peerDependencies": { + "vue": "3.5.29" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.29.tgz", + "integrity": "sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", + "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/metadata": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz", + "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", + "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", + "license": "MIT", + "dependencies": { + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/alien-signals": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz", + "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", + "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/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/canvg": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz", + "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "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/chart.js": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", + "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "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/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "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/core-js": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "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/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" + }, + "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/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dompurify": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz", + "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optional": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "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-mart": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.6.0.tgz", + "integrity": "sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==", + "license": "MIT" + }, + "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/enhanced-resolve": { + "version": "5.19.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", + "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "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-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "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/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "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/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.3", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", + "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.3", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-context": { + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz", + "integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-tsconfig": "^4.10.1", + "stable-hash-x": "^0.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-context" + }, + "peerDependencies": { + "unrs-resolver": "^1.0.0" + }, + "peerDependenciesMeta": { + "unrs-resolver": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.4.tgz", + "integrity": "sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==", + "dev": true, + "license": "ISC", + "dependencies": { + "debug": "^4.4.1", + "eslint-import-context": "^0.1.8", + "get-tsconfig": "^4.10.1", + "is-bun-module": "^2.0.0", + "stable-hash-x": "^0.2.0", + "tinyglobby": "^0.2.14", + "unrs-resolver": "^1.7.11" + }, + "engines": { + "node": "^16.17.0 || >=18.6.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.4.tgz", + "integrity": "sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-vue": { + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.33.0.tgz", + "integrity": "sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "globals": "^13.24.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.15", + "semver": "^7.6.3", + "vue-eslint-parser": "^9.4.3", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-vue/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-vue/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-vue/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-vue/node_modules/vue-eslint-parser": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", + "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.4.tgz", + "integrity": "sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "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/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-png": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz", + "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==", + "license": "MIT", + "dependencies": { + "@types/pako": "^2.0.3", + "iobuffer": "^5.3.2", + "pako": "^2.1.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "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/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "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-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/iobuffer": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz", + "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==", + "license": "MIT" + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jspdf": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-4.2.1.tgz", + "integrity": "sha512-YyAXyvnmjTbR4bHQRLzex3CuINCDlQnBqoSYyjJwTP2x9jDLuKDzy7aKUl0hgx3uhcl7xzg32agn5vlie6HIlQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "fast-png": "^6.2.0", + "fflate": "^0.8.1" + }, + "optionalDependencies": { + "canvg": "^3.0.11", + "core-js": "^3.6.0", + "dompurify": "^3.3.1", + "html2canvas": "^1.0.0-rc.5" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/laravel-precognition": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/laravel-precognition/-/laravel-precognition-1.0.2.tgz", + "integrity": "sha512-0H08JDdMWONrL/N314fvsO3FATJwGGlFKGkMF3nNmizVFJaWs17816iM+sX7Rp8d5hUjYCx6WLfsehSKfaTxjg==", + "license": "MIT", + "dependencies": { + "axios": "^1.4.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/laravel-vite-plugin": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.1.0.tgz", + "integrity": "sha512-z+ck2BSV6KWtYcoIzk9Y5+p4NEjqM+Y4i8/H+VZRLq0OgNjW2DqyADquwYu5j8qRvaXwzNmfCWl1KrMlV1zpsg==", + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "vite-plugin-full-reload": "^1.1.0" + }, + "bin": { + "clean-orphaned-assets": "bin/clean.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^7.0.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", + "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", + "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.31.1", + "lightningcss-darwin-arm64": "1.31.1", + "lightningcss-darwin-x64": "1.31.1", + "lightningcss-freebsd-x64": "1.31.1", + "lightningcss-linux-arm-gnueabihf": "1.31.1", + "lightningcss-linux-arm64-gnu": "1.31.1", + "lightningcss-linux-arm64-musl": "1.31.1", + "lightningcss-linux-x64-gnu": "1.31.1", + "lightningcss-linux-x64-musl": "1.31.1", + "lightningcss-win32-arm64-msvc": "1.31.1", + "lightningcss-win32-x64-msvc": "1.31.1" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", + "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", + "cpu": [ + "arm64" + ], + "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.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", + "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", + "cpu": [ + "arm64" + ], + "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.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", + "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", + "cpu": [ + "x64" + ], + "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.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", + "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", + "cpu": [ + "x64" + ], + "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.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", + "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", + "cpu": [ + "arm" + ], + "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.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", + "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", + "cpu": [ + "arm64" + ], + "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.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", + "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", + "cpu": [ + "arm64" + ], + "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.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", + "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", + "cpu": [ + "x64" + ], + "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.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", + "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", + "cpu": [ + "x64" + ], + "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.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", + "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", + "cpu": [ + "arm64" + ], + "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.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", + "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lucide-vue-next": { + "version": "0.468.0", + "resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-0.468.0.tgz", + "integrity": "sha512-quV/6T8YB1XK0VOEnebg3Byd8Rsan5/m95cvjnuHV4vcS3qEnLAybkrSh0hk3ppavx+V7R1PjNW+mGDvcBdz4A==", + "license": "ISC", + "peerDependencies": { + "vue": ">=3.0.1" + } + }, + "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/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/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "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/minimatch": { + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz", + "integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT", + "optional": true + }, + "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": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "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.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.6.14", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz", + "integrity": "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-hermes": "*", + "@prettier/plugin-oxc": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-hermes": { + "optional": true + }, + "@prettier/plugin-oxc": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "license": "MIT", + "optional": true, + "dependencies": { + "performance-now": "^2.1.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT", + "optional": true + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reka-ui": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.8.2.tgz", + "integrity": "sha512-8lTKcJhmG+D3UyJxhBnNnW/720sLzm0pbA9AC1MWazmJ5YchJAyTSl+O00xP/kxBmEN0fw5JqWVHguiFmsGjzA==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.6.13", + "@floating-ui/vue": "^1.1.6", + "@internationalized/date": "^3.5.0", + "@internationalized/number": "^3.5.0", + "@tanstack/vue-virtual": "^3.12.0", + "@vueuse/core": "^14.1.0", + "@vueuse/shared": "^14.1.0", + "aria-hidden": "^1.2.4", + "defu": "^6.1.4", + "ohash": "^2.0.11" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/zernonia" + }, + "peerDependencies": { + "vue": ">= 3.2.0" + } + }, + "node_modules/reka-ui/node_modules/@vueuse/core": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.2.1.tgz", + "integrity": "sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "14.2.1", + "@vueuse/shared": "14.2.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/reka-ui/node_modules/@vueuse/metadata": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.2.1.tgz", + "integrity": "sha512-1ButlVtj5Sb/HDtIy1HFr1VqCP4G6Ypqt5MAo0lCgjokrk2mvQKsK2uuy0vqu/Ks+sHfuHo0B9Y9jn9xKdjZsw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/reka-ui/node_modules/@vueuse/shared": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.2.1.tgz", + "integrity": "sha512-shTJncjV9JTI4oVNyF1FQonetYAiTBd+Qj7cY89SWbXSkx7gyhrgtEdF2ZAVWS1S3SHlaROO6F2IesJxQEkZBw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "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/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rollup/node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "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/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "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/stable-hash-x": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz", + "integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/tailwind-merge": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", + "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", + "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/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/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "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/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "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==", + "license": "0BSD" + }, + "node_modules/tw-animate-css": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", + "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.1.tgz", + "integrity": "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.56.1", + "@typescript-eslint/parser": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "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", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.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 + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "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-compression": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/vite-plugin-compression/-/vite-plugin-compression-0.5.1.tgz", + "integrity": "sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "debug": "^4.3.3", + "fs-extra": "^10.0.0" + }, + "peerDependencies": { + "vite": ">=2.0.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "picomatch": "^2.3.1" + } + }, + "node_modules/vite/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/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "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.29", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.29.tgz", + "integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@vue/compiler-dom": "3.5.29", + "@vue/compiler-sfc": "3.5.29", + "@vue/runtime-dom": "3.5.29", + "@vue/server-renderer": "3.5.29", + "@vue/shared": "3.5.29" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-eslint-parser": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.4.0.tgz", + "integrity": "sha512-Vxi9pJdbN3ZnVGLODVtZ7y4Y2kzAAE2Cm0CZ3ZDRvydVYxZ6VrnBhLikBsRS+dpwj4Jv4UCv21PTEwF5rQ9WXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "eslint-scope": "^8.2.0 || ^9.0.0", + "eslint-visitor-keys": "^4.2.0 || ^5.0.0", + "espree": "^10.3.0 || ^11.0.0", + "esquery": "^1.6.0", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/vue-i18n": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.5.tgz", + "integrity": "sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==", + "deprecated": "v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html", + "license": "MIT", + "dependencies": { + "@intlify/core-base": "9.14.5", + "@intlify/shared": "9.14.5", + "@vue/devtools-api": "^6.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vue-input-otp": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/vue-input-otp/-/vue-input-otp-0.3.2.tgz", + "integrity": "sha512-QMl1842WB6uNAsK4+mZXIskb00TOfahH3AQt8rpRecbtQnOp+oHSUbL/Z3wekfy6pAl+hyN3e1rCUSkCMzbDLQ==", + "license": "MIT", + "dependencies": { + "@vueuse/core": "^12.8.2", + "reka-ui": "^2.6.1" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vue-tsc": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.12.tgz", + "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.15", + "@vue/language-core": "2.2.12" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "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/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" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..eacb15c --- /dev/null +++ b/package.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://www.schemastore.org/package.json", + "private": true, + "type": "module", + "scripts": { + "build": "vite build", + "build:ssr": "vite build && vite build --ssr", + "dev": "vite", + "format": "prettier --write resources/", + "format:check": "prettier --check resources/", + "lint": "eslint . --fix" + }, + "devDependencies": { + "@eslint/js": "^9.19.0", + "@tailwindcss/vite": "^4.1.11", + "@types/node": "^22.13.5", + "@vitejs/plugin-vue": "^6.0.0", + "@vue/eslint-config-typescript": "^14.3.0", + "concurrently": "^9.0.1", + "eslint": "^9.17.0", + "eslint-config-prettier": "^10.0.1", + "eslint-import-resolver-typescript": "^4.4.4", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-vue": "^9.32.0", + "prettier": "^3.4.2", + "prettier-plugin-tailwindcss": "^0.6.11", + "typescript": "^5.2.2", + "typescript-eslint": "^8.23.0", + "vite": "^7.0.4", + "vite-plugin-compression": "^0.5.1", + "vue-tsc": "^2.2.4" + }, + "dependencies": { + "@emoji-mart/data": "^1.2.1", + "@inertiajs/vue3": "^2.3.7", + "@vueuse/core": "^12.8.2", + "chart.js": "^4.5.1", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "emoji-mart": "^5.6.0", + "html2canvas": "^1.4.1", + "jspdf": "^4.2.0", + "laravel-vite-plugin": "^2.0.0", + "lucide-vue-next": "^0.468.0", + "reka-ui": "^2.6.1", + "tailwind-merge": "^3.2.0", + "tailwindcss": "^4.1.1", + "tw-animate-css": "^1.2.5", + "vue": "^3.5.13", + "vue-i18n": "^9.14.0", + "vue-input-otp": "^0.3.2" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "4.9.5", + "@rollup/rollup-win32-x64-msvc": "4.9.5", + "@tailwindcss/oxide-linux-x64-gnu": "^4.0.1", + "@tailwindcss/oxide-win32-x64-msvc": "^4.0.1", + "lightningcss-linux-x64-gnu": "^1.29.1", + "lightningcss-win32-x64-msvc": "^1.29.1" + } +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..d703241 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,35 @@ + + + + + tests/Unit + + + tests/Feature + + + + + app + + + + + + + + + + + + + + + + + + diff --git a/pint.json b/pint.json new file mode 100644 index 0000000..93061b6 --- /dev/null +++ b/pint.json @@ -0,0 +1,3 @@ +{ + "preset": "laravel" +} 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/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000..c2efef6 Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..236fadb Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..e4e710e --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,3 @@ + + + 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/public/sounds/notification.mp3 b/public/sounds/notification.mp3 new file mode 100644 index 0000000..e6e2336 Binary files /dev/null and b/public/sounds/notification.mp3 differ diff --git a/resources/css/app.css b/resources/css/app.css new file mode 100644 index 0000000..6f9d1f5 --- /dev/null +++ b/resources/css/app.css @@ -0,0 +1,172 @@ +@import 'tailwindcss'; + +@import 'tw-animate-css'; + +@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php'; +@source '../../storage/framework/views/*.php'; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --font-sans: + Instrument Sans, ui-sans-serif, system-ui, sans-serif, + 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', + 'Noto Color Emoji'; + + --radius-lg: var(--radius); + --radius-md: calc(var(--radius) - 2px); + --radius-sm: calc(var(--radius) - 4px); + + --color-background: var(--background); + --color-foreground: var(--foreground); + + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + + --color-sidebar: var(--sidebar-background); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +/* + The default border color has changed to `currentColor` in Tailwind CSS v4, + so we've added these compatibility styles to make sure everything still + looks the same as it did with Tailwind CSS v3. + + If we ever want to remove these styles, we need to add an explicit border + color utility to any element that depends on these defaults. +*/ +@layer base { + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + border-color: var(--color-gray-200, currentColor); + } +} + +@layer utilities { + body, + html { + --font-sans: + 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, + 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', + 'Noto Color Emoji'; + } +} + +:root { + --background: hsl(0 0% 100%); + --foreground: hsl(0 0% 3.9%); + --card: hsl(0 0% 100%); + --card-foreground: hsl(0 0% 3.9%); + --popover: hsl(0 0% 100%); + --popover-foreground: hsl(0 0% 3.9%); + --primary: hsl(0 0% 9%); + --primary-foreground: hsl(0 0% 98%); + --secondary: hsl(0 0% 92.1%); + --secondary-foreground: hsl(0 0% 9%); + --muted: hsl(0 0% 96.1%); + --muted-foreground: hsl(0 0% 45.1%); + --accent: hsl(0 0% 96.1%); + --accent-foreground: hsl(0 0% 9%); + --destructive: hsl(0 84.2% 60.2%); + --destructive-foreground: hsl(0 0% 98%); + --border: hsl(0 0% 92.8%); + --input: hsl(0 0% 89.8%); + --ring: hsl(0 0% 3.9%); + --chart-1: hsl(12 76% 61%); + --chart-2: hsl(173 58% 39%); + --chart-3: hsl(197 37% 24%); + --chart-4: hsl(43 74% 66%); + --chart-5: hsl(27 87% 67%); + --radius: 0.5rem; + --sidebar-background: hsl(0 0% 98%); + --sidebar-foreground: hsl(240 5.3% 26.1%); + --sidebar-primary: hsl(0 0% 10%); + --sidebar-primary-foreground: hsl(0 0% 98%); + --sidebar-accent: hsl(0 0% 94%); + --sidebar-accent-foreground: hsl(0 0% 30%); + --sidebar-border: hsl(0 0% 91%); + --sidebar-ring: hsl(217.2 91.2% 59.8%); + --sidebar: hsl(0 0% 98%); +} + +.dark { + --background: hsl(0 0% 3.9%); + --foreground: hsl(0 0% 98%); + --card: hsl(0 0% 3.9%); + --card-foreground: hsl(0 0% 98%); + --popover: hsl(0 0% 3.9%); + --popover-foreground: hsl(0 0% 98%); + --primary: hsl(0 0% 98%); + --primary-foreground: hsl(0 0% 9%); + --secondary: hsl(0 0% 14.9%); + --secondary-foreground: hsl(0 0% 98%); + --muted: hsl(0 0% 16.08%); + --muted-foreground: hsl(0 0% 63.9%); + --accent: hsl(0 0% 14.9%); + --accent-foreground: hsl(0 0% 98%); + --destructive: hsl(0 84% 60%); + --destructive-foreground: hsl(0 0% 98%); + --border: hsl(0 0% 14.9%); + --input: hsl(0 0% 14.9%); + --ring: hsl(0 0% 83.1%); + --chart-1: hsl(220 70% 50%); + --chart-2: hsl(160 60% 45%); + --chart-3: hsl(30 80% 55%); + --chart-4: hsl(280 65% 60%); + --chart-5: hsl(340 75% 55%); + --sidebar-background: hsl(0 0% 7%); + --sidebar-foreground: hsl(0 0% 95.9%); + --sidebar-primary: hsl(360, 100%, 100%); + --sidebar-primary-foreground: hsl(0 0% 100%); + --sidebar-accent: hsl(0 0% 15.9%); + --sidebar-accent-foreground: hsl(240 4.8% 95.9%); + --sidebar-border: hsl(0 0% 15.9%); + --sidebar-ring: hsl(217.2 91.2% 59.8%); + --sidebar: hsl(240 5.9% 10%); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/resources/js/app.ts b/resources/js/app.ts new file mode 100644 index 0000000..aab053e --- /dev/null +++ b/resources/js/app.ts @@ -0,0 +1,49 @@ +import { createInertiaApp } from '@inertiajs/vue3'; +import axios from 'axios'; +import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; +import type { DefineComponent } from 'vue'; +import { createApp, h } from 'vue'; +import '../css/app.css'; +import { initializeTheme } from './composables/useAppearance'; +import { initializePrimaryColor } from './composables/usePrimaryColor'; +import { i18n, initI18n } from './i18n'; + +const appName = import.meta.env.VITE_APP_NAME || 'Laravel'; + +// Configure Axios to include CSRF token +axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; +const token = document.head.querySelector('meta[name="csrf-token"]'); +if (token) { + axios.defaults.headers.common['X-CSRF-TOKEN'] = (token as HTMLMetaElement).content; +} + +createInertiaApp({ + title: (title) => (title ? `${title} - ${appName}` : appName), + resolve: (name) => + resolvePageComponent( + `./pages/${name}.vue`, + import.meta.glob('./pages/**/*.vue', { eager: false }), + ), + setup({ el, App, props, plugin }) { + const initialLocale = ((props as any).initialPage?.props?.locale) || 'en'; + const app = createApp({ render: () => h(App, props) }); + + // Initialize i18n with the server-provided locale, then mount + initI18n(initialLocale).finally(() => { + app.use(plugin).use(i18n).mount(el); + }); + }, + progress: { + color: '#df006a', + }, +}); + +// This will set light / dark mode on page load... +initializeTheme(); +// Apply saved primary color (main accent) on page load... +initializePrimaryColor(); +// Initialize casino data-theme before first paint to avoid flash +try { + const t = localStorage.getItem('casino-theme'); + document.documentElement.setAttribute('data-theme', t === 'light' ? 'light' : 'dark'); +} catch {} diff --git a/resources/js/components/AppContent.vue b/resources/js/components/AppContent.vue new file mode 100644 index 0000000..9b6ef3c --- /dev/null +++ b/resources/js/components/AppContent.vue @@ -0,0 +1,12 @@ + + + diff --git a/resources/js/components/AppHeader.vue b/resources/js/components/AppHeader.vue new file mode 100644 index 0000000..f3983f5 --- /dev/null +++ b/resources/js/components/AppHeader.vue @@ -0,0 +1,26 @@ + + + diff --git a/resources/js/components/AppLogoIcon.vue b/resources/js/components/AppLogoIcon.vue new file mode 100644 index 0000000..9b8cfb3 --- /dev/null +++ b/resources/js/components/AppLogoIcon.vue @@ -0,0 +1,14 @@ + diff --git a/resources/js/components/AppShell.vue b/resources/js/components/AppShell.vue new file mode 100644 index 0000000..0d3850a --- /dev/null +++ b/resources/js/components/AppShell.vue @@ -0,0 +1,12 @@ + + + diff --git a/resources/js/components/AppSidebar.vue b/resources/js/components/AppSidebar.vue new file mode 100644 index 0000000..61e61d5 --- /dev/null +++ b/resources/js/components/AppSidebar.vue @@ -0,0 +1,9 @@ + + + diff --git a/resources/js/components/AppSidebarHeader.vue b/resources/js/components/AppSidebarHeader.vue new file mode 100644 index 0000000..c771a93 --- /dev/null +++ b/resources/js/components/AppSidebarHeader.vue @@ -0,0 +1,24 @@ + + + diff --git a/resources/js/components/AppearanceTabs.vue b/resources/js/components/AppearanceTabs.vue new file mode 100644 index 0000000..777f01a --- /dev/null +++ b/resources/js/components/AppearanceTabs.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/resources/js/components/Heading.vue b/resources/js/components/Heading.vue new file mode 100644 index 0000000..8d74f04 --- /dev/null +++ b/resources/js/components/Heading.vue @@ -0,0 +1,24 @@ + + + diff --git a/resources/js/components/InputError.vue b/resources/js/components/InputError.vue new file mode 100644 index 0000000..df44455 --- /dev/null +++ b/resources/js/components/InputError.vue @@ -0,0 +1,13 @@ + + + diff --git a/resources/js/components/TextLink.vue b/resources/js/components/TextLink.vue new file mode 100644 index 0000000..3166d79 --- /dev/null +++ b/resources/js/components/TextLink.vue @@ -0,0 +1,18 @@ + + + diff --git a/resources/js/components/TwoFactorRecoveryCodes.vue b/resources/js/components/TwoFactorRecoveryCodes.vue new file mode 100644 index 0000000..0ddaa28 --- /dev/null +++ b/resources/js/components/TwoFactorRecoveryCodes.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/resources/js/components/TwoFactorSetupModal.vue b/resources/js/components/TwoFactorSetupModal.vue new file mode 100644 index 0000000..26c0d6f --- /dev/null +++ b/resources/js/components/TwoFactorSetupModal.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/resources/js/components/auth/AuthModals.vue b/resources/js/components/auth/AuthModals.vue new file mode 100644 index 0000000..562525a --- /dev/null +++ b/resources/js/components/auth/AuthModals.vue @@ -0,0 +1,323 @@ + + + + + diff --git a/resources/js/components/auth/LoginForm.vue b/resources/js/components/auth/LoginForm.vue new file mode 100644 index 0000000..38df9d3 --- /dev/null +++ b/resources/js/components/auth/LoginForm.vue @@ -0,0 +1,289 @@ + + + + + diff --git a/resources/js/components/auth/RegisterForm.vue b/resources/js/components/auth/RegisterForm.vue new file mode 100644 index 0000000..03df784 --- /dev/null +++ b/resources/js/components/auth/RegisterForm.vue @@ -0,0 +1,567 @@ + + + + + diff --git a/resources/js/components/chat/GlobalChat.vue b/resources/js/components/chat/GlobalChat.vue new file mode 100644 index 0000000..3937ed6 --- /dev/null +++ b/resources/js/components/chat/GlobalChat.vue @@ -0,0 +1,1557 @@ + + + + + diff --git a/resources/js/components/support/SupportChat.vue b/resources/js/components/support/SupportChat.vue new file mode 100644 index 0000000..4a9e9df --- /dev/null +++ b/resources/js/components/support/SupportChat.vue @@ -0,0 +1,694 @@ + + + + + diff --git a/resources/js/components/ui/AppLoading.vue b/resources/js/components/ui/AppLoading.vue new file mode 100644 index 0000000..c0c5797 --- /dev/null +++ b/resources/js/components/ui/AppLoading.vue @@ -0,0 +1,126 @@ + + + + + diff --git a/resources/js/components/ui/ConfirmModal.vue b/resources/js/components/ui/ConfirmModal.vue new file mode 100644 index 0000000..a23fae5 --- /dev/null +++ b/resources/js/components/ui/ConfirmModal.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/resources/js/components/ui/CountrySelect.vue b/resources/js/components/ui/CountrySelect.vue new file mode 100644 index 0000000..cec14e8 --- /dev/null +++ b/resources/js/components/ui/CountrySelect.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/resources/js/components/ui/DatePicker.vue b/resources/js/components/ui/DatePicker.vue new file mode 100644 index 0000000..477b128 --- /dev/null +++ b/resources/js/components/ui/DatePicker.vue @@ -0,0 +1,72 @@ + + + diff --git a/resources/js/components/ui/Footer.vue b/resources/js/components/ui/Footer.vue new file mode 100644 index 0000000..6ea34b2 --- /dev/null +++ b/resources/js/components/ui/Footer.vue @@ -0,0 +1,258 @@ + + + + + diff --git a/resources/js/components/ui/Notification.vue b/resources/js/components/ui/Notification.vue new file mode 100644 index 0000000..04c3a98 --- /dev/null +++ b/resources/js/components/ui/Notification.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/resources/js/components/ui/SearchModal.vue b/resources/js/components/ui/SearchModal.vue new file mode 100644 index 0000000..a375b30 --- /dev/null +++ b/resources/js/components/ui/SearchModal.vue @@ -0,0 +1,589 @@ + + + + + diff --git a/resources/js/components/ui/Separator.vue b/resources/js/components/ui/Separator.vue new file mode 100644 index 0000000..0e71245 --- /dev/null +++ b/resources/js/components/ui/Separator.vue @@ -0,0 +1,3 @@ + diff --git a/resources/js/components/ui/button.ts b/resources/js/components/ui/button.ts new file mode 100644 index 0000000..c088dad --- /dev/null +++ b/resources/js/components/ui/button.ts @@ -0,0 +1 @@ +export { default as Button } from './button.vue'; diff --git a/resources/js/components/ui/button.vue b/resources/js/components/ui/button.vue new file mode 100644 index 0000000..986d58d --- /dev/null +++ b/resources/js/components/ui/button.vue @@ -0,0 +1,36 @@ + + + diff --git a/resources/js/components/ui/checkbox.vue b/resources/js/components/ui/checkbox.vue new file mode 100644 index 0000000..1788f42 --- /dev/null +++ b/resources/js/components/ui/checkbox.vue @@ -0,0 +1,22 @@ + + + diff --git a/resources/js/components/ui/input-otp/InputOTP.vue b/resources/js/components/ui/input-otp/InputOTP.vue new file mode 100644 index 0000000..3c94269 --- /dev/null +++ b/resources/js/components/ui/input-otp/InputOTP.vue @@ -0,0 +1,41 @@ + + + diff --git a/resources/js/components/ui/input-otp/InputOTPGroup.vue b/resources/js/components/ui/input-otp/InputOTPGroup.vue new file mode 100644 index 0000000..b4f57bc --- /dev/null +++ b/resources/js/components/ui/input-otp/InputOTPGroup.vue @@ -0,0 +1,5 @@ + diff --git a/resources/js/components/ui/input-otp/InputOTPSlot.vue b/resources/js/components/ui/input-otp/InputOTPSlot.vue new file mode 100644 index 0000000..a2539e3 --- /dev/null +++ b/resources/js/components/ui/input-otp/InputOTPSlot.vue @@ -0,0 +1,9 @@ + + + diff --git a/resources/js/components/ui/input-otp/index.ts b/resources/js/components/ui/input-otp/index.ts new file mode 100644 index 0000000..0a3cc96 --- /dev/null +++ b/resources/js/components/ui/input-otp/index.ts @@ -0,0 +1,3 @@ +export { default as InputOTP } from './InputOTP.vue'; +export { default as InputOTPGroup } from './InputOTPGroup.vue'; +export { default as InputOTPSlot } from './InputOTPSlot.vue'; diff --git a/resources/js/components/ui/input.ts b/resources/js/components/ui/input.ts new file mode 100644 index 0000000..bf1360e --- /dev/null +++ b/resources/js/components/ui/input.ts @@ -0,0 +1 @@ +export { default as Input } from './input.vue'; diff --git a/resources/js/components/ui/input.vue b/resources/js/components/ui/input.vue new file mode 100644 index 0000000..a64ac58 --- /dev/null +++ b/resources/js/components/ui/input.vue @@ -0,0 +1,31 @@ + + + diff --git a/resources/js/components/ui/label.ts b/resources/js/components/ui/label.ts new file mode 100644 index 0000000..9b1fa3a --- /dev/null +++ b/resources/js/components/ui/label.ts @@ -0,0 +1 @@ +export { default as Label } from './label.vue'; diff --git a/resources/js/components/ui/label.vue b/resources/js/components/ui/label.vue new file mode 100644 index 0000000..3e9472a --- /dev/null +++ b/resources/js/components/ui/label.vue @@ -0,0 +1,11 @@ + + + diff --git a/resources/js/components/ui/select.vue b/resources/js/components/ui/select.vue new file mode 100644 index 0000000..882baa0 --- /dev/null +++ b/resources/js/components/ui/select.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/resources/js/components/ui/separator.ts b/resources/js/components/ui/separator.ts new file mode 100644 index 0000000..3cb07b0 --- /dev/null +++ b/resources/js/components/ui/separator.ts @@ -0,0 +1 @@ +export { default as Separator } from './Separator.vue'; diff --git a/resources/js/components/ui/spinner.vue b/resources/js/components/ui/spinner.vue new file mode 100644 index 0000000..93e209e --- /dev/null +++ b/resources/js/components/ui/spinner.vue @@ -0,0 +1,28 @@ + + + diff --git a/resources/js/components/vault/VaultModal.vue b/resources/js/components/vault/VaultModal.vue new file mode 100644 index 0000000..c56c10f --- /dev/null +++ b/resources/js/components/vault/VaultModal.vue @@ -0,0 +1,718 @@ + + + + + + + diff --git a/resources/js/composables/useAppearance.ts b/resources/js/composables/useAppearance.ts new file mode 100644 index 0000000..0568774 --- /dev/null +++ b/resources/js/composables/useAppearance.ts @@ -0,0 +1,124 @@ +import type { ComputedRef, Ref } from 'vue'; +import { computed, onMounted, ref } from 'vue'; +import type { Appearance, ResolvedAppearance } from '@/types'; + +export type { Appearance, ResolvedAppearance }; + +export type UseAppearanceReturn = { + appearance: Ref; + resolvedAppearance: ComputedRef; + updateAppearance: (value: Appearance) => void; +}; + +export function updateTheme(value: Appearance): void { + if (typeof window === 'undefined') { + return; + } + + if (value === 'system') { + const mediaQueryList = window.matchMedia( + '(prefers-color-scheme: dark)', + ); + const systemTheme = mediaQueryList.matches ? 'dark' : 'light'; + + document.documentElement.classList.toggle( + 'dark', + systemTheme === 'dark', + ); + } else { + document.documentElement.classList.toggle('dark', value === 'dark'); + } +} + +const setCookie = (name: string, value: string, days = 365) => { + if (typeof document === 'undefined') { + return; + } + + const maxAge = days * 24 * 60 * 60; + + document.cookie = `${name}=${value};path=/;max-age=${maxAge};SameSite=Lax`; +}; + +const mediaQuery = () => { + if (typeof window === 'undefined') { + return null; + } + + return window.matchMedia('(prefers-color-scheme: dark)'); +}; + +const getStoredAppearance = () => { + if (typeof window === 'undefined') { + return null; + } + + return localStorage.getItem('appearance') as Appearance | null; +}; + +const prefersDark = (): boolean => { + if (typeof window === 'undefined') { + return false; + } + + return window.matchMedia('(prefers-color-scheme: dark)').matches; +}; + +const handleSystemThemeChange = () => { + const currentAppearance = getStoredAppearance(); + + updateTheme(currentAppearance || 'system'); +}; + +export function initializeTheme(): void { + if (typeof window === 'undefined') { + return; + } + + // Initialize theme from saved preference or default to system... + const savedAppearance = getStoredAppearance(); + updateTheme(savedAppearance || 'system'); + + // Set up system theme change listener... + mediaQuery()?.addEventListener('change', handleSystemThemeChange); +} + +const appearance = ref('system'); + +export function useAppearance(): UseAppearanceReturn { + onMounted(() => { + const savedAppearance = localStorage.getItem( + 'appearance', + ) as Appearance | null; + + if (savedAppearance) { + appearance.value = savedAppearance; + } + }); + + const resolvedAppearance = computed(() => { + if (appearance.value === 'system') { + return prefersDark() ? 'dark' : 'light'; + } + + return appearance.value; + }); + + function updateAppearance(value: Appearance) { + appearance.value = value; + + // Store in localStorage for client-side persistence... + localStorage.setItem('appearance', value); + + // Store in cookie for SSR... + setCookie('appearance', value); + + updateTheme(value); + } + + return { + appearance, + resolvedAppearance, + updateAppearance, + }; +} diff --git a/resources/js/composables/useCurrentUrl.ts b/resources/js/composables/useCurrentUrl.ts new file mode 100644 index 0000000..0736a91 --- /dev/null +++ b/resources/js/composables/useCurrentUrl.ts @@ -0,0 +1,59 @@ +import type { InertiaLinkProps } from '@inertiajs/vue3'; +import { usePage } from '@inertiajs/vue3'; +import type { ComputedRef, DeepReadonly } from 'vue'; +import { computed, readonly } from 'vue'; +import { toUrl } from '@/lib/utils'; + +export type UseCurrentUrlReturn = { + currentUrl: DeepReadonly>; + isCurrentUrl: ( + urlToCheck: NonNullable, + currentUrl?: string, + ) => boolean; + whenCurrentUrl: ( + urlToCheck: NonNullable, + ifTrue: T, + ifFalse?: F, + ) => T | F; +}; + +const page = usePage(); +const currentUrlReactive = computed( + () => new URL(page.url, window?.location.origin).pathname, +); + +export function useCurrentUrl(): UseCurrentUrlReturn { + function isCurrentUrl( + urlToCheck: NonNullable, + currentUrl?: string, + ) { + const urlToCompare = currentUrl ?? currentUrlReactive.value; + const urlString = toUrl(urlToCheck); + + if (!urlString.startsWith('http')) { + return urlString === urlToCompare; + } + + try { + const absoluteUrl = new URL(urlString); + + return absoluteUrl.pathname === urlToCompare; + } catch { + return false; + } + } + + function whenCurrentUrl( + urlToCheck: NonNullable, + ifTrue: any, + ifFalse: any = null, + ) { + return isCurrentUrl(urlToCheck) ? ifTrue : ifFalse; + } + + return { + currentUrl: readonly(currentUrlReactive), + isCurrentUrl, + whenCurrentUrl, + }; +} diff --git a/resources/js/composables/useInitials.ts b/resources/js/composables/useInitials.ts new file mode 100644 index 0000000..971e7d9 --- /dev/null +++ b/resources/js/composables/useInitials.ts @@ -0,0 +1,18 @@ +export type UseInitialsReturn = { + getInitials: (fullName?: string) => string; +}; + +export function getInitials(fullName?: string): string { + if (!fullName) return ''; + + const names = fullName.trim().split(' '); + + if (names.length === 0) return ''; + if (names.length === 1) return names[0].charAt(0).toUpperCase(); + + return `${names[0].charAt(0)}${names[names.length - 1].charAt(0)}`.toUpperCase(); +} + +export function useInitials(): UseInitialsReturn { + return { getInitials }; +} diff --git a/resources/js/composables/useNotifications.ts b/resources/js/composables/useNotifications.ts new file mode 100644 index 0000000..29de98e --- /dev/null +++ b/resources/js/composables/useNotifications.ts @@ -0,0 +1,90 @@ +import { ref } from 'vue'; + +export type ToastType = 'green' | 'magenta' | 'blue' | 'gold' | 'red'; + +export interface NotificationData { + type: ToastType; + title: string; + desc: string; + icon: string; +} + +interface Toast extends NotificationData { + id: number; + active: boolean; + flyingOut: boolean; + progress: number; +} + +interface HistoryItem extends NotificationData { + id: number; + timestamp: Date; + read: boolean; +} + +const toasts = ref([]); +const history = ref([]); +const unreadCount = ref(0); +let toastId = 0; + +export function useNotifications() { + const notify = (data: NotificationData) => { + // Add to toasts (popup) + const id = toastId++; + const toast: Toast = { id, ...data, active: false, flyingOut: false, progress: 100 }; + toasts.value.push(toast); + + // Animation logic for toast + setTimeout(() => { + const t = toasts.value.find(x => x.id === id); + if(t) t.active = true; + // Trigger lucide icons update if needed in component + }, 50); + + const interval = setInterval(() => { + const t = toasts.value.find(x => x.id === id); + if (!t) { clearInterval(interval); return; } + t.progress -= 0.5; + if (t.progress <= 0) { clearInterval(interval); closeToast(id); } + }, 40); + + // Add to history + history.value.unshift({ + id: Date.now() + Math.random(), + ...data, + timestamp: new Date(), + read: false + }); + unreadCount.value++; + }; + + const closeToast = (id: number) => { + const t = toasts.value.find(x => x.id === id); + if (t) { + t.flyingOut = true; + setTimeout(() => { + toasts.value = toasts.value.filter(x => x.id !== id); + }, 600); + } + }; + + const markAllRead = () => { + history.value.forEach(n => n.read = true); + unreadCount.value = 0; + }; + + const clearAll = () => { + history.value = []; + unreadCount.value = 0; + }; + + return { + toasts, + history, + unreadCount, + notify, + closeToast, + markAllRead, + clearAll + }; +} diff --git a/resources/js/composables/usePrimaryColor.ts b/resources/js/composables/usePrimaryColor.ts new file mode 100644 index 0000000..113e4b4 --- /dev/null +++ b/resources/js/composables/usePrimaryColor.ts @@ -0,0 +1,129 @@ +import { ref, onMounted } from 'vue'; + +export type UsePrimaryColorReturn = { + primaryColor: ReturnType>; + updatePrimaryColor: (value: string) => void; +}; + +const STORAGE_KEY = 'primaryColor'; + +function setCookie(name: string, value: string, days = 365) { + if (typeof document === 'undefined') return; + const maxAge = days * 24 * 60 * 60; + document.cookie = `${name}=${value};path=/;max-age=${maxAge};SameSite=Lax`; +} + +function getStoredPrimaryColor(): string | null { + if (typeof window === 'undefined') return null; + return localStorage.getItem(STORAGE_KEY); +} + +function parseColorToRGB(color: string): { r: number; g: number; b: number } | null { + // Supports #rgb, #rrggbb, rgb(), hsl() + color = color.trim().toLowerCase(); + + // Hex + if (color.startsWith('#')) { + let hex = color.slice(1); + if (hex.length === 3) { + hex = hex.split('').map((c) => c + c).join(''); + } + if (hex.length === 6) { + const num = parseInt(hex, 16); + return { r: (num >> 16) & 255, g: (num >> 8) & 255, b: num & 255 }; + } + } + + // rgb/rgba + if (color.startsWith('rgb')) { + const m = color.match(/rgba?\(([^)]+)\)/); + if (m) { + const [r, g, b] = m[1] + .split(',') + .slice(0, 3) + .map((v) => parseFloat(v.trim())); + return { r, g, b }; + } + } + + // hsl/hsla + if (color.startsWith('hsl')) { + const m = color.match(/hsla?\(([^)]+)\)/); + if (m) { + const [hStr, sStr, lStr] = m[1].split(',').map((v) => v.trim()); + const h = parseFloat(hStr); + const s = parseFloat(sStr) / 100; + const l = parseFloat(lStr) / 100; + // convert HSL to RGB + const c = (1 - Math.abs(2 * l - 1)) * s; + const x = c * (1 - Math.abs(((h / 60) % 2) - 1)); + const m2 = l - c / 2; + let r1 = 0, g1 = 0, b1 = 0; + if (0 <= h && h < 60) [r1, g1, b1] = [c, x, 0]; + else if (60 <= h && h < 120) [r1, g1, b1] = [x, c, 0]; + else if (120 <= h && h < 180) [r1, g1, b1] = [0, c, x]; + else if (180 <= h && h < 240) [r1, g1, b1] = [0, x, c]; + else if (240 <= h && h < 300) [r1, g1, b1] = [x, 0, c]; + else [r1, g1, b1] = [c, 0, x]; + return { + r: Math.round((r1 + m2) * 255), + g: Math.round((g1 + m2) * 255), + b: Math.round((b1 + m2) * 255), + }; + } + } + + return null; +} + +function getContrastColor(bg: string): '#000000' | '#FFFFFF' { + const rgb = parseColorToRGB(bg) || { r: 0, g: 0, b: 0 }; + // Relative luminance + const srgb = [rgb.r, rgb.g, rgb.b].map((v) => { + const c = v / 255; + return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4); + }); + const L = 0.2126 * srgb[0] + 0.7152 * srgb[1] + 0.0722 * srgb[2]; + return L > 0.179 ? '#000000' : '#FFFFFF'; +} + +export function applyPrimaryColor(value: string): void { + if (typeof document === 'undefined') return; + const root = document.documentElement; + + // Apply to global primary variables used by Tailwind theme tokens + root.style.setProperty('--primary', value); + root.style.setProperty('--primary-foreground', getContrastColor(value)); + + // Align commonly used accent variables in user layout to the same main color + root.style.setProperty('--magenta', value); + root.style.setProperty('--sidebar-primary', value); + root.style.setProperty('--sidebar-primary-foreground', getContrastColor(value)); +} + +export function initializePrimaryColor(): void { + const saved = getStoredPrimaryColor(); + if (saved) applyPrimaryColor(saved); +} + +const primaryColorRef = ref(getStoredPrimaryColor() || '#ff007a'); + +export function usePrimaryColor(): UsePrimaryColorReturn { + onMounted(() => { + const saved = getStoredPrimaryColor(); + if (saved) { + primaryColorRef.value = saved; + } + }); + + function updatePrimaryColor(value: string) { + primaryColorRef.value = value; + if (typeof window !== 'undefined') { + localStorage.setItem(STORAGE_KEY, value); + } + setCookie(STORAGE_KEY, value); + applyPrimaryColor(value); + } + + return { primaryColor: primaryColorRef, updatePrimaryColor }; +} diff --git a/resources/js/composables/useTwoFactorAuth.ts b/resources/js/composables/useTwoFactorAuth.ts new file mode 100644 index 0000000..4754606 --- /dev/null +++ b/resources/js/composables/useTwoFactorAuth.ts @@ -0,0 +1,120 @@ +import type { ComputedRef, Ref } from 'vue'; +import { computed, ref } from 'vue'; +import { qrCode, recoveryCodes, secretKey } from '@/routes/two-factor'; + +export type UseTwoFactorAuthReturn = { + qrCodeSvg: Ref; + manualSetupKey: Ref; + recoveryCodesList: Ref; + errors: Ref; + hasSetupData: ComputedRef; + clearSetupData: () => void; + clearErrors: () => void; + clearTwoFactorAuthData: () => void; + fetchQrCode: () => Promise; + fetchSetupKey: () => Promise; + fetchSetupData: () => Promise; + fetchRecoveryCodes: () => Promise; +}; + +const fetchJson = async (url: string): Promise => { + const response = await fetch(url, { + headers: { Accept: 'application/json' }, + }); + + if (!response.ok) { + throw new Error(`Failed to fetch: ${response.status}`); + } + + return response.json(); +}; + +const errors = ref([]); +const manualSetupKey = ref(null); +const qrCodeSvg = ref(null); +const recoveryCodesList = ref([]); + +const hasSetupData = computed( + () => qrCodeSvg.value !== null && manualSetupKey.value !== null, +); + +export const useTwoFactorAuth = (): UseTwoFactorAuthReturn => { + const fetchQrCode = async (): Promise => { + try { + const { svg } = await fetchJson<{ svg: string; url: string }>( + qrCode.url(), + ); + + qrCodeSvg.value = svg; + } catch { + errors.value.push('Failed to fetch QR code'); + qrCodeSvg.value = null; + } + }; + + const fetchSetupKey = async (): Promise => { + try { + const { secretKey: key } = await fetchJson<{ secretKey: string }>( + secretKey.url(), + ); + + manualSetupKey.value = key; + } catch { + errors.value.push('Failed to fetch a setup key'); + manualSetupKey.value = null; + } + }; + + const clearSetupData = (): void => { + manualSetupKey.value = null; + qrCodeSvg.value = null; + clearErrors(); + }; + + const clearErrors = (): void => { + errors.value = []; + }; + + const clearTwoFactorAuthData = (): void => { + clearSetupData(); + clearErrors(); + recoveryCodesList.value = []; + }; + + const fetchRecoveryCodes = async (): Promise => { + try { + clearErrors(); + recoveryCodesList.value = await fetchJson( + recoveryCodes.url(), + ); + } catch { + errors.value.push('Failed to fetch recovery codes'); + recoveryCodesList.value = []; + } + }; + + const fetchSetupData = async (): Promise => { + try { + clearErrors(); + await Promise.all([fetchQrCode(), fetchSetupKey()]); + } catch { + qrCodeSvg.value = null; + manualSetupKey.value = null; + } + }; + + return { + qrCodeSvg, + manualSetupKey, + recoveryCodesList, + errors, + hasSetupData, + clearSetupData, + clearErrors, + clearTwoFactorAuthData, + fetchQrCode, + fetchSetupKey, + fetchSetupData, + fetchRecoveryCodes, + }; +}; diff --git a/resources/js/composables/useVault.ts b/resources/js/composables/useVault.ts new file mode 100644 index 0000000..3a40c53 --- /dev/null +++ b/resources/js/composables/useVault.ts @@ -0,0 +1,180 @@ +import { ref } from 'vue'; +import { csrfFetch } from '@/utils/csrfFetch'; + +export interface VaultBalances { + balance: string; + vault_balance: string; + vault_balances: Record; + currency: string | null; +} + +export function useVault() { + const loading = ref(false); + const error = ref(null); + const balances = ref(null); + const transfers = ref(null); + const pinRequired = ref(false); + const lockedUntil = ref(null); + + const sessionPin = ref(null); + + async function load(perPage = 10) { + loading.value = true; + error.value = null; + try { + const resp = await fetch(`/api/wallet/vault?per_page=${perPage}`, { + headers: { 'Accept': 'application/json' } + }); + if (!resp.ok) throw new Error(`HTTP ${resp.status}`); + const json = await resp.json(); + balances.value = { + balance: json.balance, + vault_balance: json.vault_balance, + vault_balances: json.vault_balances ?? { BTX: json.vault_balance }, + currency: json.currency, + }; + transfers.value = json.transfers; + } catch (e: any) { + error.value = e?.message || 'Failed to load vault'; + } finally { + loading.value = false; + } + } + + function uuidv4(): string { + if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) { + return (crypto as any).randomUUID(); + } + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + const r = (Math.random() * 16) | 0; + const v = c === 'x' ? r : (r & 0x3) | 0x8; + return v.toString(16); + }); + } + + async function deposit(amount: string, currency = 'BTX', pin?: string) { + return doAction('/api/wallet/vault/deposit', amount, currency, pin); + } + + async function withdraw(amount: string, currency = 'BTX', pin?: string) { + return doAction('/api/wallet/vault/withdraw', amount, currency, pin); + } + + async function doAction(url: string, amount: string, currency: string, pin?: string) { + loading.value = true; + error.value = null; + pinRequired.value = false; + lockedUntil.value = null; + + try { + const usePin = pin ?? sessionPin.value; + if (!usePin) { + pinRequired.value = true; + throw new Error('PIN required'); + } + + const body = { amount, currency, pin: usePin, idempotency_key: uuidv4() }; + const resp = await csrfFetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, + body: JSON.stringify(body), + }); + + if (resp.status === 423) { + const j = await resp.json().catch(() => ({})); + pinRequired.value = true; + lockedUntil.value = j?.locked_until || null; + throw new Error(j?.message || 'PIN required'); + } + + if (!resp.ok) { + const j = await resp.json().catch(() => ({})); + throw new Error(j?.message || `HTTP ${resp.status}`); + } + + const json = await resp.json(); + + if (json?.balances) { + balances.value = { + balance: json.balances.balance, + vault_balance: json.balances.vault_balance, + vault_balances: json.balances.vault_balances ?? { BTX: json.balances.vault_balance }, + currency: balances.value?.currency || 'BTX', + }; + } else { + await load(); + } + return json; + } catch (e: any) { + error.value = e?.message || 'Vault action failed'; + throw e; + } finally { + loading.value = false; + } + } + + async function verifyPin(pin: string) { + error.value = null; + try { + const resp = await csrfFetch('/api/wallet/vault/pin/verify', { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, + body: JSON.stringify({ pin }), + }); + + if (!resp.ok) { + const j = await resp.json().catch(() => ({})); + if (resp.status === 423) lockedUntil.value = j?.locked_until || null; + throw new Error(j?.message || `HTTP ${resp.status}`); + } + + sessionPin.value = pin; + pinRequired.value = false; + lockedUntil.value = null; + + return await resp.json(); + } catch (e: any) { + error.value = e?.message || 'PIN verification failed'; + throw e; + } + } + + function clearSessionPin() { + sessionPin.value = null; + } + + async function setPin(pin: string, current_pin?: string) { + error.value = null; + const payload: any = { pin }; + if (current_pin) payload.current_pin = current_pin; + + const resp = await csrfFetch('/api/wallet/vault/pin/set', { + method: 'POST', + headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, + body: JSON.stringify(payload), + }); + + if (!resp.ok) { + const j = await resp.json().catch(() => ({})); + throw new Error(j?.message || `HTTP ${resp.status}`); + } + + sessionPin.value = pin; + return await resp.json(); + } + + return { + loading, + error, + balances, + transfers, + pinRequired, + lockedUntil, + load, + deposit, + withdraw, + verifyPin, + setPin, + clearSessionPin, + }; +} diff --git a/resources/js/i18n/de.json b/resources/js/i18n/de.json new file mode 100644 index 0000000..7ab2d9e --- /dev/null +++ b/resources/js/i18n/de.json @@ -0,0 +1,162 @@ +{ + "nav": { + "lobby": "Lobby", + "liveCasino": "Live Casino", + "slots": "Slots", + "wallet": "Wallet", + "bonuses": "Boni", + "vip": "VIP Club", + "guilds": "Gilden", + "responsible": "Verantwortung", + "faq": "FAQ", + "settings": "Einstellungen", + "public_profile": "Öffentliches Profil", + "account_settings": "Kontoeinstellungen" + }, + "topbar": { + "welcome_guest": "Willkommen bei BetiX", + "welcome_user": "Willkommen zurück, {name}", + "vault": "Vault", + "search": "Suche", + "deposit": "Einzahlen" + }, + "footer": { + "legal": { + "terms": "Allgemeine Geschäftsbedingungen", + "cookies": "Cookie-Richtlinie", + "privacy": "Datenschutz", + "bonusPolicy": "Bonus-Richtlinie", + "disputes": "Streitbeilegung", + "responsible": "Verantwortungsvolles Spielen", + "aml": "AML-Richtlinie", + "risks": "Risikohinweise" + } + }, + "wallet": { "title": "Wallet", "subtitle": "Verwalte dein Guthaben, Ein- und Auszahlungen." }, + "vault": { + "title": "Vault", + "toVault": "In Vault", + "fromVault": "Aus Vault", + "amount": { "placeholder": "z. B. 6200 oder 6200.0000" } + }, + "bonuses": { + "title": "Aktionen", + "tabs": { "available": "Verfügbar", "active": "Aktiv", "history": "Historie" }, + "featured": "FEATURED", + "minDeposit": "Min. Einzahlung", + "claim": "Jetzt sichern", + "activate": "Aktivieren", + "errorLoad": "Boni konnten nicht geladen werden", + "noActive": "Keine aktiven Boni. Prüfe die verfügbaren Angebote!", + "noHistory": "Noch keine Historie.", + "ended": "Beendet", + "wagerProgress": "Umsatzfortschritt", + "wageredOf": "{wagered} / {total} umgesetzt", + "bonus": "Bonus" + }, + "notif": { "success": "Erfolg", "error": "Fehler" }, + "sections": { + "gaming": "Gaming", + "user": "Benutzer", + "supportInfo": "Support & Info" + }, + "policy": { + "toc": "Inhalt", + "lastUpdated": "Zuletzt aktualisiert" + }, + "common": { + "guest": "Gast", + "loginToPlay": "Bitte einloggen zum Spielen" + }, + "auth": { + "login": "Einloggen", + "register": "Registrieren", + "logout": "Abmelden" + }, + "search": { + "placeholder": "Spiele, @Nutzer, Anbieter suchen...", + "slots": "Spiele", + "users": "Spieler", + "providers": "Anbieter", + "noResults": "Keine Ergebnisse gefunden", + "hint": "@ für Nutzer, Anbietername für Provider" + }, + "notifications": { + "title": "Benachrichtigungen", + "clearAll": "Alle löschen", + "empty": "Noch keine Benachrichtigungen", + "markRead": "Alle als gelesen markieren" + }, + "dashboard": { + "welcome_offer": "Willkommensangebot", + "claim_now": "Jetzt sichern", + "read_terms": "AGB lesen", + "search_placeholder": "Spiele suchen...", + "originals": "Originals", + "loading": "Lädt...", + "reload": "Erneut laden", + "no_games": "Keine Spiele gefunden.", + "no_slots": "Keine Slots gefunden.", + "no_live": "Keine Live-Spiele gefunden.", + "popular_slots": "Beliebte Slots", + "show_all": "Alle anzeigen", + "live_wins": "Live-Gewinne", + "live_feed": "Live-Feed", + "won_in": "gewonnen in", + "new_releases": "Neuerscheinungen", + "live_casino": "Live Casino", + "hall_of_fame": "Ruhmeshalle", + "recently_played": "Zuletzt gespielt", + "error_loading": "Fehler beim Laden der Spiele." + }, + "vip": { + "your_rank": "DEIN RANG", + "max": "MAX", + "unlocked": "Freigeschaltet", + "current": "Aktuell", + "locked": "Gesperrt", + "benefits": "Vorteile", + "requires": "Benötigt", + "reward_claimed": "Belohnung erhalten" + }, + "bonus": { + "promo_title": "Promo Code einlösen", + "promo_placeholder": "Code eingeben (z. B. WELCOME10)", + "promo_redeem": "Einlösen", + "promo_redeeming": "Wird eingelöst…", + "unlock_at": "Freischaltung bei" + }, + "guilds": { + "title": "Gilden", + "subtitle_new": "Erstelle eine Gilde oder tritt einer mit einem Einladungscode bei.", + "subtitle_member": "Verwalte deine Gilde, lade Mitglieder ein und sieh deine Mitgliederliste.", + "tab_active": "Aktiv", + "create": "Gilde erstellen", + "join": "Gilde beitreten", + "name": "Name", + "tag": "Kürzel", + "logo_url": "Logo-URL", + "description": "Beschreibung", + "invite_code": "Einladungscode", + "appearance": "Erscheinungsbild", + "members": "Mitglieder", + "sorted_by_wager": "Sortiert nach Einsatz", + "wagered": "Eingesetzt", + "kick": "Mitglied entfernen", + "leave": "Gilde verlassen", + "top_title": "Bestenliste", + "top_desc": "Die am höchsten bewerteten Gilden nach Punkten. Top-Gilden erhalten Boni.", + "rank": "Rang", + "guild": "Gilde", + "points": "Punkte", + "no_guilds": "Noch keine Gilden gefunden.", + "members_col": "Mitglieder" + }, + "trophy": { + "title": "Trophäenraum", + "title_user": "Trophäenraum von {name}", + "achievements": "Errungenschaften", + "unlocked_label": "Freigeschaltet", + "locked_label": "Gesperrt" + } +} diff --git a/resources/js/i18n/en.json b/resources/js/i18n/en.json new file mode 100644 index 0000000..35df4a6 --- /dev/null +++ b/resources/js/i18n/en.json @@ -0,0 +1,162 @@ +{ + "nav": { + "lobby": "Lobby", + "liveCasino": "Live Casino", + "slots": "Slots", + "wallet": "Wallet", + "bonuses": "Bonuses", + "vip": "VIP Club", + "guilds": "Guilds", + "responsible": "Responsible", + "faq": "FAQ", + "settings": "Settings", + "public_profile": "Public Profile", + "account_settings": "Account Settings" + }, + "topbar": { + "welcome_guest": "Welcome to BetiX", + "welcome_user": "Welcome back, {name}", + "vault": "Vault", + "search": "Search", + "deposit": "Deposit" + }, + "footer": { + "legal": { + "terms": "Terms and Conditions", + "cookies": "Cookie Policy", + "privacy": "Privacy Policy", + "bonusPolicy": "Bonus Policy", + "disputes": "Dispute Resolution Policy", + "responsible": "Responsible Gaming", + "aml": "AML Policy", + "risks": "Risk Warnings" + } + }, + "wallet": { "title": "Wallet", "subtitle": "Manage your funds, deposit, and withdraw." }, + "vault": { + "title": "Vault", + "toVault": "To Vault", + "fromVault": "From Vault", + "amount": { "placeholder": "e.g. 6200 or 6200.0000" } + }, + "bonuses": { + "title": "Promotions", + "tabs": { "available": "Available", "active": "Active", "history": "History" }, + "featured": "FEATURED", + "minDeposit": "Min. Deposit", + "claim": "Claim Now", + "activate": "Activate", + "errorLoad": "Failed to load bonuses", + "noActive": "No active bonuses. Check available offers!", + "noHistory": "No history yet.", + "ended": "Ended", + "wagerProgress": "Wager Progress", + "wageredOf": "{wagered} / {total} wagered", + "bonus": "Bonus" + }, + "notif": { "success": "Success", "error": "Error", "dropdown": { "title": "Notifications", "clearAll": "Clear All", "empty": "No notifications yet" } }, + "sections": { + "gaming": "Gaming", + "user": "User", + "supportInfo": "Support & Info" + }, + "policy": { + "toc": "Contents", + "lastUpdated": "Last updated" + }, + "common": { + "guest": "Guest", + "loginToPlay": "Please login to play" + }, + "auth": { + "login": "Log In", + "register": "Sign Up", + "logout": "Logout" + }, + "search": { + "placeholder": "Search games, @users, providers...", + "slots": "Games", + "users": "Players", + "providers": "Providers", + "noResults": "No results found", + "hint": "Type @ to search users, type provider name to filter" + }, + "notifications": { + "title": "Notifications", + "clearAll": "Clear All", + "empty": "No notifications yet", + "markRead": "Mark all read" + }, + "dashboard": { + "welcome_offer": "Welcome Offer", + "claim_now": "Claim Now", + "read_terms": "Read Terms", + "search_placeholder": "Search games...", + "originals": "Originals", + "loading": "Loading...", + "reload": "Retry", + "no_games": "No games found.", + "no_slots": "No slots found.", + "no_live": "No live games found.", + "popular_slots": "Popular Slots", + "show_all": "Show All", + "live_wins": "Live Wins", + "live_feed": "Live Feed", + "won_in": "won in", + "new_releases": "New Releases", + "live_casino": "Live Casino", + "hall_of_fame": "Hall of Fame", + "recently_played": "Recently Played", + "error_loading": "Error loading games." + }, + "vip": { + "your_rank": "YOUR RANK", + "max": "MAX", + "unlocked": "Unlocked", + "current": "Current", + "locked": "Locked", + "benefits": "Benefits", + "requires": "Requires", + "reward_claimed": "Reward Claimed" + }, + "bonus": { + "promo_title": "Redeem Promo Code", + "promo_placeholder": "Enter your code (e.g. WELCOME10)", + "promo_redeem": "Redeem", + "promo_redeeming": "Redeeming...", + "unlock_at": "Unlock at" + }, + "guilds": { + "title": "Guilds", + "subtitle_new": "Create a guild or join one with an invite code.", + "subtitle_member": "Manage your guild, invite members, and see your roster.", + "tab_active": "Active", + "create": "Create a Guild", + "join": "Join a Guild", + "name": "Name", + "tag": "Tag", + "logo_url": "Logo URL", + "description": "Description", + "invite_code": "Invite code", + "appearance": "Appearance", + "members": "Members", + "sorted_by_wager": "Sorted by Wager", + "wagered": "Wagered", + "kick": "Kick Member", + "leave": "Leave Guild", + "top_title": "Leaderboard", + "top_desc": "Highest ranked guilds by points. Top guilds receive bonuses.", + "rank": "Rank", + "guild": "Guild", + "points": "Points", + "no_guilds": "No guilds found yet.", + "members_col": "Members" + }, + "trophy": { + "title": "Trophy Room", + "title_user": "{name}'s Trophy Room", + "achievements": "Achievements", + "unlocked_label": "Unlocked", + "locked_label": "Locked" + } +} diff --git a/resources/js/i18n/es.json b/resources/js/i18n/es.json new file mode 100644 index 0000000..e2ae0aa --- /dev/null +++ b/resources/js/i18n/es.json @@ -0,0 +1,68 @@ +{ + "nav": { + "lobby": "Lobby", + "liveCasino": "Casino en Vivo", + "slots": "Tragamonedas", + "wallet": "Billetera", + "bonuses": "Bonos", + "vip": "Club VIP", + "guilds": "Gremios", + "responsible": "Juego responsable", + "faq": "FAQ" + }, + "topbar": { + "welcome_guest": "Bienvenido a BetiX", + "welcome_user": "Bienvenido de nuevo, {name}", + "vault": "Vault", + "search": "Buscar", + "deposit": "Depositar" + }, + "footer": { + "legal": { + "terms": "Términos y Condiciones", + "cookies": "Política de Cookies", + "privacy": "Política de Privacidad", + "bonusPolicy": "Política de Bonos", + "disputes": "Resolución de Disputas", + "responsible": "Juego Responsable", + "aml": "Política AML", + "risks": "Advertencias de Riesgo" + } + }, + "wallet": { "title": "Billetera", "subtitle": "Administra tus fondos, depósitos y retiros." }, + "vault": { + "title": "Vault", + "toVault": "A Vault", + "fromVault": "Desde Vault", + "amount": { "placeholder": "p. ej., 6200 o 6200.0000" } + }, + "bonuses": { + "title": "Promociones", + "tabs": { "available": "Disponibles", "active": "Activos", "history": "Historial" }, + "featured": "DESTACADO", + "minDeposit": "Depósito mín.", + "claim": "Reclamar", + "activate": "Activar", + "errorLoad": "No se pudieron cargar los bonos", + "noActive": "No hay bonos activos. ¡Revisa las ofertas disponibles!", + "noHistory": "Aún no hay historial.", + "ended": "Finalizado", + "wagerProgress": "Progreso de apuesta", + "wageredOf": "{wagered} / {total} apostado", + "bonus": "Bono" + }, + "notif": { "success": "Éxito", "error": "Error", "dropdown": { "title": "Notificaciones", "clearAll": "Borrar todo", "empty": "Aún no hay notificaciones" } }, + "sections": { + "gaming": "Juegos", + "user": "Usuario", + "supportInfo": "Soporte e Información" + }, + "policy": { + "toc": "Contenido", + "lastUpdated": "Última actualización" + }, + "common": { + "guest": "Invitado", + "loginToPlay": "Inicia sesión para jugar" + } +} diff --git a/resources/js/i18n/index.ts b/resources/js/i18n/index.ts new file mode 100644 index 0000000..032fdc2 --- /dev/null +++ b/resources/js/i18n/index.ts @@ -0,0 +1,43 @@ +import { createI18n } from 'vue-i18n'; + +// Start with English synchronously to avoid FOUC; other locales lazy-loaded +async function loadLocaleMessages(locale: string) { + const norm = locale.replace('-', '_'); + switch (norm) { + case 'de': + return (await import('./de.json')).default; + case 'es': + return (await import('./es.json')).default; + case 'pt_BR': + case 'pt-br': + return (await import('./pt_BR.json')).default; + case 'tr': + return (await import('./tr.json')).default; + case 'pl': + return (await import('./pl.json')).default; + default: + return (await import('./en.json')).default; + } +} + +export const i18n = createI18n({ + legacy: false, + locale: 'en', + fallbackLocale: 'en', + messages: {}, +}); + +export async function initI18n(startLocale: string) { + const msgs = await loadLocaleMessages(startLocale || 'en'); + i18n.global.setLocaleMessage(startLocale || 'en', msgs); + i18n.global.locale.value = startLocale || 'en'; +} + +export async function setLocale(locale: string) { + const norm = locale.replace('-', '_'); + if (!i18n.global.getLocaleMessage(norm) || Object.keys(i18n.global.getLocaleMessage(norm)).length === 0) { + const msgs = await loadLocaleMessages(norm); + i18n.global.setLocaleMessage(norm, msgs); + } + i18n.global.locale.value = norm; +} diff --git a/resources/js/i18n/pl.json b/resources/js/i18n/pl.json new file mode 100644 index 0000000..a01eef5 --- /dev/null +++ b/resources/js/i18n/pl.json @@ -0,0 +1,40 @@ +{ + "nav": { + "lobby": "Lobby", + "liveCasino": "Kasyno na żywo", + "slots": "Sloty", + "wallet": "Portfel", + "bonuses": "Bonusy", + "vip": "Klub VIP", + "guilds": "Gildie", + "responsible": "Odpowiedzialna gra", + "faq": "FAQ" + }, + "topbar": { + "welcome_guest": "Witamy w BetiX", + "welcome_user": "Witamy ponownie, {name}", + "vault": "Sejf", + "search": "Szukaj", + "deposit": "Wpłata" + }, + "footer": { + "legal": { + "terms": "Regulamin", + "cookies": "Polityka Cookies", + "privacy": "Polityka Prywatności", + "bonusPolicy": "Polityka Bonusów", + "disputes": "Polityka Rozwiązywania Sporów", + "responsible": "Odpowiedzialna Gra", + "aml": "Polityka AML", + "risks": "Ostrzeżenia o Ryzyku" + } + }, + "wallet": { "title": "Portfel", "subtitle": "Zarządzaj środkami, wpłatami i wypłatami." }, + "vault": { + "title": "Sejf", + "toVault": "Do sejfu", + "fromVault": "Z sejfu", + "amount": { "placeholder": "np. 6200 lub 6200.0000" } + }, + "notif": { "success": "Sukces", "error": "Błąd" } +} diff --git a/resources/js/i18n/pt_BR.json b/resources/js/i18n/pt_BR.json new file mode 100644 index 0000000..425cb78 --- /dev/null +++ b/resources/js/i18n/pt_BR.json @@ -0,0 +1,55 @@ +{ + "nav": { + "lobby": "Lobby", + "liveCasino": "Cassino ao Vivo", + "slots": "Caça‑níqueis", + "wallet": "Carteira", + "bonuses": "Bônus", + "vip": "Clube VIP", + "guilds": "Guildas", + "responsible": "Jogo responsável", + "faq": "FAQ" + }, + "topbar": { + "welcome_guest": "Bem-vindo à BetiX", + "welcome_user": "Bem-vindo de volta, {name}", + "vault": "Cofre", + "search": "Buscar", + "deposit": "Depositar" + }, + "footer": { + "legal": { + "terms": "Termos e Condições", + "cookies": "Política de Cookies", + "privacy": "Política de Privacidade", + "bonusPolicy": "Política de Bônus", + "disputes": "Política de Resolução de Disputas", + "responsible": "Jogo Responsável", + "aml": "Política AML", + "risks": "Avisos de Risco" + } + }, + "wallet": { "title": "Carteira", "subtitle": "Gerencie seus fundos, depósitos e saques." }, + "vault": { + "title": "Cofre", + "toVault": "Para o Cofre", + "fromVault": "Do Cofre", + "amount": { "placeholder": "ex.: 6200 ou 6200.0000" } + }, + "bonuses": { + "title": "Promoções", + "tabs": { "available": "Disponíveis", "active": "Ativos", "history": "Histórico" }, + "featured": "DESTAQUE", + "minDeposit": "Depósito mín.", + "claim": "Resgatar", + "activate": "Ativar", + "errorLoad": "Falha ao carregar os bônus", + "noActive": "Não há bônus ativos. Veja as ofertas disponíveis!", + "noHistory": "Ainda sem histórico.", + "ended": "Encerrado", + "wagerProgress": "Progresso de aposta", + "wageredOf": "{wagered} / {total} apostado", + "bonus": "Bônus" + }, + "notif": { "success": "Sucesso", "error": "Erro" } +} diff --git a/resources/js/i18n/tr.json b/resources/js/i18n/tr.json new file mode 100644 index 0000000..5db0e74 --- /dev/null +++ b/resources/js/i18n/tr.json @@ -0,0 +1,55 @@ +{ + "nav": { + "lobby": "Lobi", + "liveCasino": "Canlı Casino", + "slots": "Slotlar", + "wallet": "Cüzdan", + "bonuses": "Bonuslar", + "vip": "VIP Kulübü", + "guilds": "Loncalar", + "responsible": "Sorumlu Oyun", + "faq": "SSS" + }, + "topbar": { + "welcome_guest": "BetiX'e Hoş Geldiniz", + "welcome_user": "Tekrar hoş geldin, {name}", + "vault": "Kasa", + "search": "Ara", + "deposit": "Yatır" + }, + "footer": { + "legal": { + "terms": "Şartlar ve Koşullar", + "cookies": "Çerez Politikası", + "privacy": "Gizlilik Politikası", + "bonusPolicy": "Bonus Politikası", + "disputes": "Uyuşmazlık Çözümü Politikası", + "responsible": "Sorumlu Oyun", + "aml": "AML Politikası", + "risks": "Risk Uyarıları" + } + }, + "wallet": { "title": "Cüzdan", "subtitle": "Bakiyeni yönet, para yatır ve çek." }, + "vault": { + "title": "Kasa", + "toVault": "Kasaya", + "fromVault": "Kasadan", + "amount": { "placeholder": "örn. 6200 veya 6200.0000" } + }, + "bonuses": { + "title": "Promosyonlar", + "tabs": { "available": "Mevcut", "active": "Aktif", "history": "Geçmiş" }, + "featured": "ÖNE ÇIKAN", + "minDeposit": "Min. Yatırım", + "claim": "Hemen Al", + "activate": "Aktifleştir", + "errorLoad": "Bonuslar yüklenemedi", + "noActive": "Aktif bonus yok. Mevcut tekliflere göz at!", + "noHistory": "Henüz geçmiş yok.", + "ended": "Bitti", + "wagerProgress": "Çevrim İlerleme", + "wageredOf": "{wagered} / {total} çevrildi", + "bonus": "Bonus" + }, + "notif": { "success": "Başarılı", "error": "Hata" } +} diff --git a/resources/js/layouts/AppLayout.vue b/resources/js/layouts/AppLayout.vue new file mode 100644 index 0000000..b4423a0 --- /dev/null +++ b/resources/js/layouts/AppLayout.vue @@ -0,0 +1,18 @@ + + + diff --git a/resources/js/layouts/AuthLayout.vue b/resources/js/layouts/AuthLayout.vue new file mode 100644 index 0000000..4b09455 --- /dev/null +++ b/resources/js/layouts/AuthLayout.vue @@ -0,0 +1,14 @@ + + + diff --git a/resources/js/layouts/admin/AdminLayout.vue b/resources/js/layouts/admin/AdminLayout.vue new file mode 100644 index 0000000..8eed470 --- /dev/null +++ b/resources/js/layouts/admin/AdminLayout.vue @@ -0,0 +1,88 @@ + + + + + diff --git a/resources/js/layouts/admin/CasinoAdminLayout.vue b/resources/js/layouts/admin/CasinoAdminLayout.vue new file mode 100644 index 0000000..843aebc --- /dev/null +++ b/resources/js/layouts/admin/CasinoAdminLayout.vue @@ -0,0 +1,729 @@ + + + + + diff --git a/resources/js/layouts/app/AppHeaderLayout.vue b/resources/js/layouts/app/AppHeaderLayout.vue new file mode 100644 index 0000000..a109f03 --- /dev/null +++ b/resources/js/layouts/app/AppHeaderLayout.vue @@ -0,0 +1,23 @@ + + + diff --git a/resources/js/layouts/app/AppSidebarLayout.vue b/resources/js/layouts/app/AppSidebarLayout.vue new file mode 100644 index 0000000..d9de9bf --- /dev/null +++ b/resources/js/layouts/app/AppSidebarLayout.vue @@ -0,0 +1,25 @@ + + + diff --git a/resources/js/layouts/auth/AuthCardLayout.vue b/resources/js/layouts/auth/AuthCardLayout.vue new file mode 100644 index 0000000..57afbb5 --- /dev/null +++ b/resources/js/layouts/auth/AuthCardLayout.vue @@ -0,0 +1,50 @@ + + + diff --git a/resources/js/layouts/auth/AuthSimpleLayout.vue b/resources/js/layouts/auth/AuthSimpleLayout.vue new file mode 100644 index 0000000..db6f154 --- /dev/null +++ b/resources/js/layouts/auth/AuthSimpleLayout.vue @@ -0,0 +1,40 @@ + + + diff --git a/resources/js/layouts/auth/AuthSplitLayout.vue b/resources/js/layouts/auth/AuthSplitLayout.vue new file mode 100644 index 0000000..2d21dfc --- /dev/null +++ b/resources/js/layouts/auth/AuthSplitLayout.vue @@ -0,0 +1,47 @@ + + + diff --git a/resources/js/layouts/settings/Layout.vue b/resources/js/layouts/settings/Layout.vue new file mode 100644 index 0000000..9f00c1a --- /dev/null +++ b/resources/js/layouts/settings/Layout.vue @@ -0,0 +1,76 @@ + + + diff --git a/resources/js/layouts/user/userlayout.vue b/resources/js/layouts/user/userlayout.vue new file mode 100644 index 0000000..8e303a0 --- /dev/null +++ b/resources/js/layouts/user/userlayout.vue @@ -0,0 +1,1507 @@ + + + + + diff --git a/resources/js/lib/utils.ts b/resources/js/lib/utils.ts new file mode 100644 index 0000000..e2f6ca8 --- /dev/null +++ b/resources/js/lib/utils.ts @@ -0,0 +1,11 @@ +import type { InertiaLinkProps } from '@inertiajs/vue3'; +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +export function toUrl(href: NonNullable) { + return typeof href === 'string' ? href : href?.url; +} diff --git a/resources/js/pages/Admin/CasinoDashboard.vue b/resources/js/pages/Admin/CasinoDashboard.vue new file mode 100644 index 0000000..e85a6d1 --- /dev/null +++ b/resources/js/pages/Admin/CasinoDashboard.vue @@ -0,0 +1,502 @@ + + + + + diff --git a/resources/js/pages/Admin/Chat.vue b/resources/js/pages/Admin/Chat.vue new file mode 100644 index 0000000..a2273bf --- /dev/null +++ b/resources/js/pages/Admin/Chat.vue @@ -0,0 +1,110 @@ + + + + + \ No newline at end of file diff --git a/resources/js/pages/Admin/ChatReportShow.vue b/resources/js/pages/Admin/ChatReportShow.vue new file mode 100644 index 0000000..f7dcffc --- /dev/null +++ b/resources/js/pages/Admin/ChatReportShow.vue @@ -0,0 +1,761 @@ + + + + + diff --git a/resources/js/pages/Admin/ChatReports.vue b/resources/js/pages/Admin/ChatReports.vue new file mode 100644 index 0000000..acc7e31 --- /dev/null +++ b/resources/js/pages/Admin/ChatReports.vue @@ -0,0 +1,354 @@ + + + + + diff --git a/resources/js/pages/Admin/Dashboard.vue b/resources/js/pages/Admin/Dashboard.vue new file mode 100644 index 0000000..70faea6 --- /dev/null +++ b/resources/js/pages/Admin/Dashboard.vue @@ -0,0 +1,155 @@ + + + diff --git a/resources/js/pages/Admin/GeoBlock.vue b/resources/js/pages/Admin/GeoBlock.vue new file mode 100644 index 0000000..4809802 --- /dev/null +++ b/resources/js/pages/Admin/GeoBlock.vue @@ -0,0 +1,440 @@ + + + + + diff --git a/resources/js/pages/Admin/PaymentsSettings.vue b/resources/js/pages/Admin/PaymentsSettings.vue new file mode 100644 index 0000000..f35aaee --- /dev/null +++ b/resources/js/pages/Admin/PaymentsSettings.vue @@ -0,0 +1,297 @@ + + + + + diff --git a/resources/js/pages/Admin/ProfileReportShow.vue b/resources/js/pages/Admin/ProfileReportShow.vue new file mode 100644 index 0000000..27cd3c3 --- /dev/null +++ b/resources/js/pages/Admin/ProfileReportShow.vue @@ -0,0 +1,1007 @@ + + + + + diff --git a/resources/js/pages/Admin/ProfileReports.vue b/resources/js/pages/Admin/ProfileReports.vue new file mode 100644 index 0000000..7267201 --- /dev/null +++ b/resources/js/pages/Admin/ProfileReports.vue @@ -0,0 +1,359 @@ + + + + + diff --git a/resources/js/pages/Admin/Promos.vue b/resources/js/pages/Admin/Promos.vue new file mode 100644 index 0000000..558d860 --- /dev/null +++ b/resources/js/pages/Admin/Promos.vue @@ -0,0 +1,137 @@ + + + diff --git a/resources/js/pages/Admin/SiteSettings.vue b/resources/js/pages/Admin/SiteSettings.vue new file mode 100644 index 0000000..f972d10 --- /dev/null +++ b/resources/js/pages/Admin/SiteSettings.vue @@ -0,0 +1,281 @@ + + + + + diff --git a/resources/js/pages/Admin/Support.vue b/resources/js/pages/Admin/Support.vue new file mode 100644 index 0000000..deffcf2 --- /dev/null +++ b/resources/js/pages/Admin/Support.vue @@ -0,0 +1,632 @@ + + + + + diff --git a/resources/js/pages/Admin/UserShow.vue b/resources/js/pages/Admin/UserShow.vue new file mode 100644 index 0000000..67b1116 --- /dev/null +++ b/resources/js/pages/Admin/UserShow.vue @@ -0,0 +1,420 @@ + + + + + diff --git a/resources/js/pages/Admin/Users.vue b/resources/js/pages/Admin/Users.vue new file mode 100644 index 0000000..def31fb --- /dev/null +++ b/resources/js/pages/Admin/Users.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/resources/js/pages/Admin/WalletsSettings.vue b/resources/js/pages/Admin/WalletsSettings.vue new file mode 100644 index 0000000..9362ec2 --- /dev/null +++ b/resources/js/pages/Admin/WalletsSettings.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/resources/js/pages/Bonus.vue b/resources/js/pages/Bonus.vue new file mode 100644 index 0000000..7944145 --- /dev/null +++ b/resources/js/pages/Bonus.vue @@ -0,0 +1,415 @@ + + + + + diff --git a/resources/js/pages/Dashboard.vue b/resources/js/pages/Dashboard.vue new file mode 100644 index 0000000..29a7a20 --- /dev/null +++ b/resources/js/pages/Dashboard.vue @@ -0,0 +1,1189 @@ + + + + + diff --git a/resources/js/pages/Errors/Banned.vue b/resources/js/pages/Errors/Banned.vue new file mode 100644 index 0000000..287b0a0 --- /dev/null +++ b/resources/js/pages/Errors/Banned.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/resources/js/pages/Faq.vue b/resources/js/pages/Faq.vue new file mode 100644 index 0000000..59346ce --- /dev/null +++ b/resources/js/pages/Faq.vue @@ -0,0 +1,120 @@ + + + + + diff --git a/resources/js/pages/GamePlay.vue b/resources/js/pages/GamePlay.vue new file mode 100644 index 0000000..e5f03c5 --- /dev/null +++ b/resources/js/pages/GamePlay.vue @@ -0,0 +1,1560 @@ + + + + + diff --git a/resources/js/pages/GeoBlocked.vue b/resources/js/pages/GeoBlocked.vue new file mode 100644 index 0000000..d3a0072 --- /dev/null +++ b/resources/js/pages/GeoBlocked.vue @@ -0,0 +1,154 @@ + + + + + diff --git a/resources/js/pages/Maintenance.vue b/resources/js/pages/Maintenance.vue new file mode 100644 index 0000000..0e7f5f3 --- /dev/null +++ b/resources/js/pages/Maintenance.vue @@ -0,0 +1,30 @@ + + + + + diff --git a/resources/js/pages/Social/Hub.vue b/resources/js/pages/Social/Hub.vue new file mode 100644 index 0000000..e327f50 --- /dev/null +++ b/resources/js/pages/Social/Hub.vue @@ -0,0 +1,1472 @@ + + +