commit d61b3651ba83bf034b36edc176b986b2995bf121
Author: ZKRA000 <44751014+ZKRA000@users.noreply.github.com>
Date: Tue Feb 17 17:44:40 2026 +0700
init
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..8f0de65
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,18 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 4
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[*.{yml,yaml}]
+indent_size = 2
+
+[docker-compose.yml]
+indent_size = 4
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..bba5574
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,61 @@
+APP_NAME=Laravel
+APP_ENV=local
+APP_KEY=
+APP_DEBUG=true
+APP_URL=http://localhost
+CORS_ALLOWED_ORIGINS=http://localhost:5173,http://127.0.0.1:5173
+CORS_SUPPORTS_CREDENTIALS=false
+
+LOG_CHANNEL=stack
+LOG_DEPRECATIONS_CHANNEL=null
+LOG_LEVEL=debug
+
+DB_CONNECTION=pgsql
+DB_HOST=127.0.0.1
+DB_PORT=5432
+DB_DATABASE=pxg_2026
+DB_USERNAME=root
+DB_PASSWORD=
+
+BROADCAST_DRIVER=log
+CACHE_DRIVER=file
+FILESYSTEM_DISK=local
+QUEUE_CONNECTION=sync
+SESSION_DRIVER=file
+SESSION_LIFETIME=120
+
+MEMCACHED_HOST=127.0.0.1
+
+REDIS_HOST=127.0.0.1
+REDIS_PASSWORD=null
+REDIS_PORT=6379
+
+MAIL_MAILER=smtp
+MAIL_HOST=mailpit
+MAIL_PORT=1025
+MAIL_USERNAME=null
+MAIL_PASSWORD=null
+MAIL_ENCRYPTION=null
+MAIL_FROM_ADDRESS="hello@example.com"
+MAIL_FROM_NAME="${APP_NAME}"
+
+AWS_ACCESS_KEY_ID=
+AWS_SECRET_ACCESS_KEY=
+AWS_DEFAULT_REGION=us-east-1
+AWS_BUCKET=
+AWS_USE_PATH_STYLE_ENDPOINT=false
+
+PUSHER_APP_ID=
+PUSHER_APP_KEY=
+PUSHER_APP_SECRET=
+PUSHER_HOST=
+PUSHER_PORT=443
+PUSHER_SCHEME=https
+PUSHER_APP_CLUSTER=mt1
+
+VITE_APP_NAME="${APP_NAME}"
+VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
+VITE_PUSHER_HOST="${PUSHER_HOST}"
+VITE_PUSHER_PORT="${PUSHER_PORT}"
+VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
+VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..fcb21d3
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,11 @@
+* text=auto eol=lf
+
+*.blade.php diff=html
+*.css diff=css
+*.html diff=html
+*.md diff=markdown
+*.php diff=php
+
+/.github export-ignore
+CHANGELOG.md export-ignore
+.styleci.yml export-ignore
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7fe978f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,19 @@
+/.phpunit.cache
+/node_modules
+/public/build
+/public/hot
+/public/storage
+/storage/*.key
+/vendor
+.env
+.env.backup
+.env.production
+.phpunit.result.cache
+Homestead.json
+Homestead.yaml
+auth.json
+npm-debug.log
+yarn-error.log
+/.fleet
+/.idea
+/.vscode
diff --git a/INSTALL_NOTES.md b/INSTALL_NOTES.md
new file mode 100644
index 0000000..a555957
--- /dev/null
+++ b/INSTALL_NOTES.md
@@ -0,0 +1,43 @@
+## Integration notes
+
+1) Copy files into an existing Laravel project.
+2) Add `config/pxg.php`.
+3) Register `App\Providers\PxgServiceProvider` in `config/app.php` providers array (Laravel 10) if not auto-discovered.
+
+4) Register command:
+- Ensure `app/Console/Kernel.php` loads commands folder (default).
+- Command: `php artisan pxg:seed-flights --event=1`
+
+5) CORS:
+- Ensure your Laravel CORS allows the Vue origin (e.g., localhost:5173).
+
+
+7) Scheduler pairing (recommended):
+- Add Laravel scheduler cron on server:
+ * * * * * php /path/to/artisan schedule:run >> /dev/null 2>&1
+- Scheduler will run `pxg:pairing-tick` every minute.
+- You can also run manually: `php artisan pxg:pairing-tick --event=1`
+
+
+8) Pairing heuristics (v3):
+- Semi-hard course preference: tries preferred course first (A/B) and only falls back if preferred course has no seats.
+- Hole bottleneck mitigation: distributes HIGH/BEGINNER across start holes.
+- Tune via config/pxg.php -> pairing.*
+
+
+9) Admin auth (Sanctum):
+- Register middleware alias in `app/Http/Kernel.php`:
+ 'role' => \App\Http\Middleware\EnsureAdminRole::class
+
+- Install Sanctum if not present: `composer require laravel/sanctum` then `php artisan sanctum:install`
+- Ensure `api` middleware includes `EnsureFrontendRequestsAreStateful` only if you need cookie auth; this project uses bearer tokens.
+- Seed admin user: `php artisan db:seed --class=AdminUserSeeder`
+
+
+10) Exports (Excel friendly CSV):
+- Start sheet: GET /api/v1/admin/events/{event}/exports/start-sheet.csv
+- Pairing list: GET /api/v1/admin/events/{event}/exports/pairing-list.csv
+
+11) Flight ops:
+- Lock/unlock flights for sponsor/VIP so pairing engine won't use them.
+- Manual assign/move/remove members via admin endpoints.
diff --git a/MIDDLEWARE_NOTE.md b/MIDDLEWARE_NOTE.md
new file mode 100644
index 0000000..55c536a
--- /dev/null
+++ b/MIDDLEWARE_NOTE.md
@@ -0,0 +1,2 @@
+Add to app/Http/Kernel.php $routeMiddleware:
+'role' => \App\Http\Middleware\EnsureAdminRole::class,
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8f0098d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,66 @@
+# TOURNAMENT PXG 2026 — Laravel API Skeleton (matches Vue frontend contract)
+
+This is a **Laravel API skeleton** you can copy into an existing Laravel 10/11 project.
+It matches the frontend contract used by the Vue project:
+
+## Endpoints (v1)
+- `GET /api/v1/events/{event}`
+- `POST /api/v1/events/{event}/registrations`
+- `POST /api/v1/events/{event}/groups`
+- `GET /api/v1/groups/{code}`
+- `POST /api/v1/groups/{code}/join`
+- `POST /api/v1/registrations/{registration}/pay`
+- `POST /api/v1/pairing/lookup`
+- `POST /api/v1/pairing/otp/request`
+- `POST /api/v1/pairing/otp/verify`
+- `GET /api/v1/pairing/me` (Bearer pairing_token)
+- `POST /api/v1/webhooks/xendit`
+
+## Environment variables (.env)
+Add:
+- `XENDIT_API_KEY=...`
+- `XENDIT_WEBHOOK_TOKEN=...` (the token you set in Xendit dashboard)
+- `WA_OTP_DRIVER=log` (default) or your provider driver name
+- `PAIRING_TOKEN_TTL_MINUTES=30`
+- `OTP_TTL_MINUTES=5`
+
+## WhatsApp OTP
+This skeleton provides an interface `OtpSenderInterface` + default driver `LogOtpSender` which writes OTP to logs.
+Replace it with your WhatsApp Business API provider integration.
+
+## Xendit
+This skeleton includes a minimal `XenditService` stub.
+Replace `createInvoice()` with real Xendit Invoice API call and map the webhook payload accordingly.
+
+## Installation approach
+1. Create a fresh Laravel project (or use existing).
+2. Copy the `app/`, `routes/`, and `database/migrations/` from this skeleton into your project.
+3. Run migrations:
+ ```bash
+ php artisan migrate
+ ```
+4. Seed flights:
+ ```bash
+ php artisan pxg:seed-flights --event=1
+ ```
+5. Configure `.env` and run:
+ ```bash
+ php artisan serve
+ ```
+
+## Notes
+- Pairing token is an **encrypted bearer token** (not Sanctum), issued after OTP verification.
+- OTP uses hashing + rate-limits (basic) + attempt limits.
+- Pairing assignment logic is **NOT implemented** here (you can plug your pairing job/service later).
+ The API returns pairing info only if a registration has been assigned to a flight via `flight_members`.
+
+
+## Admin Dashboard (API)
+- Login: `POST /api/v1/admin/login` with email+password -> returns Sanctum token
+- Default seeded admin:
+ - email: `admin@pxg.local`
+ - password: `pxg2026admin`
+Run:
+```bash
+php artisan db:seed --class=AdminUserSeeder
+```
diff --git a/app/Console/Commands/PairingTick.php b/app/Console/Commands/PairingTick.php
new file mode 100644
index 0000000..22f82d4
--- /dev/null
+++ b/app/Console/Commands/PairingTick.php
@@ -0,0 +1,52 @@
+option('event');
+
+ $events = Event::query();
+ if ($eventOpt) {
+ $events->where('id', (int)$eventOpt);
+ } else {
+ // default: only events that are active/open (best effort)
+ $events->where(function($q){
+ $q->whereNull('registration_close_at')
+ ->orWhere('registration_close_at', '>=', now()->subDays(1));
+ });
+ }
+
+ $ids = $events->pluck('id')->all();
+ $dispatched = 0;
+
+ foreach ($ids as $eventId) {
+ $pending = Registration::where('event_id', $eventId)
+ ->where('status', 'CONFIRMED')
+ ->whereDoesntHave('flightMember')
+ ->count();
+
+ if ($pending > 0) {
+ dispatch(new AssignPairingJob((int)$eventId));
+ $dispatched++;
+ $this->info("Dispatched pairing job for event {$eventId} (pending {$pending})");
+ }
+ }
+
+ if ($dispatched === 0) {
+ $this->line('No events require pairing.');
+ }
+
+ return 0;
+ }
+}
diff --git a/app/Console/Commands/RunPairing.php b/app/Console/Commands/RunPairing.php
new file mode 100644
index 0000000..acb98e7
--- /dev/null
+++ b/app/Console/Commands/RunPairing.php
@@ -0,0 +1,23 @@
+option('event');
+ $res = $engine->run($eventId);
+ $this->info('Assigned: '.$res['assigned']);
+ foreach (($res['notes'] ?? []) as $n) {
+ $this->line('- '.$n);
+ }
+ return 0;
+ }
+}
diff --git a/app/Console/Commands/SeedFlights.php b/app/Console/Commands/SeedFlights.php
new file mode 100644
index 0000000..b27ee7c
--- /dev/null
+++ b/app/Console/Commands/SeedFlights.php
@@ -0,0 +1,50 @@
+option('event');
+ $event = Event::find($eventId);
+ if (!$event) {
+ $this->error('Event not found: '.$eventId);
+ return 1;
+ }
+
+ // Create A01..A36 and B01..B36
+ $this->info('Seeding flights for event '.$eventId);
+
+ $courses = ['A','B'];
+ foreach ($courses as $course) {
+ $idx = 1;
+ for ($hole=1; $hole<=18; $hole++) {
+ foreach (['A','B'] as $tee) {
+ $code = $course . str_pad((string)$idx, 2, '0', STR_PAD_LEFT);
+ Flight::updateOrCreate(
+ ['event_id' => $eventId, 'code' => $code],
+ [
+ 'course' => $course,
+ 'start_hole' => $hole,
+ 'start_tee' => $tee,
+ 'type' => 'NORMAL',
+ 'status' => 'OPEN',
+ ]
+ );
+ $idx++;
+ }
+ }
+ }
+
+ $this->info('Done.');
+ return 0;
+ }
+}
diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php
new file mode 100644
index 0000000..4194376
--- /dev/null
+++ b/app/Console/Kernel.php
@@ -0,0 +1,21 @@
+command('pxg:pairing-tick')->everyMinute();
+ }
+
+ protected function commands(): void
+ {
+ $this->load(__DIR__.'/Commands');
+ require base_path('routes/console.php');
+ }
+}
diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php
new file mode 100644
index 0000000..56af264
--- /dev/null
+++ b/app/Exceptions/Handler.php
@@ -0,0 +1,30 @@
+
+ */
+ protected $dontFlash = [
+ 'current_password',
+ 'password',
+ 'password_confirmation',
+ ];
+
+ /**
+ * Register the exception handling callbacks for the application.
+ */
+ public function register(): void
+ {
+ $this->reportable(function (Throwable $e) {
+ //
+ });
+ }
+}
diff --git a/app/Http/Controllers/Api/V1/Admin/AuditController.php b/app/Http/Controllers/Api/V1/Admin/AuditController.php
new file mode 100644
index 0000000..9af81bd
--- /dev/null
+++ b/app/Http/Controllers/Api/V1/Admin/AuditController.php
@@ -0,0 +1,29 @@
+id);
+ if ($request->filled('action')) $q->where('action', $request->string('action'));
+ $rows = $q->orderByDesc('id')->limit(500)->get()->map(function($a){
+ return [
+ 'id' => $a->id,
+ 'action' => $a->action,
+ 'entity_type' => $a->entity_type,
+ 'entity_id' => $a->entity_id,
+ 'actor_user_id' => $a->actor_user_id,
+ 'meta' => $a->meta,
+ 'created_at' => $a->created_at->toIso8601String(),
+ ];
+ });
+ return response()->json(['data'=>$rows]);
+ }
+}
diff --git a/app/Http/Controllers/Api/V1/Admin/AuthController.php b/app/Http/Controllers/Api/V1/Admin/AuthController.php
new file mode 100644
index 0000000..eba3222
--- /dev/null
+++ b/app/Http/Controllers/Api/V1/Admin/AuthController.php
@@ -0,0 +1,41 @@
+validate([
+ 'email' => ['required','email'],
+ 'password' => ['required','string'],
+ ]);
+
+ $user = User::where('email', $data['email'])->first();
+ if (!$user || !Hash::check($data['password'], $user->password)) {
+ return response()->json(['message' => 'Invalid credentials'], 401);
+ }
+
+ $token = $user->createToken('pxg-admin')->plainTextToken;
+
+ return response()->json([
+ 'token' => $token,
+ 'user' => [
+ 'id' => $user->id,
+ 'name' => $user->name,
+ 'email' => $user->email,
+ ],
+ ]);
+ }
+
+ public function logout(Request $request)
+ {
+ $request->user()->currentAccessToken()->delete();
+ return response()->json(['ok' => true]);
+ }
+}
diff --git a/app/Http/Controllers/Api/V1/Admin/BatchPrintController.php b/app/Http/Controllers/Api/V1/Admin/BatchPrintController.php
new file mode 100644
index 0000000..7ea5694
--- /dev/null
+++ b/app/Http/Controllers/Api/V1/Admin/BatchPrintController.php
@@ -0,0 +1,141 @@
+query('ids');
+ $checkedIn = $request->boolean('checked_in');
+
+ $q = Registration::where('event_id',$event->id)->with(['player','flightMember.flight']);
+
+ if ($checkedIn) {
+ $regIds = Checkin::where('event_id',$event->id)->whereNotNull('checked_in_at')->pluck('registration_id')->all();
+ $q->whereIn('id', $regIds);
+ } elseif ($ids) {
+ $idArr = array_filter(array_map('intval', explode(',', (string)$ids)));
+ $q->whereIn('id', $idArr);
+ } else {
+ return response('Missing ids or checked_in=1', 422);
+ }
+
+ $regs = $q->orderBy('id')->get();
+ return response($this->badgesHtml($event->name, $regs))->header('Content-Type','text/html');
+ }
+
+ public function scorecards(Event $event, Request $request)
+ {
+ $course = strtoupper((string)$request->query('course',''));
+ $q = Flight::where('event_id',$event->id)->with(['members.registration.player'])->orderBy('course')->orderBy('code');
+ if (in_array($course, ['A','B'], true)) $q->where('course',$course);
+ $flights = $q->get();
+ return response($this->scorecardsHtml($event->name, $flights))->header('Content-Type','text/html');
+ }
+
+ private function badgesHtml(string $eventName, $regs): string
+ {
+ $eventName = e($eventName);
+ $cards = '';
+ foreach ($regs as $r) {
+ $name = e($r->player?->name ?? '');
+ $code = e($r->registration_code);
+ $flight = $r->flightMember?->flight;
+ $flightText = $flight ? "Flight {$flight->code} • Course {$flight->course} • Hole {$flight->start_hole}{$flight->start_tee}" : "Flight: —";
+ $flightText = e($flightText);
+
+ $cards .= "
+
+
{$name}
+
{$code}
+
{$flightText}
+
";
+ }
+
+ return "
+
+
+";
+ }
+
+ private function scorecardsHtml(string $eventName, $flights): string
+ {
+ $eventName = e($eventName);
+ $cards = '';
+
+ foreach ($flights as $f) {
+ $flightCode = e($f->code);
+ $course = e($f->course);
+ $hole = e((string)$f->start_hole);
+ $tee = e($f->start_tee);
+
+ $rows = '';
+ foreach ($f->members->sortBy('seat_no') as $m) {
+ $seat = (int)$m->seat_no;
+ $name = e($m->registration?->player?->name ?? '');
+ $band = e((string)($m->registration?->handicap_band ?? '—'));
+ $hcp = e((string)($m->registration?->handicap_value ?? '—'));
+ $rows .= "{$seat} {$name} {$band} {$hcp} ";
+ }
+
+ $cards .= "
+
+
+
{$eventName}
+
Scorecard • Flight {$flightCode}
+
+
Course {$course} • Start Hole {$hole}{$tee}
+
+
+ Seat Player Band HCP
+ {$rows}
+
+
Scoring: Stableford Nett • Shotgun Start
+
";
+ }
+
+ return "
+
+
+";
+ }
+}
diff --git a/app/Http/Controllers/Api/V1/Admin/CheckinController.php b/app/Http/Controllers/Api/V1/Admin/CheckinController.php
new file mode 100644
index 0000000..a951cfb
--- /dev/null
+++ b/app/Http/Controllers/Api/V1/Admin/CheckinController.php
@@ -0,0 +1,140 @@
+validate([
+ 'code' => ['required','string','max:40'],
+ ]);
+
+ $reg = Registration::where('event_id',$event->id)
+ ->where('registration_code', $data['code'])
+ ->with(['player','flightMember.flight'])
+ ->first();
+
+ if (!$reg) return response()->json(['message' => 'Registration not found'], 404);
+
+ $checkin = Checkin::where('event_id',$event->id)->where('registration_id',$reg->id)->first();
+
+ return response()->json([
+ 'registration' => [
+ 'id' => $reg->id,
+ 'registration_code' => $reg->registration_code,
+ 'status' => $reg->status,
+ 'player' => [
+ 'name' => $reg->player?->name,
+ 'phone' => $reg->player?->phone,
+ 'email' => $reg->player?->email,
+ ],
+ 'flight' => $reg->flightMember?->flight ? [
+ 'code' => $reg->flightMember->flight->code,
+ 'course' => $reg->flightMember->flight->course,
+ 'start_hole' => (int)$reg->flightMember->flight->start_hole,
+ 'start_tee' => $reg->flightMember->flight->start_tee,
+ ] : null
+ ],
+ 'checkin' => $checkin ? [
+ 'checked_in_at' => optional($checkin->checked_in_at)->toIso8601String(),
+ 'method' => $checkin->method,
+ ] : null
+ ]);
+ }
+
+ public function checkin(Event $event, Request $request, AuditLogger $audit)
+ {
+ $data = $request->validate([
+ 'registration_id' => ['required','integer','exists:registrations,id'],
+ 'method' => ['nullable','in:manual,qr'],
+ ]);
+
+ $reg = Registration::where('event_id',$event->id)->findOrFail((int)$data['registration_id']);
+
+ $checkin = Checkin::updateOrCreate(
+ ['event_id' => $event->id, 'registration_id' => $reg->id],
+ [
+ 'checked_in_at' => now(),
+ 'checked_in_by' => $request->user()?->id,
+ 'method' => $data['method'] ?? 'manual',
+ ]
+ );
+
+ $audit->log($event->id, $request->user()?->id, 'checkin', 'registration', $reg->id, [
+ 'registration_code' => $reg->registration_code,
+ 'method' => $checkin->method
+ ]);
+
+ return response()->json(['ok' => true, 'checked_in_at' => $checkin->checked_in_at->toIso8601String()]);
+ }
+
+ public function undo(Event $event, Request $request, AuditLogger $audit)
+ {
+ $data = $request->validate([
+ 'registration_id' => ['required','integer','exists:registrations,id'],
+ ]);
+
+ $reg = Registration::where('event_id',$event->id)->findOrFail((int)$data['registration_id']);
+ $checkin = Checkin::where('event_id',$event->id)->where('registration_id',$reg->id)->first();
+ if ($checkin) $checkin->delete();
+
+ $audit->log($event->id, $request->user()?->id, 'checkin_undo', 'registration', $reg->id, [
+ 'registration_code' => $reg->registration_code
+ ]);
+
+ return response()->json(['ok' => true]);
+ }
+
+public function bulkImport(Event $event, Request $request, AuditLogger $audit)
+{
+ $request->validate([
+ 'file' => ['required','file','mimes:csv,txt','max:5120'],
+ 'method' => ['nullable','in:manual,qr'],
+ ]);
+
+ $content = file_get_contents($request->file('file')->getRealPath());
+ $lines = preg_split("/\r\n|\n|\r/", trim($content));
+ if (count($lines) < 1) return response()->json(['message'=>'Empty file'], 422);
+
+ $first = trim($lines[0]);
+ $hasHeader = str_contains(strtolower($first), 'registration_code') || str_contains(strtolower($first), 'code');
+ if ($hasHeader) array_shift($lines);
+
+ $method = $request->input('method','manual');
+ $ok = 0; $skipped = 0; $errors = [];
+
+ foreach ($lines as $i => $line) {
+ $line = trim($line);
+ if ($line === '') continue;
+ $cols = str_getcsv($line);
+ $code = trim($cols[0] ?? '');
+ if ($code === '') { $skipped++; continue; }
+
+ $reg = \App\Models\Registration::where('event_id',$event->id)->where('registration_code',$code)->first();
+ if (!$reg) { $errors[] = ['row' => ($hasHeader? $i+2 : $i+1), 'message' => "not found: {$code}"]; $skipped++; continue; }
+
+ \App\Models\Checkin::updateOrCreate(
+ ['event_id' => $event->id, 'registration_id' => $reg->id],
+ ['checked_in_at' => now(), 'checked_in_by' => $request->user()?->id, 'method' => $method]
+ );
+
+ $audit->log($event->id, $request->user()?->id, 'checkin_bulk', 'registration', $reg->id, [
+ 'registration_code' => $reg->registration_code,
+ 'method' => $method
+ ]);
+
+ $ok++;
+ }
+
+ return response()->json(['ok'=>true,'checked_in'=>$ok,'skipped'=>$skipped,'errors'=>$errors]);
+}
+
+}
diff --git a/app/Http/Controllers/Api/V1/Admin/DashboardController.php b/app/Http/Controllers/Api/V1/Admin/DashboardController.php
new file mode 100644
index 0000000..e848ad0
--- /dev/null
+++ b/app/Http/Controllers/Api/V1/Admin/DashboardController.php
@@ -0,0 +1,103 @@
+id)->count();
+ $confirmed = Registration::where('event_id', $event->id)->where('status','CONFIRMED')->count();
+ $pendingPairing = Registration::where('event_id', $event->id)->where('status','CONFIRMED')->whereDoesntHave('flightMember')->count();
+ $flightsFull = Flight::where('event_id', $event->id)->where('status','FULL')->count();
+
+ $courseLoadRows = DB::table('flight_members')
+ ->join('flights','flight_members.flight_id','=','flights.id')
+ ->where('flights.event_id',$event->id)
+ ->select('flights.course', DB::raw('count(*) as cnt'))
+ ->groupBy('flights.course')
+ ->get();
+
+ $courseLoad = ['A'=>0,'B'=>0];
+ foreach ($courseLoadRows as $r) $courseLoad[$r->course] = (int)$r->cnt;
+
+ return response()->json([
+ 'event' => [
+ 'id' => $event->id,
+ 'name' => $event->name,
+ ],
+ 'kpi' => [
+ 'registered' => $registered,
+ 'confirmed' => $confirmed,
+ 'pending_pairing' => $pendingPairing,
+ 'flights_full' => $flightsFull,
+ ],
+ 'course_load' => $courseLoad,
+ ]);
+ }
+
+ public function exportStartSheet(Event $event): StreamedResponse
+ {
+ $filename = "start-sheet-event-{$event->id}.csv";
+
+ $flights = Flight::where('event_id', $event->id)
+ ->orderBy('course')->orderBy('code')
+ ->get();
+
+ $callback = function() use ($flights) {
+ $out = fopen('php://output', 'w');
+ fputcsv($out, ['Course','Flight','Start Hole','Tee','Type','Status','Member Count']);
+ foreach ($flights as $f) {
+ $cnt = DB::table('flight_members')->where('flight_id',$f->id)->count();
+ fputcsv($out, [$f->course,$f->code,$f->start_hole,$f->start_tee,$f->type,$f->status,$cnt]);
+ }
+ fclose($out);
+ };
+
+ return response()->streamDownload($callback, $filename, ['Content-Type' => 'text/csv']);
+ }
+
+ public function exportPairingList(Event $event): StreamedResponse
+ {
+ $filename = "pairing-list-event-{$event->id}.csv";
+
+ $rows = Registration::where('event_id', $event->id)
+ ->with(['player','flightMember.flight'])
+ ->orderBy('id')
+ ->get();
+
+ $callback = function() use ($rows) {
+ $out = fopen('php://output', 'w');
+ fputcsv($out, ['Reg ID','Reg Code','Name','Phone','Email','Status','Band','HCP','Pairing Mode','Course Pref','Flight','Course Assigned','Start Hole','Tee']);
+ foreach ($rows as $r) {
+ $f = $r->flightMember?->flight;
+ fputcsv($out, [
+ $r->id,
+ $r->registration_code,
+ $r->player?->name,
+ $r->player?->phone,
+ $r->player?->email,
+ $r->status,
+ $r->handicap_band,
+ $r->handicap_value,
+ $r->pairing_mode,
+ $r->course_pref,
+ $f?->code,
+ $f?->course,
+ $f?->start_hole,
+ $f?->start_tee,
+ ]);
+ }
+ fclose($out);
+ };
+
+ return response()->streamDownload($callback, $filename, ['Content-Type' => 'text/csv']);
+ }
+}
diff --git a/app/Http/Controllers/Api/V1/Admin/EventAdminController.php b/app/Http/Controllers/Api/V1/Admin/EventAdminController.php
new file mode 100644
index 0000000..4001232
--- /dev/null
+++ b/app/Http/Controllers/Api/V1/Admin/EventAdminController.php
@@ -0,0 +1,36 @@
+pairing_finalized_at = now();
+ $event->save();
+ return response()->json(['ok' => true, 'pairing_finalized_at' => $event->pairing_finalized_at->toIso8601String()]);
+ }
+
+ public function publish(Event $event)
+ {
+ if (!$event->pairing_finalized_at) {
+ return response()->json(['message' => 'Finalize pairing first'], 409);
+ }
+ $event->pairing_published_at = now();
+ $event->save();
+ return response()->json(['ok' => true, 'pairing_published_at' => $event->pairing_published_at->toIso8601String()]);
+ }
+
+ public function unfinalize(Event $event)
+ {
+ $event->pairing_finalized_at = null;
+ $event->pairing_published_at = null;
+ $event->save();
+ return response()->json(['ok' => true]);
+ }
+}
diff --git a/app/Http/Controllers/Api/V1/Admin/FlightsController.php b/app/Http/Controllers/Api/V1/Admin/FlightsController.php
new file mode 100644
index 0000000..48a3868
--- /dev/null
+++ b/app/Http/Controllers/Api/V1/Admin/FlightsController.php
@@ -0,0 +1,205 @@
+id)
+ ->orderBy('course')->orderBy('code')
+ ->get()
+ ->map(function($f){
+ $cnt = DB::table('flight_members')->where('flight_id', $f->id)->count();
+ return [
+ 'id' => $f->id,
+ 'course' => $f->course,
+ 'code' => $f->code,
+ 'start_hole' => (int)$f->start_hole,
+ 'start_tee' => $f->start_tee,
+ 'type' => $f->type,
+ 'status' => $f->status,
+ 'member_count' => (int)$cnt,
+ ];
+ });
+
+ return response()->json(['data' => $rows]);
+ }
+
+ public function show(Flight $flight)
+ {
+ $flight->load(['members.registration.player']);
+ $members = $flight->members->map(function($m){
+ return [
+ 'flight_member_id' => $m->id,
+ 'seat_no' => (int)$m->seat_no,
+ 'registration_id' => $m->registration_id,
+ 'registration_code' => $m->registration?->registration_code,
+ 'name' => $m->registration?->player?->name,
+ 'band' => $m->registration?->handicap_band,
+ 'hcp' => $m->registration?->handicap_value,
+ ];
+ })->sortBy('seat_no')->values();
+
+ return response()->json([
+ 'flight' => [
+ 'id' => $flight->id,
+ 'event_id' => $flight->event_id,
+ 'course' => $flight->course,
+ 'code' => $flight->code,
+ 'start_hole' => (int)$flight->start_hole,
+ 'start_tee' => $flight->start_tee,
+ 'type' => $flight->type,
+ 'status' => $flight->status,
+ ],
+ 'members' => $members,
+ ]);
+ }
+
+ public function assign(Flight $flight, Request $request, AuditLogger $audit)
+ {
+ $data = $request->validate([
+ 'registration_id' => ['nullable','integer','exists:registrations,id'],
+ 'registration_code' => ['nullable','string','max:40'],
+ 'seat_no' => ['required','integer','in:1,2,3,4'],
+ ]);
+
+ $reg = null;
+ if (!empty($data['registration_id'])) {
+ $reg = Registration::findOrFail((int)$data['registration_id']);
+ } else if (!empty($data['registration_code'])) {
+ $reg = Registration::where('registration_code', $data['registration_code'])->firstOrFail();
+ } else {
+ return response()->json(['message' => 'registration_id or registration_code required'], 422);
+ }
+
+ if ($reg->event_id !== $flight->event_id) {
+ return response()->json(['message' => 'Registration not in this event'], 422);
+ }
+
+ if (FlightMember::where('registration_id', $reg->id)->exists()) {
+ return response()->json(['message' => 'Registration already assigned to a flight'], 409);
+ }
+
+ if (FlightMember::where('flight_id', $flight->id)->where('seat_no', (int)$data['seat_no'])->exists()) {
+ return response()->json(['message' => 'Seat already taken'], 409);
+ }
+
+ FlightMember::create([
+ 'flight_id' => $flight->id,
+ 'registration_id' => $reg->id,
+ 'seat_no' => (int)$data['seat_no'],
+ 'lock_flag' => false,
+ ]);
+
+ $this->refreshFlightStatus($flight->id);
+
+ $audit->log($flight->event_id, $request->user()?->id, 'flight_member_assign', 'flight', $flight->id, [
+ 'registration_id' => $reg->id,
+ 'registration_code' => $reg->registration_code,
+ 'seat_no' => (int)$data['seat_no'],
+ ]);
+
+ return response()->json(['ok' => true]);
+ }
+
+ public function moveMember(FlightMember $flightMember, Request $request, AuditLogger $audit)
+ {
+ $event = Event::find($flightMember->flight->event_id);
+ if ($event && $event->pairing_finalized_at) {
+ return response()->json(['message' => 'Pairing finalized. Use swap request workflow.'], 409);
+ }
+
+ $data = $request->validate([
+ 'to_flight_id' => ['required','integer','exists:flights,id'],
+ 'to_seat_no' => ['required','integer','in:1,2,3,4'],
+ ]);
+
+ $toFlight = Flight::findOrFail((int)$data['to_flight_id']);
+
+ if ($toFlight->event_id !== $flightMember->flight->event_id) {
+ return response()->json(['message' => 'Target flight is not in same event'], 422);
+ }
+
+ if (FlightMember::where('flight_id', $toFlight->id)->where('seat_no', (int)$data['to_seat_no'])->exists()) {
+ return response()->json(['message' => 'Target seat already taken'], 409);
+ }
+
+ $fromFlightId = $flightMember->flight_id;
+
+ DB::transaction(function() use ($flightMember, $toFlight, $data){
+ $flightMember->flight_id = $toFlight->id;
+ $flightMember->seat_no = (int)$data['to_seat_no'];
+ $flightMember->save();
+ });
+
+ $this->refreshFlightStatus($fromFlightId);
+ $this->refreshFlightStatus($toFlight->id);
+
+ $audit->log($toFlight->event_id, $request->user()?->id, 'flight_member_move', 'flight_member', $flightMember->id, [
+ 'from_flight_id' => $fromFlightId,
+ 'to_flight_id' => $toFlight->id,
+ 'to_seat_no' => (int)$data['to_seat_no'],
+ ]);
+
+ return response()->json(['ok' => true]);
+ }
+
+ public function removeMember(FlightMember $flightMember, Request $request, AuditLogger $audit)
+ {
+ $event = Event::find($flightMember->flight->event_id);
+ if ($event && $event->pairing_finalized_at) {
+ return response()->json(['message' => 'Pairing finalized. Use swap request workflow.'], 409);
+ }
+
+ $flightId = $flightMember->flight_id;
+ $regId = $flightMember->registration_id;
+
+ $flightMember->delete();
+ $this->refreshFlightStatus($flightId);
+
+ $audit->log(Flight::find($flightId)?->event_id, $request->user()?->id, 'flight_member_remove', 'flight', $flightId, [
+ 'registration_id' => $regId,
+ ]);
+
+ return response()->json(['ok' => true]);
+ }
+
+ public function lock(Flight $flight, Request $request, AuditLogger $audit)
+ {
+ $flight->type = 'SPONSOR_LOCKED';
+ $flight->status = 'LOCKED';
+ $flight->save();
+
+ $audit->log($flight->event_id, $request->user()?->id, 'flight_lock', 'flight', $flight->id);
+
+ return response()->json(['ok' => true]);
+ }
+
+ public function unlock(Flight $flight, Request $request, AuditLogger $audit)
+ {
+ $flight->type = 'NORMAL';
+ $cnt = DB::table('flight_members')->where('flight_id', $flight->id)->count();
+ $flight->status = $cnt >= 4 ? 'FULL' : 'OPEN';
+ $flight->save();
+
+ $audit->log($flight->event_id, $request->user()?->id, 'flight_unlock', 'flight', $flight->id);
+
+ return response()->json(['ok' => true]);
+ }
+
+ private function refreshFlightStatus(int $flightId): void
+ {
+ $count = DB::table('flight_members')->where('flight_id', $flightId)->count();
+ Flight::where('id', $flightId)->update(['status' => $count >= 4 ? 'FULL' : 'OPEN']);
+ }
+}
diff --git a/app/Http/Controllers/Api/V1/Admin/ImportController.php b/app/Http/Controllers/Api/V1/Admin/ImportController.php
new file mode 100644
index 0000000..2c5c8ab
--- /dev/null
+++ b/app/Http/Controllers/Api/V1/Admin/ImportController.php
@@ -0,0 +1,117 @@
+validate([
+ 'file' => ['required','file','mimes:csv,txt','max:5120'],
+ ]);
+
+ $content = file_get_contents($request->file('file')->getRealPath());
+ $lines = preg_split("/\r\n|\n|\r/", trim($content));
+ if (count($lines) < 2) return response()->json(['message' => 'CSV must include header + at least 1 row'], 422);
+
+ $header = str_getcsv(array_shift($lines));
+ $header = array_map(fn($h)=> strtolower(trim($h)), $header);
+
+ $required = ['name','phone','email','handicap_type','handicap_band','pairing_mode','course_pref'];
+ foreach ($required as $r) {
+ if (!in_array($r, $header, true)) {
+ return response()->json(['message' => "Missing required column: {$r}"], 422);
+ }
+ }
+
+ $idx = array_flip($header);
+
+ $created = 0;
+ $skipped = 0;
+ $errors = [];
+
+ DB::transaction(function() use ($lines, $idx, $event, $codeService, &$created, &$skipped, &$errors){
+ foreach ($lines as $i => $line) {
+ if (trim($line) === '') continue;
+ $row = str_getcsv($line);
+
+ $get = function($key) use ($row, $idx){
+ return isset($idx[$key]) ? trim((string)($row[$idx[$key]] ?? '')) : '';
+ };
+
+ $name = $get('name');
+ $phone = $get('phone');
+ $email = $get('email');
+
+ if ($name==='' || $phone==='' || $email==='') {
+ $errors[] = ['row' => $i+2, 'message' => 'name/phone/email required'];
+ $skipped++; continue;
+ }
+
+ // avoid duplicate by email+event (simple)
+ $exists = Registration::where('event_id',$event->id)->whereHas('player', fn($q)=> $q->where('email',$email))->exists();
+ if ($exists) { $skipped++; continue; }
+
+ $handicapType = strtoupper($get('handicap_type'));
+ $handicapBand = strtoupper($get('handicap_band'));
+ $pairingMode = strtoupper($get('pairing_mode'));
+ $coursePref = strtoupper($get('course_pref'));
+ $status = strtoupper($get('status') ?: 'PENDING_PAYMENT');
+ $hcpValRaw = $get('handicap_value');
+ $hcpVal = $hcpValRaw !== '' ? (float)$hcpValRaw : null;
+
+ $valid = in_array($handicapType, ['HI','EVENT_BAND'], true)
+ && in_array($handicapBand, ['LOW','MID','HIGH','BEGINNER'], true)
+ && in_array($pairingMode, ['LEVEL','BALANCED','RANDOM'], true)
+ && in_array($coursePref, ['A','B','ANY'], true)
+ && in_array($status, ['DRAFT','PENDING_PAYMENT','CONFIRMED','WAITLIST','CANCELLED'], true);
+
+ if (!$valid) {
+ $errors[] = ['row' => $i+2, 'message' => 'invalid enum value(s)'];
+ $skipped++; continue;
+ }
+
+ $player = Player::create([
+ 'name' => $name,
+ 'phone' => $phone,
+ 'email' => $email,
+ ]);
+
+ Registration::create([
+ 'event_id' => $event->id,
+ 'player_id' => $player->id,
+ 'type' => 'INDIVIDUAL',
+ 'status' => $status,
+ 'handicap_type' => $handicapType,
+ 'handicap_value' => $hcpVal,
+ 'handicap_band' => $handicapBand,
+ 'pairing_mode' => $pairingMode,
+ 'course_pref' => $coursePref,
+ 'registration_code' => $codeService->make(),
+ ]);
+
+ $created++;
+ }
+ });
+
+ return response()->json([
+ 'ok' => true,
+ 'created' => $created,
+ 'skipped' => $skipped,
+ 'errors' => $errors,
+ ]);
+ }
+}
diff --git a/app/Http/Controllers/Api/V1/Admin/PairingAdminController.php b/app/Http/Controllers/Api/V1/Admin/PairingAdminController.php
new file mode 100644
index 0000000..dfa11cc
--- /dev/null
+++ b/app/Http/Controllers/Api/V1/Admin/PairingAdminController.php
@@ -0,0 +1,19 @@
+pairing_finalized_at) {
+ return response()->json(['message' => 'Pairing is finalized. Unfinalize to run again.'], 409);
+ }
+ dispatch(new AssignPairingJob((int)$event->id));
+ return response()->json(['ok' => true]);
+ }
+}
diff --git a/app/Http/Controllers/Api/V1/Admin/PaymentsController.php b/app/Http/Controllers/Api/V1/Admin/PaymentsController.php
new file mode 100644
index 0000000..342e001
--- /dev/null
+++ b/app/Http/Controllers/Api/V1/Admin/PaymentsController.php
@@ -0,0 +1,42 @@
+whereHas('registration', fn($qq)=> $qq->where('event_id',$event->id));
+
+ if ($request->filled('status')) {
+ $q->where('status', $request->string('status'));
+ }
+
+ if ($request->filled('q')) {
+ $term = trim((string)$request->string('q'));
+ $q->where(function($qq) use ($term){
+ $qq->where('provider_ref_id','like',"%{$term}%")
+ ->orWhere('registration_id', $term);
+ });
+ }
+
+ $rows = $q->orderByDesc('id')->limit(1000)->get()->map(function($p){
+ return [
+ 'id' => $p->id,
+ 'registration_id' => $p->registration_id,
+ 'provider_ref_id' => $p->provider_ref_id,
+ 'amount' => (int) $p->amount,
+ 'status' => $p->status,
+ 'paid_at' => optional($p->paid_at)->toIso8601String(),
+ ];
+ });
+
+ return response()->json(['data' => $rows]);
+ }
+}
diff --git a/app/Http/Controllers/Api/V1/Admin/PrintController.php b/app/Http/Controllers/Api/V1/Admin/PrintController.php
new file mode 100644
index 0000000..78b1463
--- /dev/null
+++ b/app/Http/Controllers/Api/V1/Admin/PrintController.php
@@ -0,0 +1,121 @@
+event_id !== $event->id) abort(404);
+
+ $registration->load(['player','flightMember.flight']);
+
+ $name = e($registration->player?->name ?? '');
+ $code = e($registration->registration_code);
+ $flight = $registration->flightMember?->flight;
+ $flightText = $flight ? "Flight {$flight->code} • Course {$flight->course} • Hole {$flight->start_hole}{$flight->start_tee}" : "Flight: —";
+
+ return response($this->badgeHtml($event->name, $name, $code, $flightText))
+ ->header('Content-Type','text/html');
+ }
+
+ public function scorecard(Event $event, Flight $flight)
+ {
+ if ($flight->event_id !== $event->id) abort(404);
+ $flight->load(['members.registration.player']);
+
+ $members = $flight->members->sortBy('seat_no')->map(function($m){
+ return [
+ 'seat' => (int)$m->seat_no,
+ 'name' => e($m->registration?->player?->name ?? ''),
+ 'hcp' => e((string)($m->registration?->handicap_value ?? '—')),
+ 'band' => e((string)($m->registration?->handicap_band ?? '—')),
+ ];
+ })->values()->all();
+
+ return response($this->scorecardHtml($event->name, $flight, $members))
+ ->header('Content-Type','text/html');
+ }
+
+ private function badgeHtml(string $eventName, string $name, string $code, string $flightText): string
+ {
+ $eventName = e($eventName);
+ return "
+
+
+
+
{$name}
+
${code}
+
{$flightText}
+
+";
+ }
+
+ private function scorecardHtml(string $eventName, Flight $flight, array $members): string
+ {
+ $eventName = e($eventName);
+ $flightCode = e($flight->code);
+ $course = e($flight->course);
+ $hole = e((string)$flight->start_hole);
+ $tee = e($flight->start_tee);
+
+ $rows = '';
+ foreach ($members as $m) {
+ $rows .= "{$m['seat']} {$m['name']} {$m['band']} {$m['hcp']} ";
+ }
+
+ return "
+
+
+
+
+
+
{$eventName}
+
Scorecard • Flight {$flightCode}
+
+
Course {$course} • Start Hole {$hole}{$tee}
+
+
+
+ Seat Player Band HCP
+ {$rows}
+
+
+
Scoring: Stableford Nett • Shotgun Start
+
+
+";
+ }
+}
diff --git a/app/Http/Controllers/Api/V1/Admin/RegistrationsController.php b/app/Http/Controllers/Api/V1/Admin/RegistrationsController.php
new file mode 100644
index 0000000..671e8d7
--- /dev/null
+++ b/app/Http/Controllers/Api/V1/Admin/RegistrationsController.php
@@ -0,0 +1,120 @@
+id)
+ ->with(['player','flightMember.flight']);
+
+ if ($request->filled('status')) {
+ $q->where('status', $request->string('status'));
+ }
+ if ($request->filled('course_pref')) {
+ $q->where('course_pref', $request->string('course_pref'));
+ }
+ if ($request->filled('q')) {
+ $term = trim((string)$request->string('q'));
+ $q->where(function($qq) use ($term){
+ $qq->where('registration_code','like',"%{$term}%")
+ ->orWhereHas('player', function($p) use ($term){
+ $p->where('name','like',"%{$term}%")
+ ->orWhere('phone','like',"%{$term}%")
+ ->orWhere('email','like',"%{$term}%");
+ });
+ });
+ }
+
+ $rows = $q->orderByDesc('id')->limit(1000)->get()->map(function($r){
+ return [
+ 'id' => $r->id,
+ 'registration_code' => $r->registration_code,
+ 'player_name' => $r->player?->name,
+ 'player_phone' => $r->player?->phone,
+ 'player_email' => $r->player?->email,
+ 'status' => $r->status,
+ 'handicap_band' => $r->handicap_band,
+ 'handicap_value' => $r->handicap_value,
+ 'pairing_mode' => $r->pairing_mode,
+ 'course_pref' => $r->course_pref,
+ 'flight_code' => $r->flightMember?->flight?->code,
+ ];
+ });
+
+ return response()->json(['data' => $rows]);
+ }
+
+ public function show(Registration $registration)
+ {
+ $registration->load(['player','flightMember.flight.members.registration.player']);
+
+ $flight = $registration->flightMember?->flight;
+
+ $members = [];
+ if ($flight) {
+ foreach ($flight->members as $m) {
+ $members[] = [
+ 'flight_member_id' => $m->id,
+ 'seat_no' => (int)$m->seat_no,
+ 'registration_id' => $m->registration_id,
+ 'name' => $m->registration?->player?->name,
+ 'phone' => $m->registration?->player?->phone,
+ 'email' => $m->registration?->player?->email,
+ 'band' => $m->registration?->handicap_band,
+ 'hcp' => $m->registration?->handicap_value,
+ 'is_you' => $m->registration_id === $registration->id,
+ ];
+ }
+ usort($members, fn($a,$b)=> $a['seat_no'] <=> $b['seat_no']);
+ }
+
+ return response()->json([
+ 'registration' => [
+ 'id' => $registration->id,
+ 'registration_code' => $registration->registration_code,
+ 'status' => $registration->status,
+ 'pairing_mode' => $registration->pairing_mode,
+ 'course_pref' => $registration->course_pref,
+ 'handicap_band' => $registration->handicap_band,
+ 'handicap_value' => $registration->handicap_value,
+ ],
+ 'player' => [
+ 'id' => $registration->player?->id,
+ 'name' => $registration->player?->name,
+ 'phone' => $registration->player?->phone,
+ 'email' => $registration->player?->email,
+ 'club' => $registration->player?->club,
+ 'shirt_size' => $registration->player?->shirt_size,
+ ],
+ 'flight' => $flight ? [
+ 'id' => $flight->id,
+ 'code' => $flight->code,
+ 'course' => $flight->course,
+ 'start_hole' => (int)$flight->start_hole,
+ 'start_tee' => $flight->start_tee,
+ 'type' => $flight->type,
+ 'status' => $flight->status,
+ ] : null,
+ 'members' => $members,
+ ]);
+ }
+
+ public function update(Registration $registration, Request $request)
+ {
+ $data = $request->validate([
+ 'status' => ['required','in:DRAFT,PENDING_PAYMENT,CONFIRMED,WAITLIST,CANCELLED'],
+ ]);
+
+ $registration->status = $data['status'];
+ $registration->save();
+
+ return response()->json(['ok' => true]);
+ }
+}
diff --git a/app/Http/Controllers/Api/V1/Admin/SwapRequestController.php b/app/Http/Controllers/Api/V1/Admin/SwapRequestController.php
new file mode 100644
index 0000000..6860499
--- /dev/null
+++ b/app/Http/Controllers/Api/V1/Admin/SwapRequestController.php
@@ -0,0 +1,134 @@
+id)->with(['flightMember.flight','flightMember.registration.player','toFlight']);
+ if ($request->filled('status')) $q->where('status', strtoupper((string)$request->string('status')));
+ $rows = $q->orderByDesc('id')->limit(500)->get()->map(function($s){
+ return [
+ 'id' => $s->id,
+ 'status' => $s->status,
+ 'reason' => $s->reason,
+ 'requested_by' => $s->requested_by,
+ 'approved_by' => $s->approved_by,
+ 'approved_at' => optional($s->approved_at)->toIso8601String(),
+ 'from' => [
+ 'flight_code' => $s->flightMember?->flight?->code,
+ 'seat_no' => (int)($s->flightMember?->seat_no),
+ 'reg_code' => $s->flightMember?->registration?->registration_code,
+ 'name' => $s->flightMember?->registration?->player?->name,
+ ],
+ 'to' => [
+ 'flight_code' => $s->toFlight?->code,
+ 'seat_no' => (int)$s->to_seat_no,
+ ],
+ 'created_at' => $s->created_at->toIso8601String(),
+ ];
+ });
+ return response()->json(['data'=>$rows]);
+ }
+
+ public function request(Event $event, Request $request, AuditLogger $audit)
+ {
+ $data = $request->validate([
+ 'flight_member_id' => ['required','integer','exists:flight_members,id'],
+ 'to_flight_id' => ['required','integer','exists:flights,id'],
+ 'to_seat_no' => ['required','integer','in:1,2,3,4'],
+ 'reason' => ['nullable','string','max:250'],
+ ]);
+
+ $fm = FlightMember::with('flight')->findOrFail((int)$data['flight_member_id']);
+ $toFlight = Flight::findOrFail((int)$data['to_flight_id']);
+
+ if ($fm->flight->event_id !== $event->id || $toFlight->event_id !== $event->id) {
+ return response()->json(['message'=>'Event mismatch'], 422);
+ }
+
+ $sr = SwapRequest::create([
+ 'event_id' => $event->id,
+ 'flight_member_id' => $fm->id,
+ 'to_flight_id' => $toFlight->id,
+ 'to_seat_no' => (int)$data['to_seat_no'],
+ 'reason' => $data['reason'] ?? null,
+ 'status' => 'PENDING',
+ 'requested_by' => $request->user()?->id,
+ ]);
+
+ $audit->log($event->id, $request->user()?->id, 'swap_requested', 'swap_request', $sr->id, [
+ 'from_flight' => $fm->flight->code,
+ 'from_seat' => $fm->seat_no,
+ 'to_flight' => $toFlight->code,
+ 'to_seat' => (int)$data['to_seat_no'],
+ ]);
+
+ return response()->json(['ok'=>true,'id'=>$sr->id]);
+ }
+
+ public function approve(Event $event, SwapRequest $swapRequest, Request $request, AuditLogger $audit)
+ {
+ if ($swapRequest->event_id !== $event->id) return response()->json(['message'=>'Event mismatch'], 422);
+ if ($swapRequest->status !== 'PENDING') return response()->json(['message'=>'Not pending'], 409);
+
+ $swapRequest->status = 'APPROVED';
+ $swapRequest->approved_by = $request->user()?->id;
+ $swapRequest->approved_at = now();
+ $swapRequest->save();
+
+ $audit->log($event->id, $request->user()?->id, 'swap_approved', 'swap_request', $swapRequest->id);
+
+ return response()->json(['ok'=>true]);
+ }
+
+ public function reject(Event $event, SwapRequest $swapRequest, Request $request, AuditLogger $audit)
+ {
+ if ($swapRequest->event_id !== $event->id) return response()->json(['message'=>'Event mismatch'], 422);
+ if ($swapRequest->status !== 'PENDING') return response()->json(['message'=>'Not pending'], 409);
+
+ $swapRequest->status = 'REJECTED';
+ $swapRequest->approved_by = $request->user()?->id;
+ $swapRequest->approved_at = now();
+ $swapRequest->save();
+
+ $audit->log($event->id, $request->user()?->id, 'swap_rejected', 'swap_request', $swapRequest->id);
+
+ return response()->json(['ok'=>true]);
+ }
+
+ public function apply(Event $event, SwapRequest $swapRequest, Request $request, AuditLogger $audit)
+ {
+ if ($swapRequest->event_id !== $event->id) return response()->json(['message'=>'Event mismatch'], 422);
+ if ($swapRequest->status !== 'APPROVED') return response()->json(['message'=>'Approve first'], 409);
+
+ $fm = FlightMember::with('flight')->findOrFail($swapRequest->flight_member_id);
+ $toFlight = Flight::findOrFail($swapRequest->to_flight_id);
+
+ // Seat availability check
+ $taken = FlightMember::where('flight_id',$toFlight->id)->where('seat_no',$swapRequest->to_seat_no)->exists();
+ if ($taken) return response()->json(['message'=>'Target seat taken'], 409);
+
+ DB::transaction(function() use ($fm, $toFlight, $swapRequest){
+ $fm->flight_id = $toFlight->id;
+ $fm->seat_no = (int)$swapRequest->to_seat_no;
+ $fm->save();
+ $swapRequest->status = 'APPLIED';
+ $swapRequest->save();
+ });
+
+ $audit->log($event->id, $request->user()?->id, 'swap_applied', 'swap_request', $swapRequest->id);
+
+ return response()->json(['ok'=>true]);
+ }
+}
diff --git a/app/Http/Controllers/Api/V1/EventController.php b/app/Http/Controllers/Api/V1/EventController.php
new file mode 100644
index 0000000..f252268
--- /dev/null
+++ b/app/Http/Controllers/Api/V1/EventController.php
@@ -0,0 +1,26 @@
+json([
+ 'id' => $event->id,
+ 'name' => $event->name,
+ 'date' => optional($event->date)->toDateString(),
+ 'venue' => $event->venue,
+ 'shotgun_mode' => (bool) $event->shotgun_mode,
+ 'courses' => $event->courses_json,
+ 'registration_open_at' => optional($event->registration_open_at)->toIso8601String(),
+ 'registration_close_at' => optional($event->registration_close_at)->toIso8601String(),
+ 'pairing_finalized_at' => optional($event->pairing_finalized_at)->toIso8601String(),
+ 'pairing_published_at' => optional($event->pairing_published_at)->toIso8601String(),
+ ]);
+ }
+}
diff --git a/app/Http/Controllers/Api/V1/GroupController.php b/app/Http/Controllers/Api/V1/GroupController.php
new file mode 100644
index 0000000..f00135e
--- /dev/null
+++ b/app/Http/Controllers/Api/V1/GroupController.php
@@ -0,0 +1,103 @@
+validated();
+
+ $group = Group::create([
+ 'event_id' => $event->id,
+ 'group_code' => $groupCodeService->make(),
+ 'size_target' => $data['size_target'],
+ 'group_name' => $data['group_name'] ?? null,
+ 'leader_player_id' => null,
+ 'status' => 'OPEN',
+ ]);
+
+ return response()->json([
+ 'id' => $group->id,
+ 'group_code' => $group->group_code,
+ 'size_target' => $group->size_target,
+ 'status' => $group->status,
+ ], 201);
+ }
+
+ public function show(string $code)
+ {
+ $group = Group::where('group_code', $code)->firstOrFail();
+
+ $membersCount = Registration::where('group_id', $group->id)->count();
+
+ return response()->json([
+ 'id' => $group->id,
+ 'group_code' => $group->group_code,
+ 'size_target' => $group->size_target,
+ 'status' => $group->status,
+ 'members_count' => $membersCount,
+ 'group_name' => $group->group_name,
+ ]);
+ }
+
+ public function join(string $code, JoinGroupRequest $request, RegistrationCodeService $regCodeService)
+ {
+ $group = Group::where('group_code', $code)->firstOrFail();
+ $data = $request->validated();
+
+ // Simple "full" guard
+ $membersCount = Registration::where('group_id', $group->id)->count();
+ if ($membersCount >= $group->size_target) {
+ return response()->json(['message' => 'Group is full'], 409);
+ }
+
+ $player = Player::create([
+ 'name' => $data['player']['name'],
+ 'phone' => $data['player']['phone'],
+ 'email' => $data['player']['email'],
+ ]);
+
+ $registration = Registration::create([
+ 'event_id' => $group->event_id,
+ 'player_id' => $player->id,
+ 'type' => $membersCount === 0 ? 'GROUP_LEADER' : 'GROUP_MEMBER',
+ 'group_id' => $group->id,
+ 'status' => 'PENDING_PAYMENT',
+ 'handicap_type' => $data['handicap']['type'],
+ 'handicap_value' => $data['handicap']['value'] ?? null,
+ 'handicap_band' => $data['handicap']['band'],
+ // Use balanced as default for group members; can be changed later in v2
+ 'pairing_mode' => 'BALANCED',
+ 'course_pref' => 'ANY',
+ 'registration_code' => $regCodeService->make(),
+ ]);
+
+ if ($membersCount === 0) {
+ $group->leader_player_id = $player->id;
+ $group->save();
+ }
+
+ // Update group status
+ $membersCount2 = $membersCount + 1;
+ if ($membersCount2 >= $group->size_target) {
+ $group->status = 'FULL';
+ $group->save();
+ }
+
+ return response()->json([
+ 'id' => $registration->id,
+ 'registration_code' => $registration->registration_code,
+ ], 201);
+ }
+}
diff --git a/app/Http/Controllers/Api/V1/PairingController.php b/app/Http/Controllers/Api/V1/PairingController.php
new file mode 100644
index 0000000..4cbeb8c
--- /dev/null
+++ b/app/Http/Controllers/Api/V1/PairingController.php
@@ -0,0 +1,122 @@
+validated()['registration_code'];
+
+ $registration = Registration::where('registration_code', $code)->with('player')->firstOrFail();
+
+ // Mask phone for UI: +62***1234
+ $phone = $registration->player->phone;
+ $masked = $this->maskPhone($phone);
+
+ return response()->json([
+ 'registration_id' => $registration->id,
+ 'masked_phone' => $masked,
+ ]);
+ }
+
+ public function requestOtp(PairingOtpRequest $request, OtpService $otpService)
+ {
+ $registrationId = $request->validated()['registration_id'];
+ $registration = Registration::with('player')->findOrFail($registrationId);
+
+ $otpService->sendPairingOtp($registration);
+
+ return response()->json(['ok' => true]);
+ }
+
+ public function verifyOtp(PairingOtpVerifyRequest $request, OtpService $otpService, PairingTokenService $tokenService)
+ {
+ $data = $request->validated();
+ $registration = Registration::with('player')->findOrFail($data['registration_id']);
+
+ $otpService->verifyPairingOtp($registration, $data['otp']);
+
+ $token = $tokenService->issue($registration->id);
+
+ return response()->json([
+ 'pairing_token' => $token,
+ ]);
+ }
+
+ public function me(Request $request, PairingTokenService $tokenService)
+ {
+ $auth = $request->header('Authorization', '');
+ if (!str_starts_with($auth, 'Bearer ')) {
+ return response()->json(['message' => 'Unauthorized'], 401);
+ }
+ $token = trim(substr($auth, 7));
+
+ $registrationId = $tokenService->verify($token);
+ if (!$registrationId) {
+ return response()->json(['message' => 'Unauthorized'], 401);
+ }
+
+ $registration = Registration::with('player', 'flightMember.flight.members.registration.player')
+ ->findOrFail($registrationId);
+
+ $flightMember = $registration->flightMember;
+ $flight = $flightMember?->flight;
+
+ // Build members array for diagram (seat 1..4)
+ $members = [];
+ if ($flight) {
+ foreach ($flight->members as $m) {
+ $members[] = [
+ 'seat' => (int) $m->seat_no,
+ 'name' => $m->registration?->player?->name,
+ 'band' => $m->registration?->handicap_band,
+ 'hcp' => $m->registration?->handicap_value,
+ 'is_you' => $m->registration_id === $registration->id,
+ ];
+ }
+ }
+
+ return response()->json([
+ 'registration' => [
+ 'status' => $registration->status,
+ 'player' => [
+ 'name' => $registration->player->name,
+ 'band' => $registration->handicap_band,
+ 'handicap' => $registration->handicap_value,
+ ],
+ // Optional for UI copy; can be expanded later:
+ 'preferences' => [
+ 'pairing_mode' => $registration->pairing_mode,
+ 'pairing_mode_label' => strtolower($registration->pairing_mode) === 'LEVEL' ? 'level' : (strtolower($registration->pairing_mode) === 'RANDOM' ? 'random' : 'balanced'),
+ ],
+ ],
+ 'flight' => $flight ? [
+ 'code' => $flight->code,
+ 'course' => $flight->course,
+ 'start_hole' => (int) $flight->start_hole,
+ 'start_tee' => $flight->start_tee,
+ 'is_final' => $flight->status === 'FINAL',
+ ] : null,
+ 'members' => $members,
+ ]);
+ }
+
+ private function maskPhone(string $phone): string
+ {
+ // crude masking; adjust as needed for E164 normalization
+ $digits = preg_replace('/\D+/', '', $phone);
+ if (strlen($digits) <= 4) return '+**' . $digits;
+ $last4 = substr($digits, -4);
+ return '+'.substr($digits, 0, 2) . '***' . $last4;
+ }
+}
diff --git a/app/Http/Controllers/Api/V1/PaymentController.php b/app/Http/Controllers/Api/V1/PaymentController.php
new file mode 100644
index 0000000..f4ae476
--- /dev/null
+++ b/app/Http/Controllers/Api/V1/PaymentController.php
@@ -0,0 +1,41 @@
+validated();
+
+ // TODO: determine amount from event pricing table
+ $amount = 1000000; // placeholder IDR
+
+ $intent = PaymentIntent::create([
+ 'registration_id' => $registration->id,
+ 'provider' => 'XENDIT',
+ 'channel' => $data['method'], // INVOICE
+ 'amount' => $amount,
+ 'currency' => 'IDR',
+ 'status' => 'PENDING',
+ ]);
+
+ $invoice = $xendit->createInvoice($intent, $registration);
+
+ $intent->provider_ref_id = $invoice['provider_ref_id'];
+ $intent->checkout_url = $invoice['checkout_url'];
+ $intent->raw_payload = $invoice['raw_payload'] ?? null;
+ $intent->save();
+
+ return response()->json([
+ 'payment_intent_id' => $intent->id,
+ 'checkout_url' => $intent->checkout_url,
+ ]);
+ }
+}
diff --git a/app/Http/Controllers/Api/V1/RegistrationController.php b/app/Http/Controllers/Api/V1/RegistrationController.php
new file mode 100644
index 0000000..995110d
--- /dev/null
+++ b/app/Http/Controllers/Api/V1/RegistrationController.php
@@ -0,0 +1,44 @@
+validated();
+
+ $player = Player::create([
+ 'name' => $data['player']['name'],
+ 'phone' => $data['player']['phone'],
+ 'email' => $data['player']['email'],
+ 'club' => $data['player']['club'] ?? null,
+ 'shirt_size' => $data['player']['shirt_size'] ?? null,
+ ]);
+
+ $registration = Registration::create([
+ 'event_id' => $event->id,
+ 'player_id' => $player->id,
+ 'type' => 'INDIVIDUAL',
+ 'status' => 'PENDING_PAYMENT',
+ 'handicap_type' => $data['handicap']['type'],
+ 'handicap_value' => $data['handicap']['value'] ?? null,
+ 'handicap_band' => $data['handicap']['band'],
+ 'pairing_mode' => $data['preferences']['pairing_mode'],
+ 'course_pref' => $data['preferences']['course_pref'],
+ 'registration_code' => $codeService->make(),
+ ]);
+
+ return response()->json([
+ 'id' => $registration->id,
+ 'registration_code' => $registration->registration_code,
+ ], 201);
+ }
+}
diff --git a/app/Http/Controllers/Api/V1/WebhookController.php b/app/Http/Controllers/Api/V1/WebhookController.php
new file mode 100644
index 0000000..405b2c5
--- /dev/null
+++ b/app/Http/Controllers/Api/V1/WebhookController.php
@@ -0,0 +1,63 @@
+verifyWebhook($request)) {
+ return response()->json(['message' => 'Invalid webhook signature'], 401);
+ }
+
+ $payload = $request->all();
+
+ // NOTE: Map this based on the actual Xendit product you use.
+ // For Invoice webhook, you typically get invoice id + status.
+ $providerRefId = $payload['id'] ?? $payload['data']['id'] ?? null;
+ $status = strtoupper($payload['status'] ?? $payload['data']['status'] ?? '');
+
+ if (!$providerRefId) {
+ return response()->json(['message' => 'Missing provider_ref_id'], 422);
+ }
+
+ $intent = PaymentIntent::where('provider_ref_id', $providerRefId)->first();
+ if (!$intent) {
+ // idempotent: ignore unknown references
+ return response()->json(['ok' => true]);
+ }
+
+ // Convert Xendit statuses to our statuses
+ $mapped = match ($status) {
+ 'PAID', 'SETTLED' => 'PAID',
+ 'EXPIRED' => 'EXPIRED',
+ 'FAILED' => 'FAILED',
+ 'CANCELED', 'CANCELLED' => 'CANCELLED',
+ default => 'PENDING',
+ };
+
+ $intent->status = $mapped;
+ $intent->raw_payload = $payload;
+ if ($mapped === 'PAID') {
+ $intent->paid_at = now();
+ }
+ $intent->save();
+
+ if ($mapped === 'PAID') {
+ $reg = Registration::find($intent->registration_id);
+ if ($reg && $reg->status !== 'CONFIRMED') {
+ $reg->status = 'CONFIRMED';
+ $reg->save();
+ }
+ dispatch(new \App\Jobs\AssignPairingJob((int)$reg->event_id));
+ }
+
+ return response()->json(['ok' => true]);
+ }
+}
diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php
new file mode 100644
index 0000000..77ec359
--- /dev/null
+++ b/app/Http/Controllers/Controller.php
@@ -0,0 +1,12 @@
+
+ */
+ protected $middleware = [
+ // \App\Http\Middleware\TrustHosts::class,
+ \App\Http\Middleware\TrustProxies::class,
+ \Illuminate\Http\Middleware\HandleCors::class,
+ \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
+ \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
+ \App\Http\Middleware\TrimStrings::class,
+ \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
+ ];
+
+ /**
+ * The application's route middleware groups.
+ *
+ * @var array>
+ */
+ protected $middlewareGroups = [
+ 'web' => [
+ \App\Http\Middleware\EncryptCookies::class,
+ \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
+ \Illuminate\Session\Middleware\StartSession::class,
+ \Illuminate\View\Middleware\ShareErrorsFromSession::class,
+ \App\Http\Middleware\VerifyCsrfToken::class,
+ \Illuminate\Routing\Middleware\SubstituteBindings::class,
+ ],
+
+ 'api' => [
+ // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
+ \Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
+ \Illuminate\Routing\Middleware\SubstituteBindings::class,
+ ],
+ ];
+
+ /**
+ * The application's middleware aliases.
+ *
+ * Aliases may be used instead of class names to conveniently assign middleware to routes and groups.
+ *
+ * @var array
+ */
+ protected $middlewareAliases = [
+ 'auth' => \App\Http\Middleware\Authenticate::class,
+ 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
+ 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
+ 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
+ 'can' => \Illuminate\Auth\Middleware\Authorize::class,
+ 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
+ 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
+ 'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
+ 'signed' => \App\Http\Middleware\ValidateSignature::class,
+ 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
+ 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
+ ];
+}
diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php
new file mode 100644
index 0000000..d4ef644
--- /dev/null
+++ b/app/Http/Middleware/Authenticate.php
@@ -0,0 +1,17 @@
+expectsJson() ? null : route('login');
+ }
+}
diff --git a/app/Http/Middleware/EncryptCookies.php b/app/Http/Middleware/EncryptCookies.php
new file mode 100644
index 0000000..867695b
--- /dev/null
+++ b/app/Http/Middleware/EncryptCookies.php
@@ -0,0 +1,17 @@
+
+ */
+ protected $except = [
+ //
+ ];
+}
diff --git a/app/Http/Middleware/EnsureAdminRole.php b/app/Http/Middleware/EnsureAdminRole.php
new file mode 100644
index 0000000..d88be24
--- /dev/null
+++ b/app/Http/Middleware/EnsureAdminRole.php
@@ -0,0 +1,23 @@
+user();
+ if (!$user) return response()->json(['message' => 'Unauthorized'], 401);
+
+ if (!empty($roles)) {
+ $role = $user->role ?? 'committee';
+ if (!in_array($role, $roles, true)) {
+ return response()->json(['message' => 'Forbidden'], 403);
+ }
+ }
+ return $next($request);
+ }
+}
diff --git a/app/Http/Middleware/PreventRequestsDuringMaintenance.php b/app/Http/Middleware/PreventRequestsDuringMaintenance.php
new file mode 100644
index 0000000..74cbd9a
--- /dev/null
+++ b/app/Http/Middleware/PreventRequestsDuringMaintenance.php
@@ -0,0 +1,17 @@
+
+ */
+ protected $except = [
+ //
+ ];
+}
diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php
new file mode 100644
index 0000000..afc78c4
--- /dev/null
+++ b/app/Http/Middleware/RedirectIfAuthenticated.php
@@ -0,0 +1,30 @@
+check()) {
+ return redirect(RouteServiceProvider::HOME);
+ }
+ }
+
+ return $next($request);
+ }
+}
diff --git a/app/Http/Middleware/TrimStrings.php b/app/Http/Middleware/TrimStrings.php
new file mode 100644
index 0000000..88cadca
--- /dev/null
+++ b/app/Http/Middleware/TrimStrings.php
@@ -0,0 +1,19 @@
+
+ */
+ protected $except = [
+ 'current_password',
+ 'password',
+ 'password_confirmation',
+ ];
+}
diff --git a/app/Http/Middleware/TrustHosts.php b/app/Http/Middleware/TrustHosts.php
new file mode 100644
index 0000000..c9c58bd
--- /dev/null
+++ b/app/Http/Middleware/TrustHosts.php
@@ -0,0 +1,20 @@
+
+ */
+ public function hosts(): array
+ {
+ return [
+ $this->allSubdomainsOfApplicationUrl(),
+ ];
+ }
+}
diff --git a/app/Http/Middleware/TrustProxies.php b/app/Http/Middleware/TrustProxies.php
new file mode 100644
index 0000000..3391630
--- /dev/null
+++ b/app/Http/Middleware/TrustProxies.php
@@ -0,0 +1,28 @@
+|string|null
+ */
+ protected $proxies;
+
+ /**
+ * The headers that should be used to detect proxies.
+ *
+ * @var int
+ */
+ protected $headers =
+ Request::HEADER_X_FORWARDED_FOR |
+ Request::HEADER_X_FORWARDED_HOST |
+ Request::HEADER_X_FORWARDED_PORT |
+ Request::HEADER_X_FORWARDED_PROTO |
+ Request::HEADER_X_FORWARDED_AWS_ELB;
+}
diff --git a/app/Http/Middleware/ValidateSignature.php b/app/Http/Middleware/ValidateSignature.php
new file mode 100644
index 0000000..093bf64
--- /dev/null
+++ b/app/Http/Middleware/ValidateSignature.php
@@ -0,0 +1,22 @@
+
+ */
+ protected $except = [
+ // 'fbclid',
+ // 'utm_campaign',
+ // 'utm_content',
+ // 'utm_medium',
+ // 'utm_source',
+ // 'utm_term',
+ ];
+}
diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php
new file mode 100644
index 0000000..9e86521
--- /dev/null
+++ b/app/Http/Middleware/VerifyCsrfToken.php
@@ -0,0 +1,17 @@
+
+ */
+ protected $except = [
+ //
+ ];
+}
diff --git a/app/Http/Requests/CreateGroupRequest.php b/app/Http/Requests/CreateGroupRequest.php
new file mode 100644
index 0000000..1a9c8ed
--- /dev/null
+++ b/app/Http/Requests/CreateGroupRequest.php
@@ -0,0 +1,18 @@
+ ['required','integer','in:2,3,4'],
+ 'group_name' => ['nullable','string','max:120'],
+ ];
+ }
+}
diff --git a/app/Http/Requests/JoinGroupRequest.php b/app/Http/Requests/JoinGroupRequest.php
new file mode 100644
index 0000000..62b2fee
--- /dev/null
+++ b/app/Http/Requests/JoinGroupRequest.php
@@ -0,0 +1,25 @@
+ ['required','string','max:120'],
+ 'player.phone' => ['required','string','max:40'],
+ 'player.email' => ['required','email','max:180'],
+
+ 'handicap.type' => ['required','in:HI,EVENT_BAND'],
+ 'handicap.value' => ['nullable','numeric','min:0','max:60'],
+ 'handicap.band' => ['required','in:LOW,MID,HIGH,BEGINNER'],
+
+ 'consent' => ['required','boolean'],
+ ];
+ }
+}
diff --git a/app/Http/Requests/PairingLookupRequest.php b/app/Http/Requests/PairingLookupRequest.php
new file mode 100644
index 0000000..40d3aef
--- /dev/null
+++ b/app/Http/Requests/PairingLookupRequest.php
@@ -0,0 +1,17 @@
+ ['required','string','max:40'],
+ ];
+ }
+}
diff --git a/app/Http/Requests/PairingOtpRequest.php b/app/Http/Requests/PairingOtpRequest.php
new file mode 100644
index 0000000..dfc8e44
--- /dev/null
+++ b/app/Http/Requests/PairingOtpRequest.php
@@ -0,0 +1,17 @@
+ ['required','integer','exists:registrations,id'],
+ ];
+ }
+}
diff --git a/app/Http/Requests/PairingOtpVerifyRequest.php b/app/Http/Requests/PairingOtpVerifyRequest.php
new file mode 100644
index 0000000..30e0772
--- /dev/null
+++ b/app/Http/Requests/PairingOtpVerifyRequest.php
@@ -0,0 +1,18 @@
+ ['required','integer','exists:registrations,id'],
+ 'otp' => ['required','string','min:4','max:8'],
+ ];
+ }
+}
diff --git a/app/Http/Requests/PayRegistrationRequest.php b/app/Http/Requests/PayRegistrationRequest.php
new file mode 100644
index 0000000..c430179
--- /dev/null
+++ b/app/Http/Requests/PayRegistrationRequest.php
@@ -0,0 +1,17 @@
+ ['required','in:INVOICE'],
+ ];
+ }
+}
diff --git a/app/Http/Requests/StoreRegistrationRequest.php b/app/Http/Requests/StoreRegistrationRequest.php
new file mode 100644
index 0000000..b6b0119
--- /dev/null
+++ b/app/Http/Requests/StoreRegistrationRequest.php
@@ -0,0 +1,30 @@
+ ['required','string','max:120'],
+ 'player.phone' => ['required','string','max:40'],
+ 'player.email' => ['required','email','max:180'],
+ 'player.club' => ['nullable','string','max:120'],
+ 'player.shirt_size' => ['nullable','string','max:10'],
+
+ 'handicap.type' => ['required','in:HI,EVENT_BAND'],
+ 'handicap.value' => ['nullable','numeric','min:0','max:60'],
+ 'handicap.band' => ['required','in:LOW,MID,HIGH,BEGINNER'],
+
+ 'preferences.pairing_mode' => ['required','in:LEVEL,BALANCED,RANDOM'],
+ 'preferences.course_pref' => ['required','in:A,B,ANY'],
+
+ 'consent' => ['required','boolean'],
+ ];
+ }
+}
diff --git a/app/Jobs/AssignPairingJob.php b/app/Jobs/AssignPairingJob.php
new file mode 100644
index 0000000..0dfa62f
--- /dev/null
+++ b/app/Jobs/AssignPairingJob.php
@@ -0,0 +1,24 @@
+run($this->eventId);
+ Log::info('[PXG Pairing] assigned='.$result['assigned'], ['notes' => $result['notes'] ?? []]);
+ }
+}
diff --git a/app/Models/AuditLog.php b/app/Models/AuditLog.php
new file mode 100644
index 0000000..dcd99ea
--- /dev/null
+++ b/app/Models/AuditLog.php
@@ -0,0 +1,12 @@
+ 'array'];
+}
diff --git a/app/Models/Checkin.php b/app/Models/Checkin.php
new file mode 100644
index 0000000..be543cc
--- /dev/null
+++ b/app/Models/Checkin.php
@@ -0,0 +1,14 @@
+ 'datetime'];
+
+ public function registration(){ return $this->belongsTo(Registration::class); }
+}
diff --git a/app/Models/Event.php b/app/Models/Event.php
new file mode 100644
index 0000000..8a1362f
--- /dev/null
+++ b/app/Models/Event.php
@@ -0,0 +1,24 @@
+ 'date',
+ 'shotgun_mode' => 'boolean',
+ 'courses_json' => 'array',
+ 'registration_open_at' => 'datetime',
+ 'registration_close_at' => 'datetime',
+ 'pairing_finalized_at' => 'datetime',
+ 'pairing_published_at' => 'datetime',
+ ];
+}
diff --git a/app/Models/Flight.php b/app/Models/Flight.php
new file mode 100644
index 0000000..7a22036
--- /dev/null
+++ b/app/Models/Flight.php
@@ -0,0 +1,16 @@
+hasMany(FlightMember::class);
+ }
+}
diff --git a/app/Models/FlightMember.php b/app/Models/FlightMember.php
new file mode 100644
index 0000000..dd85649
--- /dev/null
+++ b/app/Models/FlightMember.php
@@ -0,0 +1,24 @@
+ 'boolean'
+ ];
+
+ public function flight(){
+ return $this->belongsTo(Flight::class);
+ }
+
+ public function registration(){
+ return $this->belongsTo(Registration::class);
+ }
+}
diff --git a/app/Models/Group.php b/app/Models/Group.php
new file mode 100644
index 0000000..1ed1c83
--- /dev/null
+++ b/app/Models/Group.php
@@ -0,0 +1,16 @@
+hasMany(Registration::class);
+ }
+}
diff --git a/app/Models/OtpRequest.php b/app/Models/OtpRequest.php
new file mode 100644
index 0000000..ca469ed
--- /dev/null
+++ b/app/Models/OtpRequest.php
@@ -0,0 +1,17 @@
+ 'datetime'
+ ];
+}
diff --git a/app/Models/PaymentIntent.php b/app/Models/PaymentIntent.php
new file mode 100644
index 0000000..339c52f
--- /dev/null
+++ b/app/Models/PaymentIntent.php
@@ -0,0 +1,22 @@
+ 'datetime',
+ 'raw_payload' => 'array'
+ ];
+
+ public function registration(){
+ return $this->belongsTo(Registration::class);
+ }
+}
diff --git a/app/Models/Player.php b/app/Models/Player.php
new file mode 100644
index 0000000..f269d11
--- /dev/null
+++ b/app/Models/Player.php
@@ -0,0 +1,12 @@
+ 'decimal:1'
+ ];
+
+ public function player(){
+ return $this->belongsTo(Player::class);
+ }
+
+ public function event(){
+ return $this->belongsTo(Event::class);
+ }
+
+ public function group(){
+ return $this->belongsTo(Group::class);
+ }
+
+public function flightMember(){
+ return $this->hasOne(FlightMember::class);
+ }
+}
diff --git a/app/Models/SwapRequest.php b/app/Models/SwapRequest.php
new file mode 100644
index 0000000..2913b3e
--- /dev/null
+++ b/app/Models/SwapRequest.php
@@ -0,0 +1,18 @@
+ 'datetime'];
+
+ public function flightMember(){ return $this->belongsTo(FlightMember::class); }
+ public function toFlight(){ return $this->belongsTo(Flight::class, 'to_flight_id'); }
+}
diff --git a/app/Models/User.php b/app/Models/User.php
new file mode 100644
index 0000000..4d7f70f
--- /dev/null
+++ b/app/Models/User.php
@@ -0,0 +1,45 @@
+
+ */
+ protected $fillable = [
+ 'name',
+ 'email',
+ 'password',
+ ];
+
+ /**
+ * The attributes that should be hidden for serialization.
+ *
+ * @var array
+ */
+ protected $hidden = [
+ 'password',
+ 'remember_token',
+ ];
+
+ /**
+ * The attributes that should be cast.
+ *
+ * @var array
+ */
+ protected $casts = [
+ 'email_verified_at' => 'datetime',
+ 'password' => 'hashed',
+ ];
+}
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
new file mode 100644
index 0000000..452e6b6
--- /dev/null
+++ b/app/Providers/AppServiceProvider.php
@@ -0,0 +1,24 @@
+
+ */
+ protected $policies = [
+ //
+ ];
+
+ /**
+ * Register any authentication / authorization services.
+ */
+ public function boot(): void
+ {
+ //
+ }
+}
diff --git a/app/Providers/BroadcastServiceProvider.php b/app/Providers/BroadcastServiceProvider.php
new file mode 100644
index 0000000..2be04f5
--- /dev/null
+++ b/app/Providers/BroadcastServiceProvider.php
@@ -0,0 +1,19 @@
+>
+ */
+ protected $listen = [
+ Registered::class => [
+ SendEmailVerificationNotification::class,
+ ],
+ ];
+
+ /**
+ * Register any events for your application.
+ */
+ public function boot(): void
+ {
+ //
+ }
+
+ /**
+ * Determine if events and listeners should be automatically discovered.
+ */
+ public function shouldDiscoverEvents(): bool
+ {
+ return false;
+ }
+}
diff --git a/app/Providers/PxgServiceProvider.php b/app/Providers/PxgServiceProvider.php
new file mode 100644
index 0000000..6ea782d
--- /dev/null
+++ b/app/Providers/PxgServiceProvider.php
@@ -0,0 +1,19 @@
+mergeConfigFrom(__DIR__.'/../../config/pxg.php', 'pxg');
+ }
+
+ public function boot(): void
+ {
+ //
+ }
+}
diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php
new file mode 100644
index 0000000..1cf5f15
--- /dev/null
+++ b/app/Providers/RouteServiceProvider.php
@@ -0,0 +1,40 @@
+by($request->user()?->id ?: $request->ip());
+ });
+
+ $this->routes(function () {
+ Route::middleware('api')
+ ->prefix('api')
+ ->group(base_path('routes/api.php'));
+
+ Route::middleware('web')
+ ->group(base_path('routes/web.php'));
+ });
+ }
+}
diff --git a/app/Services/AuditLogger.php b/app/Services/AuditLogger.php
new file mode 100644
index 0000000..b38f640
--- /dev/null
+++ b/app/Services/AuditLogger.php
@@ -0,0 +1,20 @@
+ $eventId,
+ 'actor_user_id' => $actorUserId,
+ 'action' => $action,
+ 'entity_type' => $entityType,
+ 'entity_id' => $entityId,
+ 'meta' => $meta ?: null,
+ ]);
+ }
+}
diff --git a/app/Services/DevAutoConfirm.php b/app/Services/DevAutoConfirm.php
new file mode 100644
index 0000000..6ac8c37
--- /dev/null
+++ b/app/Services/DevAutoConfirm.php
@@ -0,0 +1,11 @@
+ new LogOtpSender(),
+ default => new LogOtpSender(),
+ };
+ }
+
+ public function sendPairingOtp(Registration $registration): void
+ {
+ $ttl = config('pxg.otp_ttl_minutes', 5);
+ $maxPer10m = config('pxg.otp_rate_limit_per_phone', 3);
+
+ $phone = $this->toE164($registration->player->phone);
+
+ // Basic rate-limit: count last 10 minutes
+ $recentCount = OtpRequest::where('phone_e164', $phone)
+ ->where('created_at', '>=', now()->subMinutes(10))
+ ->count();
+
+ if ($recentCount >= $maxPer10m) {
+ abort(429, 'Too many OTP requests. Please try again later.');
+ }
+
+ $otp = (string) random_int(100000, 999999);
+
+ OtpRequest::create([
+ 'purpose' => 'PAIRING_VIEW',
+ 'registration_id' => $registration->id,
+ 'phone_e164' => $phone,
+ 'otp_hash' => Hash::make($otp),
+ 'expires_at' => now()->addMinutes($ttl),
+ 'attempts' => 0,
+ 'status' => 'SENT',
+ 'request_ip' => request()->ip(),
+ 'user_agent' => (string) request()->userAgent(),
+ ]);
+
+ $msg = "TOURNAMENT PXG 2026\nYour OTP: {$otp}\nValid for {$ttl} minutes.";
+ $this->sender()->sendWhatsApp($phone, $msg);
+ }
+
+ public function verifyPairingOtp(Registration $registration, string $otp): void
+ {
+ $maxAttempts = config('pxg.otp_max_attempts', 5);
+ $phone = $this->toE164($registration->player->phone);
+
+ $req = OtpRequest::where('registration_id', $registration->id)
+ ->where('phone_e164', $phone)
+ ->whereIn('status', ['SENT'])
+ ->orderByDesc('id')
+ ->first();
+
+ if (!$req) abort(404, 'OTP not found. Please request OTP again.');
+ if ($req->expires_at->isPast()) {
+ $req->status = 'EXPIRED'; $req->save();
+ abort(410, 'OTP expired. Please request OTP again.');
+ }
+ if ($req->attempts >= $maxAttempts) {
+ $req->status = 'LOCKED'; $req->save();
+ abort(423, 'Too many attempts. Please request OTP again later.');
+ }
+
+ $req->attempts += 1;
+ $req->save();
+
+ if (!Hash::check($otp, $req->otp_hash)) {
+ abort(401, 'Invalid OTP.');
+ }
+
+ $req->status = 'VERIFIED';
+ $req->save();
+ }
+
+ private function toE164(string $phone): string
+ {
+ // Basic normalization for Indonesia numbers: 08xx -> +628xx
+ $p = preg_replace('/\D+/', '', $phone);
+ if (Str::startsWith($p, '0')) {
+ $p = '62'.substr($p, 1);
+ }
+ if (!Str::startsWith($p, '62')) {
+ // fallback
+ $p = '62'.$p;
+ }
+ return '+'.$p;
+ }
+}
diff --git a/app/Services/PairingEngine.php b/app/Services/PairingEngine.php
new file mode 100644
index 0000000..a986fe8
--- /dev/null
+++ b/app/Services/PairingEngine.php
@@ -0,0 +1,484 @@
+where('type', 'NORMAL')
+ ->with(['members.registration'])
+ ->lockForUpdate()
+ ->get()
+ ->keyBy('id');
+
+ $seatOrder = config('pxg.pairing.seat_order', [1,2,3,4]);
+
+ // Seat availability map
+ $flightSeats = [];
+ foreach ($flights as $f) {
+ $taken = $f->members->pluck('seat_no')->map(fn($x)=> (int)$x)->all();
+ $available = array_values(array_diff($seatOrder, $taken));
+ $flightSeats[$f->id] = $available;
+ }
+
+ $assigned = 0;
+ $notes = [];
+
+ $getPendingRegs = function () use ($eventId) {
+ return Registration::where('event_id', $eventId)
+ ->where('status', 'CONFIRMED')
+ ->whereDoesntHave('flightMember')
+ ->with(['player','group'])
+ ->get();
+ };
+
+ $regs = $getPendingRegs();
+ if ($regs->isEmpty()) {
+ return ['assigned' => 0, 'notes' => ['no pending confirmed registrations']];
+ }
+
+ // Balancing stats
+ $courseLoad = $this->computeCourseLoad($eventId);
+ $holeHighLikeLoad = $this->computeHoleHighLikeLoad($eventId); // by start_hole (1..18)
+
+ // 1) Group-of-4 placement
+ $groupIds = $regs->whereNotNull('group_id')->pluck('group_id')->unique()->values();
+ if ($groupIds->isNotEmpty()) {
+ $groups = Group::whereIn('id', $groupIds)->where('size_target', 4)->get();
+ foreach ($groups as $g) {
+ $groupRegs = Registration::where('group_id', $g->id)
+ ->where('status', 'CONFIRMED')
+ ->whereDoesntHave('flightMember')
+ ->with('player')
+ ->get();
+ if ($groupRegs->count() !== 4) continue;
+
+ $coursePref = 'ANY';
+ $leader = $groupRegs->firstWhere('type', 'GROUP_LEADER') ?? $groupRegs->first();
+ if ($leader && in_array($leader->course_pref, ['A','B'])) $coursePref = $leader->course_pref;
+
+ $flightId = $this->findEmptyFlightBalanced($flights, $flightSeats, $courseLoad, $coursePref);
+ if (!$flightId) { $notes[] = "no empty flight for group {$g->group_code}"; continue; }
+
+ $seats = $flightSeats[$flightId];
+ if (count($seats) < 4) continue;
+
+ $i = 0;
+ foreach ($groupRegs as $r) {
+ FlightMember::create([
+ 'flight_id' => $flightId,
+ 'registration_id' => $r->id,
+ 'seat_no' => $seats[$i],
+ 'lock_flag' => false,
+ ]);
+ $assigned++; $i++;
+ $courseLoad[$flights[$flightId]->course] = ($courseLoad[$flights[$flightId]->course] ?? 0) + 1;
+ if ($this->isHighLike($r->handicap_band)) {
+ $h = (int)$flights[$flightId]->start_hole;
+ $holeHighLikeLoad[$h] = ($holeHighLikeLoad[$h] ?? 0) + 1;
+ }
+ }
+
+ $flightSeats[$flightId] = array_values(array_slice($seats, 4));
+ $this->refreshFlightStatus($flightId);
+ $notes[] = "group {$g->group_code} -> flight {$flights[$flightId]->code}";
+ }
+ }
+
+ // refresh remaining regs
+ $regs = $getPendingRegs();
+
+ // 2) LEVEL by band (semi-hard course_pref)
+ $levelRegs = $regs->where('pairing_mode', 'LEVEL')->values();
+ foreach (['LOW','MID','HIGH','BEGINNER'] as $band) {
+ foreach ($levelRegs->where('handicap_band', $band)->values() as $r) {
+ $flightId = $this->findBestFlightForLevel($flights, $flightSeats, $courseLoad, $holeHighLikeLoad, $band, $r->course_pref);
+ if (!$flightId) break;
+
+ $seat = array_shift($flightSeats[$flightId]);
+ FlightMember::create([
+ 'flight_id' => $flightId,
+ 'registration_id' => $r->id,
+ 'seat_no' => $seat,
+ 'lock_flag' => false,
+ ]);
+ $assigned++;
+ $courseLoad[$flights[$flightId]->course] = ($courseLoad[$flights[$flightId]->course] ?? 0) + 1;
+ if ($this->isHighLike($r->handicap_band)) {
+ $h = (int)$flights[$flightId]->start_hole;
+ $holeHighLikeLoad[$h] = ($holeHighLikeLoad[$h] ?? 0) + 1;
+ }
+ $this->refreshFlightStatus($flightId);
+
+ // reflect memory
+ $flights[$flightId]->members->push((object)[ 'seat_no' => $seat, 'registration' => $r ]);
+ }
+ }
+
+ // refresh remaining regs
+ $regs = $getPendingRegs();
+
+ // 3) BALANCED - now course_pref aware + hole bottleneck mitigation
+ $balancedTarget = config('pxg.pairing.balanced_target', ['LOW','MID','MID','HIGH']);
+ $maxHighLike = (int) config('pxg.pairing.max_high_like_per_flight', 2);
+
+ $balancedRegs = $regs->where('pairing_mode', 'BALANCED')->values();
+ $pool = $this->poolByBand($balancedRegs);
+
+ // While there are regs, pick next reg to place and choose best flight for that reg band+course_pref
+ while ($this->poolHasAny($pool)) {
+ // pick next band by target cycle
+ foreach ($balancedTarget as $needBand) {
+ if (!$this->poolHasAny($pool)) break;
+
+ $needBand = strtoupper($needBand);
+ $pickBand = $needBand;
+
+ if ($needBand === 'HIGH' && empty($pool['HIGH']) && !empty($pool['BEGINNER'])) $pickBand = 'BEGINNER';
+
+ $reg = $this->popFromPool($pool, $pickBand);
+ if (!$reg) $reg = $this->popAny($pool);
+ if (!$reg) break;
+
+ $coursePref = $reg->course_pref ?? 'ANY';
+ $flightId = $this->findBestFlightForBalanced($flights, $flightSeats, $courseLoad, $holeHighLikeLoad, $reg, $coursePref);
+ if (!$flightId) {
+ // if semi-hard pref blocks, fallback ANY
+ $flightId = $this->findBestFlightForBalanced($flights, $flightSeats, $courseLoad, $holeHighLikeLoad, $reg, 'ANY');
+ }
+ if (!$flightId) {
+ // put back and stop
+ $this->pushBack($pool, $reg);
+ break;
+ }
+
+ // Pace guardrail: avoid too many high-like in one flight
+ $curHighLike = $this->countHighLikeInFlight($flights[$flightId]);
+ if ($this->isHighLike($reg->handicap_band) && $curHighLike >= $maxHighLike) {
+ $this->pushBack($pool, $reg);
+ $alt = $this->popFirstNonHighLike($pool);
+ if ($alt) $reg = $alt; else $reg = $this->popAny($pool) ?? $reg;
+ }
+
+ if (empty($flightSeats[$flightId])) {
+ // no seat left, retry next iteration
+ $this->pushBack($pool, $reg);
+ continue;
+ }
+
+ $seat = array_shift($flightSeats[$flightId]);
+ FlightMember::create([
+ 'flight_id' => $flightId,
+ 'registration_id' => $reg->id,
+ 'seat_no' => $seat,
+ 'lock_flag' => false,
+ ]);
+ $assigned++;
+ $courseLoad[$flights[$flightId]->course] = ($courseLoad[$flights[$flightId]->course] ?? 0) + 1;
+ if ($this->isHighLike($reg->handicap_band)) {
+ $h = (int)$flights[$flightId]->start_hole;
+ $holeHighLikeLoad[$h] = ($holeHighLikeLoad[$h] ?? 0) + 1;
+ }
+ $this->refreshFlightStatus($flightId);
+
+ $flights[$flightId]->members->push((object)[ 'seat_no' => $seat, 'registration' => $reg ]);
+ }
+ }
+
+ // refresh remaining regs
+ $regs = $getPendingRegs();
+
+ // 4) RANDOM - course_pref semi-hard + course balance
+ $randomRegs = $regs->where('pairing_mode', 'RANDOM')->values();
+ foreach ($randomRegs as $r) {
+ $flightId = $this->findBestFlightForRandom($flights, $flightSeats, $courseLoad, $holeHighLikeLoad, $r, $r->course_pref);
+ if (!$flightId) $flightId = $this->findBestFlightForRandom($flights, $flightSeats, $courseLoad, $holeHighLikeLoad, $r, 'ANY');
+ if (!$flightId) break;
+
+ $seat = array_shift($flightSeats[$flightId]);
+ FlightMember::create([
+ 'flight_id' => $flightId,
+ 'registration_id' => $r->id,
+ 'seat_no' => $seat,
+ 'lock_flag' => false,
+ ]);
+ $assigned++;
+ $courseLoad[$flights[$flightId]->course] = ($courseLoad[$flights[$flightId]->course] ?? 0) + 1;
+ if ($this->isHighLike($r->handicap_band)) {
+ $h = (int)$flights[$flightId]->start_hole;
+ $holeHighLikeLoad[$h] = ($holeHighLikeLoad[$h] ?? 0) + 1;
+ }
+ $this->refreshFlightStatus($flightId);
+
+ $flights[$flightId]->members->push((object)[ 'seat_no' => $seat, 'registration' => $r ]);
+ }
+
+ return ['assigned' => $assigned, 'notes' => $notes];
+ });
+ }
+
+ private function computeCourseLoad(int $eventId): array
+ {
+ $rows = DB::table('flight_members')
+ ->join('flights', 'flight_members.flight_id', '=', 'flights.id')
+ ->where('flights.event_id', $eventId)
+ ->select('flights.course', DB::raw('count(*) as cnt'))
+ ->groupBy('flights.course')
+ ->get();
+
+ $load = ['A' => 0, 'B' => 0];
+ foreach ($rows as $r) $load[$r->course] = (int) $r->cnt;
+ return $load;
+ }
+
+ private function computeHoleHighLikeLoad(int $eventId): array
+ {
+ // count HIGH+BEGINNER assigned per start_hole across courses (shotgun bottleneck proxy)
+ $rows = DB::table('flight_members')
+ ->join('flights', 'flight_members.flight_id', '=', 'flights.id')
+ ->join('registrations', 'flight_members.registration_id', '=', 'registrations.id')
+ ->where('flights.event_id', $eventId)
+ ->whereIn('registrations.handicap_band', ['HIGH','BEGINNER'])
+ ->select('flights.start_hole', DB::raw('count(*) as cnt'))
+ ->groupBy('flights.start_hole')
+ ->get();
+
+ $load = [];
+ foreach ($rows as $r) $load[(int)$r->start_hole] = (int) $r->cnt;
+ return $load;
+ }
+
+ private function refreshFlightStatus(int $flightId): void
+ {
+ $count = FlightMember::where('flight_id', $flightId)->count();
+ Flight::where('id', $flightId)->update(['status' => $count >= 4 ? 'FULL' : 'OPEN']);
+ }
+
+ private function isHighLike(?string $band): bool
+ {
+ $b = strtoupper((string)$band);
+ return $b === 'HIGH' || $b === 'BEGINNER';
+ }
+
+ private function countHighLikeInFlight(Flight $flight): int
+ {
+ $c = 0;
+ foreach ($flight->members as $m) {
+ $b = $m->registration?->handicap_band ?? null;
+ if ($this->isHighLike($b)) $c++;
+ }
+ return $c;
+ }
+
+ private function findEmptyFlightBalanced($flights, $flightSeats, array $courseLoad, string $coursePref): ?int
+ {
+ $candidates = [];
+ foreach ($flights as $f) {
+ if (($f->members->count() ?? 0) !== 0) continue;
+ if (count($flightSeats[$f->id] ?? []) !== 4) continue;
+ if (in_array($coursePref, ['A','B']) && $f->course !== $coursePref) continue;
+ $candidates[] = $f;
+ }
+ if (empty($candidates)) {
+ if ($coursePref !== 'ANY') return $this->findEmptyFlightBalanced($flights, $flightSeats, $courseLoad, 'ANY');
+ return null;
+ }
+
+ if (!config('pxg.pairing.course_balance', true)) return $candidates[0]->id;
+
+ usort($candidates, fn($a,$b) => ($courseLoad[$a->course] ?? 0) <=> ($courseLoad[$b->course] ?? 0));
+ return $candidates[0]->id;
+ }
+
+ private function findBestFlightForLevel($flights, $flightSeats, array $courseLoad, array $holeHighLikeLoad, string $band, string $coursePref): ?int
+ {
+ // Semi-hard course preference: if preferred has any seats at all, do not choose other course.
+ if (config('pxg.pairing.course_pref_semi_hard', true) && in_array($coursePref, ['A','B'])) {
+ if ($this->courseHasSeats($flights, $flightSeats, $coursePref)) {
+ return $this->findBestFlightForLevel($flights, $flightSeats, $courseLoad, $holeHighLikeLoad, $band, $coursePref === 'A' ? 'A' : 'B');
+ }
+ // else fallback ANY below
+ $coursePref = 'ANY';
+ }
+
+ $band = strtoupper($band);
+ $candidates = [];
+
+ foreach ($flights as $f) {
+ if (empty($flightSeats[$f->id] ?? [])) continue;
+ if (in_array($coursePref, ['A','B']) && $f->course !== $coursePref) continue;
+
+ $bands = $f->members->map(fn($m)=> strtoupper((string)($m->registration?->handicap_band)))->filter()->values()->all();
+ $unique = array_values(array_unique($bands));
+ $isHomogeneousSame = (count($unique) === 1 && $unique[0] === $band);
+
+ $count = count($bands);
+ $score = 0;
+ if ($isHomogeneousSame) $score += 140;
+ if ($count === 0) $score += 80;
+ $score += (4 - $count) * 6;
+
+ if (config('pxg.pairing.course_balance', true) && $coursePref === 'ANY') {
+ $score += (200 - ($courseLoad[$f->course] ?? 0));
+ }
+
+ // If assigning HIGH/BEGINNER in level mode, spread across holes
+ if (config('pxg.pairing.hole_balance_high_like', true) && $this->isHighLike($band)) {
+ $pen = (int) config('pxg.pairing.hole_high_like_penalty', 12);
+ $score -= ($holeHighLikeLoad[(int)$f->start_hole] ?? 0) * $pen;
+ }
+
+ $candidates[] = [$f->id, $score];
+ }
+
+ if (empty($candidates)) {
+ if ($coursePref !== 'ANY') return $this->findBestFlightForLevel($flights, $flightSeats, $courseLoad, $holeHighLikeLoad, $band, 'ANY');
+ return null;
+ }
+
+ usort($candidates, fn($x,$y) => $y[1] <=> $x[1]);
+ return $candidates[0][0];
+ }
+
+ private function findBestFlightForBalanced($flights, $flightSeats, array $courseLoad, array $holeHighLikeLoad, Registration $reg, string $coursePref): ?int
+ {
+ // semi-hard course preference
+ if (config('pxg.pairing.course_pref_semi_hard', true) && in_array($coursePref, ['A','B'])) {
+ if ($this->courseHasSeats($flights, $flightSeats, $coursePref)) {
+ // keep pref
+ } else {
+ $coursePref = 'ANY';
+ }
+ }
+
+ $candidates = [];
+ $isHigh = $this->isHighLike($reg->handicap_band);
+
+ foreach ($flights as $f) {
+ if (empty($flightSeats[$f->id] ?? [])) continue;
+ if (in_array($coursePref, ['A','B']) && $f->course !== $coursePref) continue;
+
+ $count = $f->members->count();
+ $score = (4 - $count) * 12;
+
+ // Avoid flights with many high-like already
+ $score -= $this->countHighLikeInFlight($f) * 10;
+
+ if (config('pxg.pairing.course_balance', true) && $coursePref === 'ANY') {
+ $score += (200 - ($courseLoad[$f->course] ?? 0));
+ }
+
+ if (config('pxg.pairing.hole_balance_high_like', true) && $isHigh) {
+ $pen = (int) config('pxg.pairing.hole_high_like_penalty', 12);
+ $score -= ($holeHighLikeLoad[(int)$f->start_hole] ?? 0) * $pen;
+ }
+
+ $candidates[] = [$f->id, $score];
+ }
+
+ if (empty($candidates)) return null;
+ usort($candidates, fn($x,$y) => $y[1] <=> $x[1]);
+ return $candidates[0][0];
+ }
+
+ private function findBestFlightForRandom($flights, $flightSeats, array $courseLoad, array $holeHighLikeLoad, Registration $reg, string $coursePref): ?int
+ {
+ if (config('pxg.pairing.course_pref_semi_hard', true) && in_array($coursePref, ['A','B'])) {
+ if (!$this->courseHasSeats($flights, $flightSeats, $coursePref)) $coursePref = 'ANY';
+ }
+
+ $candidates = [];
+ $isHigh = $this->isHighLike($reg->handicap_band);
+
+ foreach ($flights as $f) {
+ if (empty($flightSeats[$f->id] ?? [])) continue;
+ if (in_array($coursePref, ['A','B']) && $f->course !== $coursePref) continue;
+
+ $count = $f->members->count();
+ $score = (4 - $count) * 20;
+
+ if (config('pxg.pairing.course_balance', true) && $coursePref === 'ANY') {
+ $score += (200 - ($courseLoad[$f->course] ?? 0));
+ }
+
+ if (config('pxg.pairing.hole_balance_high_like', true) && $isHigh) {
+ $pen = (int) config('pxg.pairing.hole_high_like_penalty', 12);
+ $score -= ($holeHighLikeLoad[(int)$f->start_hole] ?? 0) * $pen;
+ }
+
+ $candidates[] = [$f->id, $score];
+ }
+
+ if (empty($candidates)) return null;
+ usort($candidates, fn($x,$y) => $y[1] <=> $x[1]);
+ return $candidates[0][0];
+ }
+
+ private function courseHasSeats($flights, $flightSeats, string $course): bool
+ {
+ foreach ($flights as $f) {
+ if ($f->course !== $course) continue;
+ if (!empty($flightSeats[$f->id] ?? [])) return true;
+ }
+ return false;
+ }
+
+ private function poolByBand($regs): array
+ {
+ $pool = ['LOW'=>[], 'MID'=>[], 'HIGH'=>[], 'BEGINNER'=>[]];
+ foreach ($regs as $r) {
+ $b = strtoupper((string)($r->handicap_band ?? 'MID'));
+ if (!isset($pool[$b])) $pool[$b] = [];
+ $pool[$b][] = $r;
+ }
+ return $pool;
+ }
+
+ private function poolHasAny(array $pool): bool
+ {
+ foreach ($pool as $arr) if (!empty($arr)) return true;
+ return false;
+ }
+
+ private function popFromPool(array &$pool, string $band): ?Registration
+ {
+ $band = strtoupper($band);
+ if (empty($pool[$band])) return null;
+ return array_shift($pool[$band]);
+ }
+
+ private function popAny(array &$pool): ?Registration
+ {
+ foreach (['LOW','MID','HIGH','BEGINNER'] as $b) if (!empty($pool[$b])) return array_shift($pool[$b]);
+ return null;
+ }
+
+ private function pushBack(array &$pool, Registration $r): void
+ {
+ $b = strtoupper((string)($r->handicap_band ?? 'MID'));
+ $pool[$b][] = $r;
+ }
+
+ private function popFirstNonHighLike(array &$pool): ?Registration
+ {
+ foreach (['LOW','MID'] as $b) if (!empty($pool[$b])) return array_shift($pool[$b]);
+ return null;
+ }
+}
diff --git a/app/Services/PairingTokenService.php b/app/Services/PairingTokenService.php
new file mode 100644
index 0000000..8b6fb17
--- /dev/null
+++ b/app/Services/PairingTokenService.php
@@ -0,0 +1,32 @@
+ $registrationId,
+ 'exp' => now()->addMinutes($ttl)->timestamp,
+ ];
+ return Crypt::encryptString(json_encode($payload));
+ }
+
+ public function verify(string $token): ?int
+ {
+ try {
+ $json = Crypt::decryptString($token);
+ $payload = json_decode($json, true);
+ if (!is_array($payload)) return null;
+ if (($payload['exp'] ?? 0) < time()) return null;
+ $rid = (int) ($payload['rid'] ?? 0);
+ return $rid > 0 ? $rid : null;
+ } catch (\Throwable $e) {
+ return null;
+ }
+ }
+}
diff --git a/app/Services/RegistrationCodeService.php b/app/Services/RegistrationCodeService.php
new file mode 100644
index 0000000..bf986cb
--- /dev/null
+++ b/app/Services/RegistrationCodeService.php
@@ -0,0 +1,17 @@
+id;
+ $checkoutUrl = 'https://checkout.xendit.co/web/' . $providerRefId;
+
+ return [
+ 'provider_ref_id' => $providerRefId,
+ 'checkout_url' => $checkoutUrl,
+ 'raw_payload' => [
+ 'stub' => true,
+ 'intent_id' => $intent->id,
+ 'registration_id' => $registration->id
+ ]
+ ];
+ }
+
+ /**
+ * Minimal webhook verification using a shared token (recommended baseline).
+ * Configure `XENDIT_WEBHOOK_TOKEN` in .env and set the same token in Xendit dashboard.
+ */
+ public function verifyWebhook(Request $request): bool
+ {
+ $expected = config('pxg.xendit.webhook_token', '');
+ if (!$expected) return true; // allow in dev
+
+ // Xendit commonly uses X-Callback-Token for invoice callbacks.
+ $got = $request->header('X-Callback-Token') ?? $request->header('x-callback-token');
+ return is_string($got) && hash_equals($expected, $got);
+ }
+}
diff --git a/artisan b/artisan
new file mode 100644
index 0000000..67a3329
--- /dev/null
+++ b/artisan
@@ -0,0 +1,53 @@
+#!/usr/bin/env php
+make(Illuminate\Contracts\Console\Kernel::class);
+
+$status = $kernel->handle(
+ $input = new Symfony\Component\Console\Input\ArgvInput,
+ new Symfony\Component\Console\Output\ConsoleOutput
+);
+
+/*
+|--------------------------------------------------------------------------
+| Shutdown The Application
+|--------------------------------------------------------------------------
+|
+| Once Artisan has finished running, we will fire off the shutdown events
+| so that any final work may be done by the application before we shut
+| down the process. This is the last thing to happen to the request.
+|
+*/
+
+$kernel->terminate($input, $status);
+
+exit($status);
diff --git a/bootstrap/app.php b/bootstrap/app.php
new file mode 100644
index 0000000..037e17d
--- /dev/null
+++ b/bootstrap/app.php
@@ -0,0 +1,55 @@
+singleton(
+ Illuminate\Contracts\Http\Kernel::class,
+ App\Http\Kernel::class
+);
+
+$app->singleton(
+ Illuminate\Contracts\Console\Kernel::class,
+ App\Console\Kernel::class
+);
+
+$app->singleton(
+ Illuminate\Contracts\Debug\ExceptionHandler::class,
+ App\Exceptions\Handler::class
+);
+
+/*
+|--------------------------------------------------------------------------
+| Return The Application
+|--------------------------------------------------------------------------
+|
+| This script returns the application instance. The instance is given to
+| the calling script so we can separate the building of the instances
+| from the actual running of the application and sending responses.
+|
+*/
+
+return $app;
diff --git a/bootstrap/cache/.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/composer.json b/composer.json
new file mode 100644
index 0000000..8a3d72d
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,66 @@
+{
+ "name": "laravel/laravel",
+ "type": "project",
+ "description": "The skeleton application for the Laravel framework.",
+ "keywords": ["laravel", "framework"],
+ "license": "MIT",
+ "require": {
+ "php": "^8.1",
+ "guzzlehttp/guzzle": "^7.2",
+ "laravel/framework": "^10.10",
+ "laravel/sanctum": "^3.3",
+ "laravel/tinker": "^2.8"
+ },
+ "require-dev": {
+ "fakerphp/faker": "^1.9.1",
+ "laravel/pint": "^1.0",
+ "laravel/sail": "^1.18",
+ "mockery/mockery": "^1.4.4",
+ "nunomaduro/collision": "^7.0",
+ "phpunit/phpunit": "^10.1",
+ "spatie/laravel-ignition": "^2.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "App\\": "app/",
+ "Database\\Factories\\": "database/factories/",
+ "Database\\Seeders\\": "database/seeders/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Tests\\": "tests/"
+ }
+ },
+ "scripts": {
+ "post-autoload-dump": [
+ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
+ "@php artisan package:discover --ansi"
+ ],
+ "post-update-cmd": [
+ "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
+ ],
+ "post-root-package-install": [
+ "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
+ ],
+ "post-create-project-cmd": [
+ "@php artisan key:generate --ansi"
+ ]
+ },
+ "extra": {
+ "laravel": {
+ "dont-discover": []
+ }
+ },
+ "config": {
+ "optimize-autoloader": true,
+ "preferred-install": "dist",
+ "sort-packages": true,
+ "allow-plugins": {
+ "pestphp/pest-plugin": true,
+ "php-http/discovery": true
+ }
+ },
+ "minimum-stability": "stable",
+ "prefer-stable": true
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..4086ce0
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,8206 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "9c491b8531eec05ba41a11d9276a5749",
+ "packages": [
+ {
+ "name": "brick/math",
+ "version": "0.12.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/brick/math.git",
+ "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba",
+ "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.1"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.2",
+ "phpunit/phpunit": "^10.1",
+ "vimeo/psalm": "6.8.8"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Brick\\Math\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Arbitrary-precision arithmetic library",
+ "keywords": [
+ "Arbitrary-precision",
+ "BigInteger",
+ "BigRational",
+ "arithmetic",
+ "bigdecimal",
+ "bignum",
+ "bignumber",
+ "brick",
+ "decimal",
+ "integer",
+ "math",
+ "mathematics",
+ "rational"
+ ],
+ "support": {
+ "issues": "https://github.com/brick/math/issues",
+ "source": "https://github.com/brick/math/tree/0.12.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/BenMorel",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-28T13:11:00+00:00"
+ },
+ {
+ "name": "carbonphp/carbon-doctrine-types",
+ "version": "2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git",
+ "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/99f76ffa36cce3b70a4a6abce41dba15ca2e84cb",
+ "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4 || ^8.0"
+ },
+ "conflict": {
+ "doctrine/dbal": "<3.7.0 || >=4.0.0"
+ },
+ "require-dev": {
+ "doctrine/dbal": "^3.7.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/2.1.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": "2023-12-11T17:09:12+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.5.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/dragonmantank/cron-expression.git",
+ "reference": "1b2de7f4a468165dca07b142240733a1973e766d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/1b2de7f4a468165dca07b142240733a1973e766d",
+ "reference": "1b2de7f4a468165dca07b142240733a1973e766d",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2|^8.0"
+ },
+ "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.5.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/dragonmantank",
+ "type": "github"
+ }
+ ],
+ "time": "2025-10-31T18:36:32+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.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "21dc724a0583619cd1652f673303492272778051"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/21dc724a0583619cd1652f673303492272778051",
+ "reference": "21dc724a0583619cd1652f673303492272778051",
+ "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",
+ "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.8.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": "2025-08-23T21:21:41+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": "laravel/framework",
+ "version": "10.50.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/framework.git",
+ "reference": "3ff39b7a9b83e633383ec9b019827ed54b6d38bc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/framework/zipball/3ff39b7a9b83e633383ec9b019827ed54b6d38bc",
+ "reference": "3ff39b7a9b83e633383ec9b019827ed54b6d38bc",
+ "shasum": ""
+ },
+ "require": {
+ "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12",
+ "composer-runtime-api": "^2.2",
+ "doctrine/inflector": "^2.0.5",
+ "dragonmantank/cron-expression": "^3.3.2",
+ "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.2",
+ "guzzlehttp/uri-template": "^1.0",
+ "laravel/prompts": "^0.1.9",
+ "laravel/serializable-closure": "^1.3",
+ "league/commonmark": "^2.2.1",
+ "league/flysystem": "^3.8.0",
+ "monolog/monolog": "^3.0",
+ "nesbot/carbon": "^2.67",
+ "nunomaduro/termwind": "^1.13",
+ "php": "^8.1",
+ "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": "^6.2",
+ "symfony/error-handler": "^6.2",
+ "symfony/finder": "^6.2",
+ "symfony/http-foundation": "^6.4",
+ "symfony/http-kernel": "^6.2",
+ "symfony/mailer": "^6.2",
+ "symfony/mime": "^6.2",
+ "symfony/process": "^6.2",
+ "symfony/routing": "^6.2",
+ "symfony/uid": "^6.2",
+ "symfony/var-dumper": "^6.2",
+ "tijsverkoyen/css-to-inline-styles": "^2.2.5",
+ "vlucas/phpdotenv": "^5.4.1",
+ "voku/portable-ascii": "^2.0"
+ },
+ "conflict": {
+ "carbonphp/carbon-doctrine-types": ">=3.0",
+ "doctrine/dbal": ">=4.0",
+ "mockery/mockery": "1.6.8",
+ "phpunit/phpunit": ">=11.0.0",
+ "tightenco/collect": "<5.5.33"
+ },
+ "provide": {
+ "psr/container-implementation": "1.1|2.0",
+ "psr/simple-cache-implementation": "1.0|2.0|3.0"
+ },
+ "replace": {
+ "illuminate/auth": "self.version",
+ "illuminate/broadcasting": "self.version",
+ "illuminate/bus": "self.version",
+ "illuminate/cache": "self.version",
+ "illuminate/collections": "self.version",
+ "illuminate/conditionable": "self.version",
+ "illuminate/config": "self.version",
+ "illuminate/console": "self.version",
+ "illuminate/container": "self.version",
+ "illuminate/contracts": "self.version",
+ "illuminate/cookie": "self.version",
+ "illuminate/database": "self.version",
+ "illuminate/encryption": "self.version",
+ "illuminate/events": "self.version",
+ "illuminate/filesystem": "self.version",
+ "illuminate/hashing": "self.version",
+ "illuminate/http": "self.version",
+ "illuminate/log": "self.version",
+ "illuminate/macroable": "self.version",
+ "illuminate/mail": "self.version",
+ "illuminate/notifications": "self.version",
+ "illuminate/pagination": "self.version",
+ "illuminate/pipeline": "self.version",
+ "illuminate/process": "self.version",
+ "illuminate/queue": "self.version",
+ "illuminate/redis": "self.version",
+ "illuminate/routing": "self.version",
+ "illuminate/session": "self.version",
+ "illuminate/support": "self.version",
+ "illuminate/testing": "self.version",
+ "illuminate/translation": "self.version",
+ "illuminate/validation": "self.version",
+ "illuminate/view": "self.version"
+ },
+ "require-dev": {
+ "ably/ably-php": "^1.0",
+ "aws/aws-sdk-php": "^3.235.5",
+ "doctrine/dbal": "^3.5.1",
+ "ext-gmp": "*",
+ "fakerphp/faker": "^1.21",
+ "guzzlehttp/guzzle": "^7.5",
+ "league/flysystem-aws-s3-v3": "^3.0",
+ "league/flysystem-ftp": "^3.0",
+ "league/flysystem-path-prefixing": "^3.3",
+ "league/flysystem-read-only": "^3.3",
+ "league/flysystem-sftp-v3": "^3.0",
+ "mockery/mockery": "^1.5.1",
+ "nyholm/psr7": "^1.2",
+ "orchestra/testbench-core": "^8.23.4",
+ "pda/pheanstalk": "^4.0",
+ "phpstan/phpstan": "~1.11.11",
+ "phpunit/phpunit": "^10.0.7",
+ "predis/predis": "^2.0.2",
+ "symfony/cache": "^6.2",
+ "symfony/http-client": "^6.2.4",
+ "symfony/psr-http-message-bridge": "^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.235.5).",
+ "brianium/paratest": "Required to run tests in parallel (^6.0).",
+ "doctrine/dbal": "Required to rename columns and drop SQLite columns (^3.5.1).",
+ "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).",
+ "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).",
+ "filp/whoops": "Required for friendly error pages in development (^2.14.3).",
+ "guzzlehttp/guzzle": "Required to use the HTTP Client and the ping methods on schedules (^7.5).",
+ "laravel/tinker": "Required to use the tinker console command (^2.0).",
+ "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.0).",
+ "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.0).",
+ "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.3).",
+ "league/flysystem-read-only": "Required to use read-only disks (^3.3)",
+ "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.0).",
+ "mockery/mockery": "Required to use mocking (^1.5.1).",
+ "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).",
+ "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).",
+ "phpunit/phpunit": "Required to use assertions and run tests (^9.5.8|^10.0.7).",
+ "predis/predis": "Required to use the predis connector (^2.0.2).",
+ "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).",
+ "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).",
+ "symfony/cache": "Required to PSR-6 cache bridge (^6.2).",
+ "symfony/filesystem": "Required to enable support for relative symbolic links (^6.2).",
+ "symfony/http-client": "Required to enable support for the Symfony API mail transports (^6.2).",
+ "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^6.2).",
+ "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^6.2).",
+ "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0)."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "10.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/Support/helpers.php"
+ ],
+ "psr-4": {
+ "Illuminate\\": "src/Illuminate/",
+ "Illuminate\\Support\\": [
+ "src/Illuminate/Macroable/",
+ "src/Illuminate/Collections/",
+ "src/Illuminate/Conditionable/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ }
+ ],
+ "description": "The Laravel Framework.",
+ "homepage": "https://laravel.com",
+ "keywords": [
+ "framework",
+ "laravel"
+ ],
+ "support": {
+ "issues": "https://github.com/laravel/framework/issues",
+ "source": "https://github.com/laravel/framework"
+ },
+ "time": "2026-02-15T14:12:07+00:00"
+ },
+ {
+ "name": "laravel/prompts",
+ "version": "v0.1.25",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/prompts.git",
+ "reference": "7b4029a84c37cb2725fc7f011586e2997040bc95"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/prompts/zipball/7b4029a84c37cb2725fc7f011586e2997040bc95",
+ "reference": "7b4029a84c37cb2725fc7f011586e2997040bc95",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "illuminate/collections": "^10.0|^11.0",
+ "php": "^8.1",
+ "symfony/console": "^6.2|^7.0"
+ },
+ "conflict": {
+ "illuminate/console": ">=10.17.0 <10.25.0",
+ "laravel/framework": ">=10.17.0 <10.25.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.5",
+ "pestphp/pest": "^2.3",
+ "phpstan/phpstan": "^1.11",
+ "phpstan/phpstan-mockery": "^1.1"
+ },
+ "suggest": {
+ "ext-pcntl": "Required for the spinner to be animated."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "0.1.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.1.25"
+ },
+ "time": "2024-08-12T22:06:33+00:00"
+ },
+ {
+ "name": "laravel/sanctum",
+ "version": "v3.3.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/sanctum.git",
+ "reference": "8c104366459739f3ada0e994bcd3e6fd681ce3d5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/sanctum/zipball/8c104366459739f3ada0e994bcd3e6fd681ce3d5",
+ "reference": "8c104366459739f3ada0e994bcd3e6fd681ce3d5",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "illuminate/console": "^9.21|^10.0",
+ "illuminate/contracts": "^9.21|^10.0",
+ "illuminate/database": "^9.21|^10.0",
+ "illuminate/support": "^9.21|^10.0",
+ "php": "^8.0.2"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.0",
+ "orchestra/testbench": "^7.28.2|^8.8.3",
+ "phpstan/phpstan": "^1.10",
+ "phpunit/phpunit": "^9.6"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Laravel\\Sanctum\\SanctumServiceProvider"
+ ]
+ },
+ "branch-alias": {
+ "dev-master": "3.x-dev"
+ }
+ },
+ "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": "2023-12-19T18:44:48+00:00"
+ },
+ {
+ "name": "laravel/serializable-closure",
+ "version": "v1.3.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/serializable-closure.git",
+ "reference": "4f48ade902b94323ca3be7646db16209ec76be3d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/4f48ade902b94323ca3be7646db16209ec76be3d",
+ "reference": "4f48ade902b94323ca3be7646db16209ec76be3d",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.3|^8.0"
+ },
+ "require-dev": {
+ "illuminate/support": "^8.0|^9.0|^10.0|^11.0",
+ "nesbot/carbon": "^2.61|^3.0",
+ "pestphp/pest": "^1.21.3",
+ "phpstan/phpstan": "^1.8.2",
+ "symfony/var-dumper": "^5.4.11|^6.2.0|^7.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Laravel\\SerializableClosure\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylor@laravel.com"
+ },
+ {
+ "name": "Nuno Maduro",
+ "email": "nuno@laravel.com"
+ }
+ ],
+ "description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.",
+ "keywords": [
+ "closure",
+ "laravel",
+ "serializable"
+ ],
+ "support": {
+ "issues": "https://github.com/laravel/serializable-closure/issues",
+ "source": "https://github.com/laravel/serializable-closure"
+ },
+ "time": "2024-11-14T18:34: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.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/commonmark.git",
+ "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/4efa10c1e56488e658d10adf7b7b7dcd19940bfb",
+ "reference": "4efa10c1e56488e658d10adf7b7b7dcd19940bfb",
+ "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",
+ "symfony/process": "^5.4 | ^6.0 | ^7.0",
+ "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.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": "2025-11-26T21:48:24+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.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/flysystem.git",
+ "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/1717e0b3642b0df65ecb0cc89cdd99fa840672ff",
+ "reference": "1717e0b3642b0df65ecb0cc89cdd99fa840672ff",
+ "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.31.0"
+ },
+ "time": "2026-01-23T15:38:47+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": "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": "2.73.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/CarbonPHP/carbon.git",
+ "reference": "9228ce90e1035ff2f0db84b40ec2e023ed802075"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/CarbonPHP/carbon/zipball/9228ce90e1035ff2f0db84b40ec2e023ed802075",
+ "reference": "9228ce90e1035ff2f0db84b40ec2e023ed802075",
+ "shasum": ""
+ },
+ "require": {
+ "carbonphp/carbon-doctrine-types": "*",
+ "ext-json": "*",
+ "php": "^7.1.8 || ^8.0",
+ "psr/clock": "^1.0",
+ "symfony/polyfill-mbstring": "^1.0",
+ "symfony/polyfill-php80": "^1.16",
+ "symfony/translation": "^3.4 || ^4.0 || ^5.0 || ^6.0"
+ },
+ "provide": {
+ "psr/clock-implementation": "1.0"
+ },
+ "require-dev": {
+ "doctrine/dbal": "^2.0 || ^3.1.4 || ^4.0",
+ "doctrine/orm": "^2.7 || ^3.0",
+ "friendsofphp/php-cs-fixer": "^3.0",
+ "kylekatarnls/multi-tester": "^2.0",
+ "ondrejmirtes/better-reflection": "<6",
+ "phpmd/phpmd": "^2.9",
+ "phpstan/extension-installer": "^1.0",
+ "phpstan/phpstan": "^0.12.99 || ^1.7.14",
+ "phpunit/php-file-iterator": "^2.0.5 || ^3.0.6",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.26 || ^9.5.20",
+ "squizlabs/php_codesniffer": "^3.4"
+ },
+ "bin": [
+ "bin/carbon"
+ ],
+ "type": "library",
+ "extra": {
+ "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://carbon.nesbot.com",
+ "keywords": [
+ "date",
+ "datetime",
+ "time"
+ ],
+ "support": {
+ "docs": "https://carbon.nesbot.com/docs",
+ "issues": "https://github.com/briannesbitt/Carbon/issues",
+ "source": "https://github.com/briannesbitt/Carbon"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/kylekatarnls",
+ "type": "github"
+ },
+ {
+ "url": "https://opencollective.com/Carbon#sponsor",
+ "type": "opencollective"
+ },
+ {
+ "url": "https://tidelift.com/subscription/pkg/packagist-nesbot-carbon?utm_source=packagist-nesbot-carbon&utm_medium=referral&utm_campaign=readme",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-01-08T20:10:23+00:00"
+ },
+ {
+ "name": "nette/schema",
+ "version": "v1.3.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/schema.git",
+ "reference": "086497a2f34b82fede9b5a41cc8e131d087cd8f7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/schema/zipball/086497a2f34b82fede9b5a41cc8e131d087cd8f7",
+ "reference": "086497a2f34b82fede9b5a41cc8e131d087cd8f7",
+ "shasum": ""
+ },
+ "require": {
+ "nette/utils": "^4.0",
+ "php": "8.1 - 8.5"
+ },
+ "require-dev": {
+ "nette/tester": "^2.6",
+ "phpstan/phpstan": "^2.0@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.4"
+ },
+ "time": "2026-02-08T02:54:00+00:00"
+ },
+ {
+ "name": "nette/utils",
+ "version": "v4.0.10",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/utils.git",
+ "reference": "2778deb6aab136c8db4ed1f4d6e9f465ca2dbee3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/utils/zipball/2778deb6aab136c8db4ed1f4d6e9f465ca2dbee3",
+ "reference": "2778deb6aab136c8db4ed1f4d6e9f465ca2dbee3",
+ "shasum": ""
+ },
+ "require": {
+ "php": "8.0 - 8.5"
+ },
+ "conflict": {
+ "nette/finder": "<3",
+ "nette/schema": "<1.2.2"
+ },
+ "require-dev": {
+ "jetbrains/phpstorm-attributes": "^1.2",
+ "nette/tester": "^2.5",
+ "phpstan/phpstan-nette": "^2.0@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.0-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.0.10"
+ },
+ "time": "2025-12-01T17:30:42+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": "v1.17.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nunomaduro/termwind.git",
+ "reference": "5369ef84d8142c1d87e4ec278711d4ece3cbf301"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/5369ef84d8142c1d87e4ec278711d4ece3cbf301",
+ "reference": "5369ef84d8142c1d87e4ec278711d4ece3cbf301",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": "^8.1",
+ "symfony/console": "^6.4.15"
+ },
+ "require-dev": {
+ "illuminate/console": "^10.48.24",
+ "illuminate/support": "^10.48.24",
+ "laravel/pint": "^1.18.2",
+ "pestphp/pest": "^2.36.0",
+ "pestphp/pest-plugin-mock": "2.0.0",
+ "phpstan/phpstan": "^1.12.11",
+ "phpstan/phpstan-strict-rules": "^1.6.1",
+ "symfony/var-dumper": "^6.4.15",
+ "thecodingmachine/phpstan-strict-rules": "^1.0.0"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Termwind\\Laravel\\TermwindServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/Functions.php"
+ ],
+ "psr-4": {
+ "Termwind\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nuno Maduro",
+ "email": "enunomaduro@gmail.com"
+ }
+ ],
+ "description": "Its like Tailwind CSS, but for the console.",
+ "keywords": [
+ "cli",
+ "console",
+ "css",
+ "package",
+ "php",
+ "style"
+ ],
+ "support": {
+ "issues": "https://github.com/nunomaduro/termwind/issues",
+ "source": "https://github.com/nunomaduro/termwind/tree/v1.17.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": "2024-11-21T10:36:35+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": "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.20",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/bobthecow/psysh.git",
+ "reference": "19678eb6b952a03b8a1d96ecee9edba518bb0373"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/bobthecow/psysh/zipball/19678eb6b952a03b8a1d96ecee9edba518bb0373",
+ "reference": "19678eb6b952a03b8a1d96ecee9edba518bb0373",
+ "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.20"
+ },
+ "time": "2026-02-11T15:05:28+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/console",
+ "version": "v6.4.32",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/console.git",
+ "reference": "0bc2199c6c1f05276b05956f1ddc63f6d7eb5fc3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/console/zipball/0bc2199c6c1f05276b05956f1ddc63f6d7eb5fc3",
+ "reference": "0bc2199c6c1f05276b05956f1ddc63f6d7eb5fc3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/string": "^5.4|^6.0|^7.0"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<5.4",
+ "symfony/dotenv": "<5.4",
+ "symfony/event-dispatcher": "<5.4",
+ "symfony/lock": "<5.4",
+ "symfony/process": "<5.4"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0|2.0|3.0"
+ },
+ "require-dev": {
+ "psr/log": "^1|^2|^3",
+ "symfony/config": "^5.4|^6.0|^7.0",
+ "symfony/dependency-injection": "^5.4|^6.0|^7.0",
+ "symfony/event-dispatcher": "^5.4|^6.0|^7.0",
+ "symfony/http-foundation": "^6.4|^7.0",
+ "symfony/http-kernel": "^6.4|^7.0",
+ "symfony/lock": "^5.4|^6.0|^7.0",
+ "symfony/messenger": "^5.4|^6.0|^7.0",
+ "symfony/process": "^5.4|^6.0|^7.0",
+ "symfony/stopwatch": "^5.4|^6.0|^7.0",
+ "symfony/var-dumper": "^5.4|^6.0|^7.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/v6.4.32"
+ },
+ "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-13T08:45:59+00:00"
+ },
+ {
+ "name": "symfony/css-selector",
+ "version": "v6.4.24",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/css-selector.git",
+ "reference": "9b784413143701aa3c94ac1869a159a9e53e8761"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/css-selector/zipball/9b784413143701aa3c94ac1869a159a9e53e8761",
+ "reference": "9b784413143701aa3c94ac1869a159a9e53e8761",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\CssSelector\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Jean-François Simon",
+ "email": "jeanfrancois.simon@sensiolabs.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Converts CSS selectors to XPath expressions",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/css-selector/tree/v6.4.24"
+ },
+ "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-10T08:14:14+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": "v6.4.32",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/error-handler.git",
+ "reference": "8c18400784fcb014dc73c8d5601a9576af7f8ad4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/error-handler/zipball/8c18400784fcb014dc73c8d5601a9576af7f8ad4",
+ "reference": "8c18400784fcb014dc73c8d5601a9576af7f8ad4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/log": "^1|^2|^3",
+ "symfony/var-dumper": "^5.4|^6.0|^7.0"
+ },
+ "conflict": {
+ "symfony/deprecation-contracts": "<2.5",
+ "symfony/http-kernel": "<6.4"
+ },
+ "require-dev": {
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/http-kernel": "^6.4|^7.0",
+ "symfony/serializer": "^5.4|^6.0|^7.0"
+ },
+ "bin": [
+ "Resources/bin/patch-type-declarations"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\ErrorHandler\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides tools to manage errors and ease debugging PHP code",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/error-handler/tree/v6.4.32"
+ },
+ "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-19T19:28:19+00:00"
+ },
+ {
+ "name": "symfony/event-dispatcher",
+ "version": "v6.4.32",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/event-dispatcher.git",
+ "reference": "99d7e101826e6610606b9433248f80c1997cd20b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/99d7e101826e6610606b9433248f80c1997cd20b",
+ "reference": "99d7e101826e6610606b9433248f80c1997cd20b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "symfony/event-dispatcher-contracts": "^2.5|^3"
+ },
+ "conflict": {
+ "symfony/dependency-injection": "<5.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": "^5.4|^6.0|^7.0",
+ "symfony/dependency-injection": "^5.4|^6.0|^7.0",
+ "symfony/error-handler": "^5.4|^6.0|^7.0",
+ "symfony/expression-language": "^5.4|^6.0|^7.0",
+ "symfony/http-foundation": "^5.4|^6.0|^7.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/stopwatch": "^5.4|^6.0|^7.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/v6.4.32"
+ },
+ "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:13:48+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": "v6.4.33",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/finder.git",
+ "reference": "24965ca011dac87431729640feef8bcf7b5523e0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/24965ca011dac87431729640feef8bcf7b5523e0",
+ "reference": "24965ca011dac87431729640feef8bcf7b5523e0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "symfony/filesystem": "^6.0|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Finder\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Finds files and directories via an intuitive fluent interface",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/finder/tree/v6.4.33"
+ },
+ "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-26T13:03:48+00:00"
+ },
+ {
+ "name": "symfony/http-foundation",
+ "version": "v6.4.33",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-foundation.git",
+ "reference": "f1a490cc9d595ba7ebe684220e625d1e472ad278"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f1a490cc9d595ba7ebe684220e625d1e472ad278",
+ "reference": "f1a490cc9d595ba7ebe684220e625d1e472ad278",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/polyfill-mbstring": "~1.1",
+ "symfony/polyfill-php83": "^1.27"
+ },
+ "conflict": {
+ "symfony/cache": "<6.4.12|>=7.0,<7.1.5"
+ },
+ "require-dev": {
+ "doctrine/dbal": "^2.13.1|^3|^4",
+ "predis/predis": "^1.1|^2.0",
+ "symfony/cache": "^6.4.12|^7.1.5",
+ "symfony/dependency-injection": "^5.4|^6.0|^7.0",
+ "symfony/expression-language": "^5.4|^6.0|^7.0",
+ "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4|^7.0",
+ "symfony/mime": "^5.4|^6.0|^7.0",
+ "symfony/rate-limiter": "^5.4|^6.0|^7.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/v6.4.33"
+ },
+ "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-27T15:04:55+00:00"
+ },
+ {
+ "name": "symfony/http-kernel",
+ "version": "v6.4.33",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/http-kernel.git",
+ "reference": "73fa5c999d7f741ca544a97d3c791cc97890ae4d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/http-kernel/zipball/73fa5c999d7f741ca544a97d3c791cc97890ae4d",
+ "reference": "73fa5c999d7f741ca544a97d3c791cc97890ae4d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "psr/log": "^1|^2|^3",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/error-handler": "^6.4|^7.0",
+ "symfony/event-dispatcher": "^5.4|^6.0|^7.0",
+ "symfony/http-foundation": "^6.4|^7.0",
+ "symfony/polyfill-ctype": "^1.8"
+ },
+ "conflict": {
+ "symfony/browser-kit": "<5.4",
+ "symfony/cache": "<5.4",
+ "symfony/config": "<6.1",
+ "symfony/console": "<5.4",
+ "symfony/dependency-injection": "<6.4",
+ "symfony/doctrine-bridge": "<5.4",
+ "symfony/form": "<5.4",
+ "symfony/http-client": "<5.4",
+ "symfony/http-client-contracts": "<2.5",
+ "symfony/mailer": "<5.4",
+ "symfony/messenger": "<5.4",
+ "symfony/translation": "<5.4",
+ "symfony/translation-contracts": "<2.5",
+ "symfony/twig-bridge": "<5.4",
+ "symfony/validator": "<6.4",
+ "symfony/var-dumper": "<6.3",
+ "twig/twig": "<2.13"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0|2.0|3.0"
+ },
+ "require-dev": {
+ "psr/cache": "^1.0|^2.0|^3.0",
+ "symfony/browser-kit": "^5.4|^6.0|^7.0",
+ "symfony/clock": "^6.2|^7.0",
+ "symfony/config": "^6.1|^7.0",
+ "symfony/console": "^5.4|^6.0|^7.0",
+ "symfony/css-selector": "^5.4|^6.0|^7.0",
+ "symfony/dependency-injection": "^6.4|^7.0",
+ "symfony/dom-crawler": "^5.4|^6.0|^7.0",
+ "symfony/expression-language": "^5.4|^6.0|^7.0",
+ "symfony/finder": "^5.4|^6.0|^7.0",
+ "symfony/http-client-contracts": "^2.5|^3",
+ "symfony/process": "^5.4|^6.0|^7.0",
+ "symfony/property-access": "^5.4.5|^6.0.5|^7.0",
+ "symfony/routing": "^5.4|^6.0|^7.0",
+ "symfony/serializer": "^6.4.4|^7.0.4",
+ "symfony/stopwatch": "^5.4|^6.0|^7.0",
+ "symfony/translation": "^5.4|^6.0|^7.0",
+ "symfony/translation-contracts": "^2.5|^3",
+ "symfony/uid": "^5.4|^6.0|^7.0",
+ "symfony/validator": "^6.4|^7.0",
+ "symfony/var-dumper": "^5.4|^6.4|^7.0",
+ "symfony/var-exporter": "^6.2|^7.0",
+ "twig/twig": "^2.13|^3.0.4"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\HttpKernel\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides a structured process for converting a Request into a Response",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/http-kernel/tree/v6.4.33"
+ },
+ "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-28T10:02:13+00:00"
+ },
+ {
+ "name": "symfony/mailer",
+ "version": "v6.4.31",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/mailer.git",
+ "reference": "8835f93333474780fda1b987cae37e33c3e026ca"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/mailer/zipball/8835f93333474780fda1b987cae37e33c3e026ca",
+ "reference": "8835f93333474780fda1b987cae37e33c3e026ca",
+ "shasum": ""
+ },
+ "require": {
+ "egulias/email-validator": "^2.1.10|^3|^4",
+ "php": ">=8.1",
+ "psr/event-dispatcher": "^1",
+ "psr/log": "^1|^2|^3",
+ "symfony/event-dispatcher": "^5.4|^6.0|^7.0",
+ "symfony/mime": "^6.2|^7.0",
+ "symfony/service-contracts": "^2.5|^3"
+ },
+ "conflict": {
+ "symfony/http-client-contracts": "<2.5",
+ "symfony/http-kernel": "<5.4",
+ "symfony/messenger": "<6.2",
+ "symfony/mime": "<6.2",
+ "symfony/twig-bridge": "<6.2.1"
+ },
+ "require-dev": {
+ "symfony/console": "^5.4|^6.0|^7.0",
+ "symfony/http-client": "^5.4|^6.0|^7.0",
+ "symfony/messenger": "^6.2|^7.0",
+ "symfony/twig-bridge": "^6.2|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Mailer\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Helps sending emails",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/mailer/tree/v6.4.31"
+ },
+ "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-12-12T07:33:25+00:00"
+ },
+ {
+ "name": "symfony/mime",
+ "version": "v6.4.32",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/mime.git",
+ "reference": "7409686879ca36c09fc970a5fa8ff6e93504dba4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/mime/zipball/7409686879ca36c09fc970a5fa8ff6e93504dba4",
+ "reference": "7409686879ca36c09fc970a5fa8ff6e93504dba4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "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": "<3.2.2",
+ "phpdocumentor/type-resolver": "<1.4.0",
+ "symfony/mailer": "<5.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": "^3.0|^4.0|^5.0",
+ "symfony/dependency-injection": "^5.4|^6.0|^7.0",
+ "symfony/process": "^5.4|^6.4|^7.0",
+ "symfony/property-access": "^5.4|^6.0|^7.0",
+ "symfony/property-info": "^5.4|^6.0|^7.0",
+ "symfony/serializer": "^6.4.3|^7.0.3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Mime\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Allows manipulating MIME messages",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "mime",
+ "mime-type"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/mime/tree/v6.4.32"
+ },
+ "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-04T11:53:14+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-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": "v6.4.33",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "c46e854e79b52d07666e43924a20cb6dc546644e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/c46e854e79b52d07666e43924a20cb6dc546644e",
+ "reference": "c46e854e79b52d07666e43924a20cb6dc546644e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Process\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Executes commands in sub-processes",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/process/tree/v6.4.33"
+ },
+ "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-23T16:02:12+00:00"
+ },
+ {
+ "name": "symfony/routing",
+ "version": "v6.4.32",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/routing.git",
+ "reference": "0dc6253e864e71b486e8ba4970a56ab849106ebe"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/routing/zipball/0dc6253e864e71b486e8ba4970a56ab849106ebe",
+ "reference": "0dc6253e864e71b486e8ba4970a56ab849106ebe",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "symfony/deprecation-contracts": "^2.5|^3"
+ },
+ "conflict": {
+ "doctrine/annotations": "<1.12",
+ "symfony/config": "<6.2",
+ "symfony/dependency-injection": "<5.4",
+ "symfony/yaml": "<5.4"
+ },
+ "require-dev": {
+ "doctrine/annotations": "^1.12|^2",
+ "psr/log": "^1|^2|^3",
+ "symfony/config": "^6.2|^7.0",
+ "symfony/dependency-injection": "^5.4|^6.0|^7.0",
+ "symfony/expression-language": "^5.4|^6.0|^7.0",
+ "symfony/http-foundation": "^5.4|^6.0|^7.0",
+ "symfony/yaml": "^5.4|^6.0|^7.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/v6.4.32"
+ },
+ "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-12T08:31:19+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": "v6.4.30",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/string.git",
+ "reference": "50590a057841fa6bf69d12eceffce3465b9e32cb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/string/zipball/50590a057841fa6bf69d12eceffce3465b9e32cb",
+ "reference": "50590a057841fa6bf69d12eceffce3465b9e32cb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-intl-grapheme": "~1.0",
+ "symfony/polyfill-intl-normalizer": "~1.0",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "conflict": {
+ "symfony/translation-contracts": "<2.5"
+ },
+ "require-dev": {
+ "symfony/http-client": "^5.4|^6.0|^7.0",
+ "symfony/intl": "^6.2|^7.0",
+ "symfony/translation-contracts": "^2.5|^3.0",
+ "symfony/var-exporter": "^5.4|^6.0|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "Resources/functions.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\String\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "grapheme",
+ "i18n",
+ "string",
+ "unicode",
+ "utf-8",
+ "utf8"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/string/tree/v6.4.30"
+ },
+ "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-21T18:03:05+00:00"
+ },
+ {
+ "name": "symfony/translation",
+ "version": "v6.4.32",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/translation.git",
+ "reference": "d6cc8e2fdd484f2f41d25938b0e8e3915de3cfbc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/d6cc8e2fdd484f2f41d25938b0e8e3915de3cfbc",
+ "reference": "d6cc8e2fdd484f2f41d25938b0e8e3915de3cfbc",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/translation-contracts": "^2.5|^3.0"
+ },
+ "conflict": {
+ "symfony/config": "<5.4",
+ "symfony/console": "<5.4",
+ "symfony/dependency-injection": "<5.4",
+ "symfony/http-client-contracts": "<2.5",
+ "symfony/http-kernel": "<5.4",
+ "symfony/service-contracts": "<2.5",
+ "symfony/twig-bundle": "<5.4",
+ "symfony/yaml": "<5.4"
+ },
+ "provide": {
+ "symfony/translation-implementation": "2.3|3.0"
+ },
+ "require-dev": {
+ "nikic/php-parser": "^4.18|^5.0",
+ "psr/log": "^1|^2|^3",
+ "symfony/config": "^5.4|^6.0|^7.0",
+ "symfony/console": "^5.4|^6.0|^7.0",
+ "symfony/dependency-injection": "^5.4|^6.0|^7.0",
+ "symfony/finder": "^5.4|^6.0|^7.0",
+ "symfony/http-client-contracts": "^2.5|^3.0",
+ "symfony/http-kernel": "^5.4|^6.0|^7.0",
+ "symfony/intl": "^5.4|^6.0|^7.0",
+ "symfony/polyfill-intl-icu": "^1.21",
+ "symfony/routing": "^5.4|^6.0|^7.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/yaml": "^5.4|^6.0|^7.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/v6.4.32"
+ },
+ "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-12T19:15:33+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": "v6.4.32",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/uid.git",
+ "reference": "6b973c385f00341b246f697d82dc01a09107acdd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/uid/zipball/6b973c385f00341b246f697d82dc01a09107acdd",
+ "reference": "6b973c385f00341b246f697d82dc01a09107acdd",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "symfony/polyfill-uuid": "^1.15"
+ },
+ "require-dev": {
+ "symfony/console": "^5.4|^6.0|^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Uid\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Grégoire Pineau",
+ "email": "lyrixx@lyrixx.info"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides an object-oriented API to generate and represent UIDs",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "UID",
+ "ulid",
+ "uuid"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/uid/tree/v6.4.32"
+ },
+ "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-12-23T15:07:59+00:00"
+ },
+ {
+ "name": "symfony/var-dumper",
+ "version": "v6.4.32",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/var-dumper.git",
+ "reference": "131fc9915e0343052af5ed5040401b481ca192aa"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/131fc9915e0343052af5ed5040401b481ca192aa",
+ "reference": "131fc9915e0343052af5ed5040401b481ca192aa",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/polyfill-mbstring": "~1.0"
+ },
+ "conflict": {
+ "symfony/console": "<5.4"
+ },
+ "require-dev": {
+ "symfony/console": "^5.4|^6.0|^7.0",
+ "symfony/error-handler": "^6.3|^7.0",
+ "symfony/http-kernel": "^5.4|^6.0|^7.0",
+ "symfony/process": "^5.4|^6.0|^7.0",
+ "symfony/uid": "^5.4|^6.0|^7.0",
+ "twig/twig": "^2.13|^3.0.4"
+ },
+ "bin": [
+ "Resources/bin/var-dump-server"
+ ],
+ "type": "library",
+ "autoload": {
+ "files": [
+ "Resources/functions/dump.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\VarDumper\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides mechanisms for walking through any arbitrary PHP variable",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "debug",
+ "dump"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/var-dumper/tree/v6.4.32"
+ },
+ "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-01T13:34:06+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": "fakerphp/faker",
+ "version": "v1.24.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/FakerPHP/Faker.git",
+ "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5",
+ "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4 || ^8.0",
+ "psr/container": "^1.0 || ^2.0",
+ "symfony/deprecation-contracts": "^2.2 || ^3.0"
+ },
+ "conflict": {
+ "fzaninotto/faker": "*"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.4.1",
+ "doctrine/persistence": "^1.3 || ^2.0",
+ "ext-intl": "*",
+ "phpunit/phpunit": "^9.5.26",
+ "symfony/phpunit-bridge": "^5.4.16"
+ },
+ "suggest": {
+ "doctrine/orm": "Required to use Faker\\ORM\\Doctrine",
+ "ext-curl": "Required by Faker\\Provider\\Image to download images.",
+ "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.",
+ "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.",
+ "ext-mbstring": "Required for multibyte Unicode string functionality."
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Faker\\": "src/Faker/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "François Zaninotto"
+ }
+ ],
+ "description": "Faker is a PHP library that generates fake data for you.",
+ "keywords": [
+ "data",
+ "faker",
+ "fixtures"
+ ],
+ "support": {
+ "issues": "https://github.com/FakerPHP/Faker/issues",
+ "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1"
+ },
+ "time": "2024-11-21T13:46:39+00:00"
+ },
+ {
+ "name": "filp/whoops",
+ "version": "2.18.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/filp/whoops.git",
+ "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/filp/whoops/zipball/d2102955e48b9fd9ab24280a7ad12ed552752c4d",
+ "reference": "d2102955e48b9fd9ab24280a7ad12ed552752c4d",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0",
+ "psr/log": "^1.0.1 || ^2.0 || ^3.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.0",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.8 || ^9.3.3",
+ "symfony/var-dumper": "^4.0 || ^5.0"
+ },
+ "suggest": {
+ "symfony/var-dumper": "Pretty print complex values better with var-dumper available",
+ "whoops/soap": "Formats errors as SOAP responses"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.7-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Whoops\\": "src/Whoops/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Filipe Dobreira",
+ "homepage": "https://github.com/filp",
+ "role": "Developer"
+ }
+ ],
+ "description": "php error handling for cool kids",
+ "homepage": "https://filp.github.io/whoops/",
+ "keywords": [
+ "error",
+ "exception",
+ "handling",
+ "library",
+ "throwable",
+ "whoops"
+ ],
+ "support": {
+ "issues": "https://github.com/filp/whoops/issues",
+ "source": "https://github.com/filp/whoops/tree/2.18.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/denis-sokolov",
+ "type": "github"
+ }
+ ],
+ "time": "2025-08-08T12:00:00+00:00"
+ },
+ {
+ "name": "hamcrest/hamcrest-php",
+ "version": "v2.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/hamcrest/hamcrest-php.git",
+ "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487",
+ "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4|^8.0"
+ },
+ "replace": {
+ "cordoval/hamcrest-php": "*",
+ "davedevelopment/hamcrest-php": "*",
+ "kodova/hamcrest-php": "*"
+ },
+ "require-dev": {
+ "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0",
+ "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "hamcrest"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "description": "This is the PHP port of Hamcrest Matchers",
+ "keywords": [
+ "test"
+ ],
+ "support": {
+ "issues": "https://github.com/hamcrest/hamcrest-php/issues",
+ "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1"
+ },
+ "time": "2025-04-30T06:54:44+00:00"
+ },
+ {
+ "name": "laravel/pint",
+ "version": "v1.20.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/pint.git",
+ "reference": "53072e8ea22213a7ed168a8a15b96fbb8b82d44b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/pint/zipball/53072e8ea22213a7ed168a8a15b96fbb8b82d44b",
+ "reference": "53072e8ea22213a7ed168a8a15b96fbb8b82d44b",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "ext-tokenizer": "*",
+ "ext-xml": "*",
+ "php": "^8.1.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.66.0",
+ "illuminate/view": "^10.48.25",
+ "larastan/larastan": "^2.9.12",
+ "laravel-zero/framework": "^10.48.25",
+ "mockery/mockery": "^1.6.12",
+ "nunomaduro/termwind": "^1.17.0",
+ "pestphp/pest": "^2.36.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": [
+ "format",
+ "formatter",
+ "lint",
+ "linter",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/laravel/pint/issues",
+ "source": "https://github.com/laravel/pint"
+ },
+ "time": "2025-01-14T16:20:53+00:00"
+ },
+ {
+ "name": "laravel/sail",
+ "version": "v1.53.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/sail.git",
+ "reference": "e340eaa2bea9b99192570c48ed837155dbf24fbb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/sail/zipball/e340eaa2bea9b99192570c48ed837155dbf24fbb",
+ "reference": "e340eaa2bea9b99192570c48ed837155dbf24fbb",
+ "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-02-06T12:16:02+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": "v7.12.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nunomaduro/collision.git",
+ "reference": "995245421d3d7593a6960822063bdba4f5d7cf1a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nunomaduro/collision/zipball/995245421d3d7593a6960822063bdba4f5d7cf1a",
+ "reference": "995245421d3d7593a6960822063bdba4f5d7cf1a",
+ "shasum": ""
+ },
+ "require": {
+ "filp/whoops": "^2.17.0",
+ "nunomaduro/termwind": "^1.17.0",
+ "php": "^8.1.0",
+ "symfony/console": "^6.4.17"
+ },
+ "conflict": {
+ "laravel/framework": ">=11.0.0"
+ },
+ "require-dev": {
+ "brianium/paratest": "^7.4.8",
+ "laravel/framework": "^10.48.29",
+ "laravel/pint": "^1.21.2",
+ "laravel/sail": "^1.41.0",
+ "laravel/sanctum": "^3.3.3",
+ "laravel/tinker": "^2.10.1",
+ "nunomaduro/larastan": "^2.10.0",
+ "orchestra/testbench-core": "^8.35.0",
+ "pestphp/pest": "^2.36.0",
+ "phpunit/phpunit": "^10.5.36",
+ "sebastian/environment": "^6.1.0",
+ "spatie/laravel-ignition": "^2.9.1"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider"
+ ]
+ }
+ },
+ "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",
+ "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": "2025-03-14T22:35:49+00:00"
+ },
+ {
+ "name": "phar-io/manifest",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-phar": "*",
+ "ext-xmlwriter": "*",
+ "phar-io/version": "^3.0.1",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+ "support": {
+ "issues": "https://github.com/phar-io/manifest/issues",
+ "source": "https://github.com/phar-io/manifest/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-03T12:33:53+00:00"
+ },
+ {
+ "name": "phar-io/version",
+ "version": "3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Library for handling version information and constraints",
+ "support": {
+ "issues": "https://github.com/phar-io/version/issues",
+ "source": "https://github.com/phar-io/version/tree/3.2.1"
+ },
+ "time": "2022-02-21T01:04:05+00:00"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "10.1.16",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "7e308268858ed6baedc8704a304727d20bc07c77"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77",
+ "reference": "7e308268858ed6baedc8704a304727d20bc07c77",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-xmlwriter": "*",
+ "nikic/php-parser": "^4.19.1 || ^5.1.0",
+ "php": ">=8.1",
+ "phpunit/php-file-iterator": "^4.1.0",
+ "phpunit/php-text-template": "^3.0.1",
+ "sebastian/code-unit-reverse-lookup": "^3.0.0",
+ "sebastian/complexity": "^3.2.0",
+ "sebastian/environment": "^6.1.0",
+ "sebastian/lines-of-code": "^2.0.2",
+ "sebastian/version": "^4.0.1",
+ "theseer/tokenizer": "^1.2.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.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": "10.1.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/10.1.16"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-08-22T04:31:57+00:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "4.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c",
+ "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.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": "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/4.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-08-31T06:24:48+00:00"
+ },
+ {
+ "name": "phpunit/php-invoker",
+ "version": "4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-invoker.git",
+ "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7",
+ "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "ext-pcntl": "*",
+ "phpunit/phpunit": "^10.0"
+ },
+ "suggest": {
+ "ext-pcntl": "*"
+ },
+ "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": "Invoke callables with a timeout",
+ "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+ "keywords": [
+ "process"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+ "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T06:56:09+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "3.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748",
+ "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "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/3.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-08-31T14:07:24+00:00"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "6.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d",
+ "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.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": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T06:57:52+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "10.5.63",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "33198268dad71e926626b618f3ec3966661e4d90"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90",
+ "reference": "33198268dad71e926626b618f3ec3966661e4d90",
+ "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.1",
+ "phpunit/php-code-coverage": "^10.1.16",
+ "phpunit/php-file-iterator": "^4.1.0",
+ "phpunit/php-invoker": "^4.0.0",
+ "phpunit/php-text-template": "^3.0.1",
+ "phpunit/php-timer": "^6.0.0",
+ "sebastian/cli-parser": "^2.0.1",
+ "sebastian/code-unit": "^2.0.0",
+ "sebastian/comparator": "^5.0.5",
+ "sebastian/diff": "^5.1.1",
+ "sebastian/environment": "^6.1.0",
+ "sebastian/exporter": "^5.1.4",
+ "sebastian/global-state": "^6.0.2",
+ "sebastian/object-enumerator": "^5.0.0",
+ "sebastian/recursion-context": "^5.0.1",
+ "sebastian/type": "^4.0.0",
+ "sebastian/version": "^4.0.1"
+ },
+ "suggest": {
+ "ext-soap": "To be able to generate mocks based on WSDL files"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "10.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/10.5.63"
+ },
+ "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-01-27T05:48:37+00:00"
+ },
+ {
+ "name": "sebastian/cli-parser",
+ "version": "2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/cli-parser.git",
+ "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084",
+ "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for 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/2.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-02T07:12:49+00:00"
+ },
+ {
+ "name": "sebastian/code-unit",
+ "version": "2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit.git",
+ "reference": "a81fee9eef0b7a76af11d121767abc44c104e503"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503",
+ "reference": "a81fee9eef0b7a76af11d121767abc44c104e503",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/code-unit",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit/issues",
+ "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T06:58:43+00:00"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d",
+ "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
+ "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T06:59:15+00:00"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d",
+ "reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-mbstring": "*",
+ "php": ">=8.1",
+ "sebastian/diff": "^5.0",
+ "sebastian/exporter": "^5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.5"
+ },
+ "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"
+ },
+ {
+ "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/5.0.5"
+ },
+ "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:25:16+00:00"
+ },
+ {
+ "name": "sebastian/complexity",
+ "version": "3.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/complexity.git",
+ "reference": "68ff824baeae169ec9f2137158ee529584553799"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799",
+ "reference": "68ff824baeae169ec9f2137158ee529584553799",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^4.18 || ^5.0",
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "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/3.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-12-21T08:37:17+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "5.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e",
+ "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0",
+ "symfony/process": "^6.4"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.1-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/5.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-02T07:15:17+00:00"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "6.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "8074dbcd93529b357029f5cc5058fd3e43666984"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984",
+ "reference": "8074dbcd93529b357029f5cc5058fd3e43666984",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "suggest": {
+ "ext-posix": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "https://github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/environment/issues",
+ "security": "https://github.com/sebastianbergmann/environment/security/policy",
+ "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-23T08:47:14+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "5.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "0735b90f4da94969541dac1da743446e276defa6"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/0735b90f4da94969541dac1da743446e276defa6",
+ "reference": "0735b90f4da94969541dac1da743446e276defa6",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": ">=8.1",
+ "sebastian/recursion-context": "^5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.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": "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/5.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/exporter",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-09-24T06:09:11+00:00"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "6.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9",
+ "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "sebastian/object-reflector": "^3.0",
+ "sebastian/recursion-context": "^5.0"
+ },
+ "require-dev": {
+ "ext-dom": "*",
+ "phpunit/phpunit": "^10.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"
+ }
+ ],
+ "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/6.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-02T07:19:19+00:00"
+ },
+ {
+ "name": "sebastian/lines-of-code",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+ "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0",
+ "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^4.18 || ^5.0",
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for 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/2.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-12-21T08:38:20+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906",
+ "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "sebastian/object-reflector": "^3.0",
+ "sebastian/recursion-context": "^5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.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": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T07:08:32+00:00"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "24ed13d98130f0e7122df55d06c5c4942a577957"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957",
+ "reference": "24ed13d98130f0e7122df55d06c5c4942a577957",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Allows reflection of object attributes, including inherited and non-public ones",
+ "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T07:06:18+00:00"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "5.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/47e34210757a2f37a97dcd207d032e1b01e64c7a",
+ "reference": "47e34210757a2f37a97dcd207d032e1b01e64c7a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.5"
+ },
+ "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"
+ },
+ {
+ "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/5.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-10T07:50:56+00:00"
+ },
+ {
+ "name": "sebastian/type",
+ "version": "4.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/type.git",
+ "reference": "462699a16464c3944eefc02ebdd77882bd3925bf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf",
+ "reference": "462699a16464c3944eefc02ebdd77882bd3925bf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.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": "Collection of value objects that represent the types of the PHP type system",
+ "homepage": "https://github.com/sebastianbergmann/type",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/type/issues",
+ "source": "https://github.com/sebastianbergmann/type/tree/4.0.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-03T07:10:45+00:00"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17",
+ "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "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 that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/version/issues",
+ "source": "https://github.com/sebastianbergmann/version/tree/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2023-02-07T11:34:05+00:00"
+ },
+ {
+ "name": "spatie/backtrace",
+ "version": "1.8.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/backtrace.git",
+ "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/backtrace/zipball/8c0f16a59ae35ec8c62d85c3c17585158f430110",
+ "reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.3 || ^8.0"
+ },
+ "require-dev": {
+ "ext-json": "*",
+ "laravel/serializable-closure": "^1.3 || ^2.0",
+ "phpunit/phpunit": "^9.3 || ^11.4.3",
+ "spatie/phpunit-snapshot-assertions": "^4.2 || ^5.1.6",
+ "symfony/var-dumper": "^5.1 || ^6.0 || ^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\Backtrace\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Freek Van de Herten",
+ "email": "freek@spatie.be",
+ "homepage": "https://spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "A better backtrace",
+ "homepage": "https://github.com/spatie/backtrace",
+ "keywords": [
+ "Backtrace",
+ "spatie"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/backtrace/issues",
+ "source": "https://github.com/spatie/backtrace/tree/1.8.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/spatie",
+ "type": "github"
+ },
+ {
+ "url": "https://spatie.be/open-source/support-us",
+ "type": "other"
+ }
+ ],
+ "time": "2025-08-26T08:22:30+00:00"
+ },
+ {
+ "name": "spatie/error-solutions",
+ "version": "1.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/error-solutions.git",
+ "reference": "e495d7178ca524f2dd0fe6a1d99a1e608e1c9936"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/error-solutions/zipball/e495d7178ca524f2dd0fe6a1d99a1e608e1c9936",
+ "reference": "e495d7178ca524f2dd0fe6a1d99a1e608e1c9936",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.0"
+ },
+ "require-dev": {
+ "illuminate/broadcasting": "^10.0|^11.0|^12.0",
+ "illuminate/cache": "^10.0|^11.0|^12.0",
+ "illuminate/support": "^10.0|^11.0|^12.0",
+ "livewire/livewire": "^2.11|^3.5.20",
+ "openai-php/client": "^0.10.1",
+ "orchestra/testbench": "8.22.3|^9.0|^10.0",
+ "pestphp/pest": "^2.20|^3.0",
+ "phpstan/phpstan": "^2.1",
+ "psr/simple-cache": "^3.0",
+ "psr/simple-cache-implementation": "^3.0",
+ "spatie/ray": "^1.28",
+ "symfony/cache": "^5.4|^6.0|^7.0",
+ "symfony/process": "^5.4|^6.0|^7.0",
+ "vlucas/phpdotenv": "^5.5"
+ },
+ "suggest": {
+ "openai-php/client": "Require get solutions from OpenAI",
+ "simple-cache-implementation": "To cache solutions from OpenAI"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Spatie\\Ignition\\": "legacy/ignition",
+ "Spatie\\ErrorSolutions\\": "src",
+ "Spatie\\LaravelIgnition\\": "legacy/laravel-ignition"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ruben Van Assche",
+ "email": "ruben@spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "This is my package error-solutions",
+ "homepage": "https://github.com/spatie/error-solutions",
+ "keywords": [
+ "error-solutions",
+ "spatie"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/error-solutions/issues",
+ "source": "https://github.com/spatie/error-solutions/tree/1.1.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/Spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-14T12:29:50+00:00"
+ },
+ {
+ "name": "spatie/flare-client-php",
+ "version": "1.10.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/flare-client-php.git",
+ "reference": "bf1716eb98bd689451b071548ae9e70738dce62f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/bf1716eb98bd689451b071548ae9e70738dce62f",
+ "reference": "bf1716eb98bd689451b071548ae9e70738dce62f",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/pipeline": "^8.0|^9.0|^10.0|^11.0|^12.0",
+ "php": "^8.0",
+ "spatie/backtrace": "^1.6.1",
+ "symfony/http-foundation": "^5.2|^6.0|^7.0",
+ "symfony/mime": "^5.2|^6.0|^7.0",
+ "symfony/process": "^5.2|^6.0|^7.0",
+ "symfony/var-dumper": "^5.2|^6.0|^7.0"
+ },
+ "require-dev": {
+ "dms/phpunit-arraysubset-asserts": "^0.5.0",
+ "pestphp/pest": "^1.20|^2.0",
+ "phpstan/extension-installer": "^1.1",
+ "phpstan/phpstan-deprecation-rules": "^1.0",
+ "phpstan/phpstan-phpunit": "^1.0",
+ "spatie/pest-plugin-snapshots": "^1.0|^2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.3.x-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/helpers.php"
+ ],
+ "psr-4": {
+ "Spatie\\FlareClient\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Send PHP errors to Flare",
+ "homepage": "https://github.com/spatie/flare-client-php",
+ "keywords": [
+ "exception",
+ "flare",
+ "reporting",
+ "spatie"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/flare-client-php/issues",
+ "source": "https://github.com/spatie/flare-client-php/tree/1.10.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-14T13:42:06+00:00"
+ },
+ {
+ "name": "spatie/ignition",
+ "version": "1.15.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/ignition.git",
+ "reference": "31f314153020aee5af3537e507fef892ffbf8c85"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/ignition/zipball/31f314153020aee5af3537e507fef892ffbf8c85",
+ "reference": "31f314153020aee5af3537e507fef892ffbf8c85",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "php": "^8.0",
+ "spatie/error-solutions": "^1.0",
+ "spatie/flare-client-php": "^1.7",
+ "symfony/console": "^5.4|^6.0|^7.0",
+ "symfony/var-dumper": "^5.4|^6.0|^7.0"
+ },
+ "require-dev": {
+ "illuminate/cache": "^9.52|^10.0|^11.0|^12.0",
+ "mockery/mockery": "^1.4",
+ "pestphp/pest": "^1.20|^2.0",
+ "phpstan/extension-installer": "^1.1",
+ "phpstan/phpstan-deprecation-rules": "^1.0",
+ "phpstan/phpstan-phpunit": "^1.0",
+ "psr/simple-cache-implementation": "*",
+ "symfony/cache": "^5.4|^6.0|^7.0",
+ "symfony/process": "^5.4|^6.0|^7.0",
+ "vlucas/phpdotenv": "^5.5"
+ },
+ "suggest": {
+ "openai-php/client": "Require get solutions from OpenAI",
+ "simple-cache-implementation": "To cache solutions from OpenAI"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Spatie\\Ignition\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Spatie",
+ "email": "info@spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "A beautiful error page for PHP applications.",
+ "homepage": "https://flareapp.io/ignition",
+ "keywords": [
+ "error",
+ "flare",
+ "laravel",
+ "page"
+ ],
+ "support": {
+ "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction",
+ "forum": "https://twitter.com/flareappio",
+ "issues": "https://github.com/spatie/ignition/issues",
+ "source": "https://github.com/spatie/ignition"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-21T14:31:39+00:00"
+ },
+ {
+ "name": "spatie/laravel-ignition",
+ "version": "2.9.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/laravel-ignition.git",
+ "reference": "1baee07216d6748ebd3a65ba97381b051838707a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/1baee07216d6748ebd3a65ba97381b051838707a",
+ "reference": "1baee07216d6748ebd3a65ba97381b051838707a",
+ "shasum": ""
+ },
+ "require": {
+ "ext-curl": "*",
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "illuminate/support": "^10.0|^11.0|^12.0",
+ "php": "^8.1",
+ "spatie/ignition": "^1.15",
+ "symfony/console": "^6.2.3|^7.0",
+ "symfony/var-dumper": "^6.2.3|^7.0"
+ },
+ "require-dev": {
+ "livewire/livewire": "^2.11|^3.3.5",
+ "mockery/mockery": "^1.5.1",
+ "openai-php/client": "^0.8.1|^0.10",
+ "orchestra/testbench": "8.22.3|^9.0|^10.0",
+ "pestphp/pest": "^2.34|^3.7",
+ "phpstan/extension-installer": "^1.3.1",
+ "phpstan/phpstan-deprecation-rules": "^1.1.1|^2.0",
+ "phpstan/phpstan-phpunit": "^1.3.16|^2.0",
+ "vlucas/phpdotenv": "^5.5"
+ },
+ "suggest": {
+ "openai-php/client": "Require get solutions from OpenAI",
+ "psr/simple-cache-implementation": "Needed to cache solutions from OpenAI"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "aliases": {
+ "Flare": "Spatie\\LaravelIgnition\\Facades\\Flare"
+ },
+ "providers": [
+ "Spatie\\LaravelIgnition\\IgnitionServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/helpers.php"
+ ],
+ "psr-4": {
+ "Spatie\\LaravelIgnition\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Spatie",
+ "email": "info@spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "A beautiful error page for Laravel applications.",
+ "homepage": "https://flareapp.io/ignition",
+ "keywords": [
+ "error",
+ "flare",
+ "laravel",
+ "page"
+ ],
+ "support": {
+ "docs": "https://flareapp.io/docs/ignition-for-laravel/introduction",
+ "forum": "https://twitter.com/flareappio",
+ "issues": "https://github.com/spatie/laravel-ignition/issues",
+ "source": "https://github.com/spatie/laravel-ignition"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2025-02-20T13:13:55+00:00"
+ },
+ {
+ "name": "symfony/yaml",
+ "version": "v6.4.30",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/yaml.git",
+ "reference": "8207ae83da19ee3748d6d4f567b4d9a7c656e331"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/8207ae83da19ee3748d6d4f567b4d9a7c656e331",
+ "reference": "8207ae83da19ee3748d6d4f567b4d9a7c656e331",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/polyfill-ctype": "^1.8"
+ },
+ "conflict": {
+ "symfony/console": "<5.4"
+ },
+ "require-dev": {
+ "symfony/console": "^5.4|^6.0|^7.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/v6.4.30"
+ },
+ "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-12-02T11:50:18+00:00"
+ },
+ {
+ "name": "theseer/tokenizer",
+ "version": "1.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "b7489ce515e168639d17feec34b8847c326b0b3c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c",
+ "reference": "b7489ce515e168639d17feec34b8847c326b0b3c",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "support": {
+ "issues": "https://github.com/theseer/tokenizer/issues",
+ "source": "https://github.com/theseer/tokenizer/tree/1.3.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2025-11-17T20:03:58+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": true,
+ "prefer-lowest": false,
+ "platform": {
+ "php": "^8.1"
+ },
+ "platform-dev": [],
+ "plugin-api-version": "2.3.0"
+}
diff --git a/config/app.php b/config/app.php
new file mode 100644
index 0000000..1feafed
--- /dev/null
+++ b/config/app.php
@@ -0,0 +1,190 @@
+ env('APP_NAME', 'Laravel'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application Environment
+ |--------------------------------------------------------------------------
+ |
+ | This value determines the "environment" your application is currently
+ | running in. This may determine how you prefer to configure various
+ | services the application utilizes. Set this in your ".env" file.
+ |
+ */
+
+ 'env' => env('APP_ENV', 'production'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application Debug Mode
+ |--------------------------------------------------------------------------
+ |
+ | When your application is in debug mode, detailed error messages with
+ | stack traces will be shown on every error that occurs within your
+ | application. If disabled, a simple generic error page is shown.
+ |
+ */
+
+ 'debug' => (bool) env('APP_DEBUG', false),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application URL
+ |--------------------------------------------------------------------------
+ |
+ | This URL is used by the console to properly generate URLs when using
+ | the Artisan command line tool. You should set this to the root of
+ | your application so that it is used when running Artisan tasks.
+ |
+ */
+
+ 'url' => env('APP_URL', 'http://localhost'),
+
+ 'asset_url' => env('ASSET_URL'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application Timezone
+ |--------------------------------------------------------------------------
+ |
+ | Here you may specify the default timezone for your application, which
+ | will be used by the PHP date and date-time functions. We have gone
+ | ahead and set this to a sensible default for you out of the box.
+ |
+ */
+
+ 'timezone' => 'UTC',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application Locale Configuration
+ |--------------------------------------------------------------------------
+ |
+ | The application locale determines the default locale that will be used
+ | by the translation service provider. You are free to set this value
+ | to any of the locales which will be supported by the application.
+ |
+ */
+
+ 'locale' => 'en',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application Fallback Locale
+ |--------------------------------------------------------------------------
+ |
+ | The fallback locale determines the locale to use when the current one
+ | is not available. You may change the value to correspond to any of
+ | the language folders that are provided through your application.
+ |
+ */
+
+ 'fallback_locale' => 'en',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Faker Locale
+ |--------------------------------------------------------------------------
+ |
+ | This locale will be used by the Faker PHP library when generating fake
+ | data for your database seeds. For example, this will be used to get
+ | localized telephone numbers, street address information and more.
+ |
+ */
+
+ 'faker_locale' => 'en_US',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Encryption Key
+ |--------------------------------------------------------------------------
+ |
+ | This key is used by the Illuminate encrypter service and should be set
+ | to a random, 32 character string, otherwise these encrypted strings
+ | will not be safe. Please do this before deploying an application!
+ |
+ */
+
+ 'key' => env('APP_KEY'),
+
+ 'cipher' => 'AES-256-CBC',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Maintenance Mode Driver
+ |--------------------------------------------------------------------------
+ |
+ | These configuration options determine the driver used to determine and
+ | manage Laravel's "maintenance mode" status. The "cache" driver will
+ | allow maintenance mode to be controlled across multiple machines.
+ |
+ | Supported drivers: "file", "cache"
+ |
+ */
+
+ 'maintenance' => [
+ 'driver' => 'file',
+ // 'store' => 'redis',
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Autoloaded Service Providers
+ |--------------------------------------------------------------------------
+ |
+ | The service providers listed here will be automatically loaded on the
+ | request to your application. Feel free to add your own services to
+ | this array to grant expanded functionality to your applications.
+ |
+ */
+
+ 'providers' => ServiceProvider::defaultProviders()->merge([
+ /*
+ * Package Service Providers...
+ */
+
+ /*
+ * Application Service Providers...
+ */
+ App\Providers\AppServiceProvider::class,
+ App\Providers\AuthServiceProvider::class,
+ // App\Providers\BroadcastServiceProvider::class,
+ App\Providers\EventServiceProvider::class,
+ App\Providers\RouteServiceProvider::class,
+
+ App\Providers\PxgServiceProvider::class,
+ ])->toArray(),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Class Aliases
+ |--------------------------------------------------------------------------
+ |
+ | This array of class aliases will be registered when this application
+ | is started. However, feel free to register as many as you wish as
+ | the aliases are "lazy" loaded so they don't hinder performance.
+ |
+ */
+
+ 'aliases' => Facade::defaultAliases()->merge([
+ // 'Example' => App\Facades\Example::class,
+ ])->toArray(),
+
+];
diff --git a/config/auth.php b/config/auth.php
new file mode 100644
index 0000000..9548c15
--- /dev/null
+++ b/config/auth.php
@@ -0,0 +1,115 @@
+ [
+ 'guard' => 'web',
+ 'passwords' => 'users',
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Authentication Guards
+ |--------------------------------------------------------------------------
+ |
+ | Next, you may define every authentication guard for your application.
+ | Of course, a great default configuration has been defined for you
+ | here which uses session storage and the Eloquent user provider.
+ |
+ | All authentication drivers have a user provider. This defines how the
+ | users are actually retrieved out of your database or other storage
+ | mechanisms used by this application to persist your user's data.
+ |
+ | Supported: "session"
+ |
+ */
+
+ 'guards' => [
+ 'web' => [
+ 'driver' => 'session',
+ 'provider' => 'users',
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | User Providers
+ |--------------------------------------------------------------------------
+ |
+ | All authentication drivers have a user provider. This defines how the
+ | users are actually retrieved out of your database or other storage
+ | mechanisms used by this application to persist your user's data.
+ |
+ | If you have multiple user tables or models you may configure multiple
+ | sources which represent each model / table. These sources may then
+ | be assigned to any extra authentication guards you have defined.
+ |
+ | Supported: "database", "eloquent"
+ |
+ */
+
+ 'providers' => [
+ 'users' => [
+ 'driver' => 'eloquent',
+ 'model' => App\Models\User::class,
+ ],
+
+ // 'users' => [
+ // 'driver' => 'database',
+ // 'table' => 'users',
+ // ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Resetting Passwords
+ |--------------------------------------------------------------------------
+ |
+ | You may specify multiple password reset configurations if you have more
+ | than one user table or model in the application and you want to have
+ | separate password reset settings based on the specific user types.
+ |
+ | The 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' => 'password_reset_tokens',
+ 'expire' => 60,
+ 'throttle' => 60,
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Password Confirmation Timeout
+ |--------------------------------------------------------------------------
+ |
+ | Here you may define the amount of seconds before a password confirmation
+ | times out and the user is prompted to re-enter their password via the
+ | confirmation screen. By default, the timeout lasts for three hours.
+ |
+ */
+
+ 'password_timeout' => 10800,
+
+];
diff --git a/config/broadcasting.php b/config/broadcasting.php
new file mode 100644
index 0000000..2410485
--- /dev/null
+++ b/config/broadcasting.php
@@ -0,0 +1,71 @@
+ env('BROADCAST_DRIVER', 'null'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Broadcast Connections
+ |--------------------------------------------------------------------------
+ |
+ | Here you may define all of the broadcast connections that will be used
+ | to broadcast events to other systems or over websockets. Samples of
+ | each available type of connection are provided inside this array.
+ |
+ */
+
+ 'connections' => [
+
+ 'pusher' => [
+ 'driver' => 'pusher',
+ 'key' => env('PUSHER_APP_KEY'),
+ 'secret' => env('PUSHER_APP_SECRET'),
+ 'app_id' => env('PUSHER_APP_ID'),
+ 'options' => [
+ 'cluster' => env('PUSHER_APP_CLUSTER'),
+ 'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
+ 'port' => env('PUSHER_PORT', 443),
+ 'scheme' => env('PUSHER_SCHEME', 'https'),
+ 'encrypted' => true,
+ 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
+ ],
+ 'client_options' => [
+ // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
+ ],
+ ],
+
+ 'ably' => [
+ 'driver' => 'ably',
+ 'key' => env('ABLY_KEY'),
+ ],
+
+ 'redis' => [
+ 'driver' => 'redis',
+ 'connection' => 'default',
+ ],
+
+ 'log' => [
+ 'driver' => 'log',
+ ],
+
+ 'null' => [
+ 'driver' => 'null',
+ ],
+
+ ],
+
+];
diff --git a/config/cache.php b/config/cache.php
new file mode 100644
index 0000000..d4171e2
--- /dev/null
+++ b/config/cache.php
@@ -0,0 +1,111 @@
+ env('CACHE_DRIVER', 'file'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Cache Stores
+ |--------------------------------------------------------------------------
+ |
+ | Here you may define all of the cache "stores" for your application as
+ | well as their drivers. You may even define multiple stores for the
+ | same cache driver to group types of items stored in your caches.
+ |
+ | Supported drivers: "apc", "array", "database", "file",
+ | "memcached", "redis", "dynamodb", "octane", "null"
+ |
+ */
+
+ 'stores' => [
+
+ 'apc' => [
+ 'driver' => 'apc',
+ ],
+
+ 'array' => [
+ 'driver' => 'array',
+ 'serialize' => false,
+ ],
+
+ 'database' => [
+ 'driver' => 'database',
+ 'table' => 'cache',
+ 'connection' => null,
+ 'lock_connection' => null,
+ ],
+
+ 'file' => [
+ 'driver' => 'file',
+ 'path' => storage_path('framework/cache/data'),
+ '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' => 'cache',
+ 'lock_connection' => 'default',
+ ],
+
+ 'dynamodb' => [
+ 'driver' => 'dynamodb',
+ 'key' => env('AWS_ACCESS_KEY_ID'),
+ 'secret' => env('AWS_SECRET_ACCESS_KEY'),
+ 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
+ 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
+ 'endpoint' => env('DYNAMODB_ENDPOINT'),
+ ],
+
+ 'octane' => [
+ 'driver' => 'octane',
+ ],
+
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Cache Key Prefix
+ |--------------------------------------------------------------------------
+ |
+ | When utilizing the APC, database, memcached, Redis, or DynamoDB cache
+ | stores there might be other applications using the same cache. For
+ | that reason, you may prefix every cache key to avoid collisions.
+ |
+ */
+
+ 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'),
+
+];
diff --git a/config/cors.php b/config/cors.php
new file mode 100644
index 0000000..fb92cfc
--- /dev/null
+++ b/config/cors.php
@@ -0,0 +1,34 @@
+ ['api/*', 'sanctum/csrf-cookie'],
+
+ 'allowed_methods' => ['*'],
+
+ 'allowed_origins' => array_values(array_filter(array_map('trim', explode(',', env('CORS_ALLOWED_ORIGINS', 'http://localhost:5173,http://127.0.0.1:5173'))))),
+
+ 'allowed_origins_patterns' => array_values(array_filter(array_map('trim', explode(',', env('CORS_ALLOWED_ORIGIN_PATTERNS', '#^https?://(localhost|127\\.0\\.0\\.1)(:\\d+)?$#'))))),
+
+ 'allowed_headers' => ['*'],
+
+ 'exposed_headers' => [],
+
+ 'max_age' => 0,
+
+ 'supports_credentials' => env('CORS_SUPPORTS_CREDENTIALS', false),
+
+];
diff --git a/config/database.php b/config/database.php
new file mode 100644
index 0000000..137ad18
--- /dev/null
+++ b/config/database.php
@@ -0,0 +1,151 @@
+ env('DB_CONNECTION', 'mysql'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Database Connections
+ |--------------------------------------------------------------------------
+ |
+ | Here are each of the database connections setup for your application.
+ | Of course, examples of configuring each database platform that is
+ | supported by Laravel is shown below to make development simple.
+ |
+ |
+ | All database work in Laravel is done through the PHP PDO facilities
+ | so make sure you have the driver for your particular database of
+ | choice installed on your machine before you begin development.
+ |
+ */
+
+ 'connections' => [
+
+ 'sqlite' => [
+ 'driver' => 'sqlite',
+ 'url' => env('DATABASE_URL'),
+ 'database' => env('DB_DATABASE', database_path('database.sqlite')),
+ 'prefix' => '',
+ 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
+ ],
+
+ 'mysql' => [
+ 'driver' => 'mysql',
+ 'url' => env('DATABASE_URL'),
+ 'host' => env('DB_HOST', '127.0.0.1'),
+ 'port' => env('DB_PORT', '3306'),
+ 'database' => env('DB_DATABASE', 'forge'),
+ 'username' => env('DB_USERNAME', 'forge'),
+ 'password' => env('DB_PASSWORD', ''),
+ 'unix_socket' => env('DB_SOCKET', ''),
+ 'charset' => 'utf8mb4',
+ 'collation' => 'utf8mb4_unicode_ci',
+ 'prefix' => '',
+ 'prefix_indexes' => true,
+ 'strict' => true,
+ 'engine' => null,
+ 'options' => extension_loaded('pdo_mysql') ? array_filter([
+ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
+ ]) : [],
+ ],
+
+ 'pgsql' => [
+ 'driver' => 'pgsql',
+ 'url' => env('DATABASE_URL'),
+ 'host' => env('DB_HOST', '127.0.0.1'),
+ 'port' => env('DB_PORT', '5432'),
+ 'database' => env('DB_DATABASE', 'forge'),
+ 'username' => env('DB_USERNAME', 'forge'),
+ 'password' => env('DB_PASSWORD', ''),
+ 'charset' => 'utf8',
+ 'prefix' => '',
+ 'prefix_indexes' => true,
+ 'search_path' => 'public',
+ 'sslmode' => 'prefer',
+ ],
+
+ 'sqlsrv' => [
+ 'driver' => 'sqlsrv',
+ 'url' => env('DATABASE_URL'),
+ 'host' => env('DB_HOST', 'localhost'),
+ 'port' => env('DB_PORT', '1433'),
+ 'database' => env('DB_DATABASE', 'forge'),
+ 'username' => env('DB_USERNAME', 'forge'),
+ 'password' => env('DB_PASSWORD', ''),
+ 'charset' => 'utf8',
+ 'prefix' => '',
+ 'prefix_indexes' => true,
+ // 'encrypt' => env('DB_ENCRYPT', 'yes'),
+ // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
+ ],
+
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Migration Repository Table
+ |--------------------------------------------------------------------------
+ |
+ | This table keeps track of all the migrations that have already run for
+ | your application. Using this information, we can determine which of
+ | the migrations on disk haven't actually been run in the database.
+ |
+ */
+
+ 'migrations' => 'migrations',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Redis Databases
+ |--------------------------------------------------------------------------
+ |
+ | Redis is an open source, fast, and advanced key-value store that also
+ | provides a richer body of commands than a typical key-value system
+ | such as APC or Memcached. Laravel makes it easy to dig right in.
+ |
+ */
+
+ 'redis' => [
+
+ 'client' => env('REDIS_CLIENT', 'phpredis'),
+
+ 'options' => [
+ 'cluster' => env('REDIS_CLUSTER', 'redis'),
+ 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
+ ],
+
+ 'default' => [
+ 'url' => env('REDIS_URL'),
+ 'host' => env('REDIS_HOST', '127.0.0.1'),
+ 'username' => env('REDIS_USERNAME'),
+ 'password' => env('REDIS_PASSWORD'),
+ 'port' => env('REDIS_PORT', '6379'),
+ 'database' => env('REDIS_DB', '0'),
+ ],
+
+ 'cache' => [
+ 'url' => env('REDIS_URL'),
+ 'host' => env('REDIS_HOST', '127.0.0.1'),
+ 'username' => env('REDIS_USERNAME'),
+ 'password' => env('REDIS_PASSWORD'),
+ 'port' => env('REDIS_PORT', '6379'),
+ 'database' => env('REDIS_CACHE_DB', '1'),
+ ],
+
+ ],
+
+];
diff --git a/config/filesystems.php b/config/filesystems.php
new file mode 100644
index 0000000..e9d9dbd
--- /dev/null
+++ b/config/filesystems.php
@@ -0,0 +1,76 @@
+ env('FILESYSTEM_DISK', 'local'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Filesystem Disks
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure as many filesystem "disks" as you wish, and you
+ | may even configure multiple disks of the same driver. Defaults have
+ | been set up for each driver as an example of the required values.
+ |
+ | Supported Drivers: "local", "ftp", "sftp", "s3"
+ |
+ */
+
+ 'disks' => [
+
+ 'local' => [
+ 'driver' => 'local',
+ 'root' => storage_path('app'),
+ 'throw' => false,
+ ],
+
+ 'public' => [
+ 'driver' => 'local',
+ 'root' => storage_path('app/public'),
+ 'url' => env('APP_URL').'/storage',
+ 'visibility' => 'public',
+ 'throw' => false,
+ ],
+
+ 's3' => [
+ 'driver' => 's3',
+ 'key' => env('AWS_ACCESS_KEY_ID'),
+ 'secret' => env('AWS_SECRET_ACCESS_KEY'),
+ 'region' => env('AWS_DEFAULT_REGION'),
+ 'bucket' => env('AWS_BUCKET'),
+ 'url' => env('AWS_URL'),
+ 'endpoint' => env('AWS_ENDPOINT'),
+ 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
+ 'throw' => false,
+ ],
+
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Symbolic Links
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure the symbolic links that will be created when the
+ | `storage:link` Artisan command is executed. The array keys should be
+ | the locations of the links and the values should be their targets.
+ |
+ */
+
+ 'links' => [
+ public_path('storage') => storage_path('app/public'),
+ ],
+
+];
diff --git a/config/hashing.php b/config/hashing.php
new file mode 100644
index 0000000..0e8a0bb
--- /dev/null
+++ b/config/hashing.php
@@ -0,0 +1,54 @@
+ 'bcrypt',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Bcrypt Options
+ |--------------------------------------------------------------------------
+ |
+ | Here you may specify the configuration options that should be used when
+ | passwords are hashed using the Bcrypt algorithm. This will allow you
+ | to control the amount of time it takes to hash the given password.
+ |
+ */
+
+ 'bcrypt' => [
+ 'rounds' => env('BCRYPT_ROUNDS', 12),
+ 'verify' => true,
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Argon Options
+ |--------------------------------------------------------------------------
+ |
+ | Here you may specify the configuration options that should be used when
+ | passwords are hashed using the Argon algorithm. These will allow you
+ | to control the amount of time it takes to hash the given password.
+ |
+ */
+
+ 'argon' => [
+ 'memory' => 65536,
+ 'threads' => 1,
+ 'time' => 4,
+ 'verify' => true,
+ ],
+
+];
diff --git a/config/logging.php b/config/logging.php
new file mode 100644
index 0000000..c44d276
--- /dev/null
+++ b/config/logging.php
@@ -0,0 +1,131 @@
+ env('LOG_CHANNEL', 'stack'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Deprecations Log Channel
+ |--------------------------------------------------------------------------
+ |
+ | This option controls the log channel that should be used to log warnings
+ | regarding deprecated PHP and library features. This allows you to get
+ | your application ready for upcoming major versions of dependencies.
+ |
+ */
+
+ 'deprecations' => [
+ 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'),
+ 'trace' => false,
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Log Channels
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure the log channels for your application. Out of
+ | the box, Laravel uses the Monolog PHP logging library. This gives
+ | you a variety of powerful log handlers / formatters to utilize.
+ |
+ | Available Drivers: "single", "daily", "slack", "syslog",
+ | "errorlog", "monolog",
+ | "custom", "stack"
+ |
+ */
+
+ 'channels' => [
+ 'stack' => [
+ 'driver' => 'stack',
+ 'channels' => ['single'],
+ 'ignore_exceptions' => false,
+ ],
+
+ 'single' => [
+ 'driver' => 'single',
+ 'path' => storage_path('logs/laravel.log'),
+ 'level' => env('LOG_LEVEL', 'debug'),
+ 'replace_placeholders' => true,
+ ],
+
+ 'daily' => [
+ 'driver' => 'daily',
+ 'path' => storage_path('logs/laravel.log'),
+ 'level' => env('LOG_LEVEL', 'debug'),
+ 'days' => 14,
+ 'replace_placeholders' => true,
+ ],
+
+ 'slack' => [
+ 'driver' => 'slack',
+ 'url' => env('LOG_SLACK_WEBHOOK_URL'),
+ 'username' => 'Laravel Log',
+ 'emoji' => ':boom:',
+ 'level' => env('LOG_LEVEL', 'critical'),
+ 'replace_placeholders' => true,
+ ],
+
+ 'papertrail' => [
+ 'driver' => 'monolog',
+ 'level' => env('LOG_LEVEL', 'debug'),
+ 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class),
+ 'handler_with' => [
+ 'host' => env('PAPERTRAIL_URL'),
+ 'port' => env('PAPERTRAIL_PORT'),
+ 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'),
+ ],
+ 'processors' => [PsrLogMessageProcessor::class],
+ ],
+
+ 'stderr' => [
+ 'driver' => 'monolog',
+ 'level' => env('LOG_LEVEL', 'debug'),
+ 'handler' => StreamHandler::class,
+ 'formatter' => env('LOG_STDERR_FORMATTER'),
+ 'with' => [
+ 'stream' => 'php://stderr',
+ ],
+ 'processors' => [PsrLogMessageProcessor::class],
+ ],
+
+ 'syslog' => [
+ 'driver' => 'syslog',
+ 'level' => env('LOG_LEVEL', 'debug'),
+ 'facility' => 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..e894b2e
--- /dev/null
+++ b/config/mail.php
@@ -0,0 +1,134 @@
+ env('MAIL_MAILER', 'smtp'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Mailer Configurations
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure all of the mailers used by your application plus
+ | their respective settings. Several examples have been configured for
+ | you and you are free to add your own as your application requires.
+ |
+ | Laravel supports a variety of mail "transport" drivers to be used while
+ | sending an e-mail. You will specify which one you are using for your
+ | mailers below. You are free to add additional mailers as required.
+ |
+ | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2",
+ | "postmark", "log", "array", "failover", "roundrobin"
+ |
+ */
+
+ 'mailers' => [
+ 'smtp' => [
+ 'transport' => 'smtp',
+ 'url' => env('MAIL_URL'),
+ 'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
+ 'port' => env('MAIL_PORT', 587),
+ 'encryption' => env('MAIL_ENCRYPTION', 'tls'),
+ 'username' => env('MAIL_USERNAME'),
+ 'password' => env('MAIL_PASSWORD'),
+ 'timeout' => null,
+ 'local_domain' => env('MAIL_EHLO_DOMAIN'),
+ ],
+
+ 'ses' => [
+ 'transport' => 'ses',
+ ],
+
+ 'postmark' => [
+ 'transport' => 'postmark',
+ // 'message_stream_id' => null,
+ // 'client' => [
+ // 'timeout' => 5,
+ // ],
+ ],
+
+ 'mailgun' => [
+ 'transport' => 'mailgun',
+ // 'client' => [
+ // 'timeout' => 5,
+ // ],
+ ],
+
+ 'sendmail' => [
+ 'transport' => 'sendmail',
+ 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'),
+ ],
+
+ 'log' => [
+ 'transport' => 'log',
+ 'channel' => env('MAIL_LOG_CHANNEL'),
+ ],
+
+ 'array' => [
+ 'transport' => 'array',
+ ],
+
+ 'failover' => [
+ 'transport' => 'failover',
+ 'mailers' => [
+ 'smtp',
+ 'log',
+ ],
+ ],
+
+ 'roundrobin' => [
+ 'transport' => 'roundrobin',
+ 'mailers' => [
+ 'ses',
+ 'postmark',
+ ],
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Global "From" Address
+ |--------------------------------------------------------------------------
+ |
+ | You may wish for all e-mails sent by your application to be sent from
+ | the same address. Here, you may specify a name and address that is
+ | used globally for all e-mails that are sent by your application.
+ |
+ */
+
+ 'from' => [
+ 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
+ 'name' => env('MAIL_FROM_NAME', 'Example'),
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Markdown Mail Settings
+ |--------------------------------------------------------------------------
+ |
+ | If you are using Markdown based email rendering, you may configure your
+ | theme and component paths here, allowing you to customize the design
+ | of the emails. Or, you may simply stick with the Laravel defaults!
+ |
+ */
+
+ 'markdown' => [
+ 'theme' => 'default',
+
+ 'paths' => [
+ resource_path('views/vendor/mail'),
+ ],
+ ],
+
+];
diff --git a/config/pxg.php b/config/pxg.php
new file mode 100644
index 0000000..3ef1664
--- /dev/null
+++ b/config/pxg.php
@@ -0,0 +1,40 @@
+ (int) env('OTP_TTL_MINUTES', 5),
+ 'otp_max_attempts' => (int) env('OTP_MAX_ATTEMPTS', 5),
+ 'otp_rate_limit_per_phone' => (int) env('OTP_RATE_LIMIT_PER_PHONE', 3), // per 10 minutes
+ 'pairing_token_ttl_minutes' => (int) env('PAIRING_TOKEN_TTL_MINUTES', 30),
+
+ 'wa_otp_driver' => env('WA_OTP_DRIVER', 'log'),
+
+ 'pairing' => [
+ // Balanced target seats: 4 slots (BEGINNER treated as HIGH-like by default)
+ 'balanced_target' => ['LOW', 'MID', 'MID', 'HIGH'],
+
+ // Seat fill order
+ 'seat_order' => [1,2,3,4],
+
+ // Pace-of-play guardrails
+ 'max_high_like_per_flight' => 2,
+
+ // Course balancing: spread assigned seats across A/B unless course_pref is set
+ 'course_balance' => true,
+
+ // Semi-hard course preference:
+ // true => always try preferred course first; only fallback if no seats exist on preferred.
+ // false => treat as soft preference (score-based).
+ 'course_pref_semi_hard' => true,
+
+ // Hole bottleneck guardrail:
+ // Distribute HIGH/BEGINNER across start holes (shotgun) to avoid stacking slow flights on same hole.
+ 'hole_balance_high_like' => true,
+ // Penalty weight for assigning high-like players onto already high-like-heavy holes.
+ 'hole_high_like_penalty' => 12,
+ ],
+
+ 'xendit' => [
+ 'api_key' => env('XENDIT_API_KEY', ''),
+ 'webhook_token' => env('XENDIT_WEBHOOK_TOKEN', ''),
+ ],
+];
diff --git a/config/queue.php b/config/queue.php
new file mode 100644
index 0000000..01c6b05
--- /dev/null
+++ b/config/queue.php
@@ -0,0 +1,109 @@
+ env('QUEUE_CONNECTION', 'sync'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Queue Connections
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure the connection information for each server that
+ | is used by your application. A default configuration has been added
+ | for each back-end shipped with Laravel. You are free to add more.
+ |
+ | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
+ |
+ */
+
+ 'connections' => [
+
+ 'sync' => [
+ 'driver' => 'sync',
+ ],
+
+ 'database' => [
+ 'driver' => 'database',
+ 'table' => 'jobs',
+ 'queue' => 'default',
+ 'retry_after' => 90,
+ 'after_commit' => false,
+ ],
+
+ 'beanstalkd' => [
+ 'driver' => 'beanstalkd',
+ 'host' => 'localhost',
+ 'queue' => 'default',
+ 'retry_after' => 90,
+ 'block_for' => 0,
+ 'after_commit' => false,
+ ],
+
+ 'sqs' => [
+ 'driver' => 'sqs',
+ 'key' => env('AWS_ACCESS_KEY_ID'),
+ 'secret' => env('AWS_SECRET_ACCESS_KEY'),
+ 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
+ 'queue' => env('SQS_QUEUE', 'default'),
+ 'suffix' => env('SQS_SUFFIX'),
+ 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
+ 'after_commit' => false,
+ ],
+
+ 'redis' => [
+ 'driver' => 'redis',
+ 'connection' => 'default',
+ 'queue' => env('REDIS_QUEUE', 'default'),
+ 'retry_after' => 90,
+ 'block_for' => null,
+ 'after_commit' => false,
+ ],
+
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | 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', 'mysql'),
+ 'table' => 'job_batches',
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Failed Queue Jobs
+ |--------------------------------------------------------------------------
+ |
+ | These options configure the behavior of failed queue job logging so you
+ | can control which database and table are used to store the jobs that
+ | have failed. You may change them to any database / table you wish.
+ |
+ */
+
+ 'failed' => [
+ 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
+ 'database' => env('DB_CONNECTION', 'mysql'),
+ 'table' => 'failed_jobs',
+ ],
+
+];
diff --git a/config/sanctum.php b/config/sanctum.php
new file mode 100644
index 0000000..35d75b3
--- /dev/null
+++ b/config/sanctum.php
@@ -0,0 +1,83 @@
+ explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
+ '%s%s',
+ 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
+ Sanctum::currentApplicationUrlWithPort()
+ ))),
+
+ /*
+ |--------------------------------------------------------------------------
+ | 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' => App\Http\Middleware\EncryptCookies::class,
+ 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
+ ],
+
+];
diff --git a/config/services.php b/config/services.php
new file mode 100644
index 0000000..0ace530
--- /dev/null
+++ b/config/services.php
@@ -0,0 +1,34 @@
+ [
+ 'domain' => env('MAILGUN_DOMAIN'),
+ 'secret' => env('MAILGUN_SECRET'),
+ 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
+ 'scheme' => 'https',
+ ],
+
+ 'postmark' => [
+ 'token' => env('POSTMARK_TOKEN'),
+ ],
+
+ 'ses' => [
+ 'key' => env('AWS_ACCESS_KEY_ID'),
+ 'secret' => env('AWS_SECRET_ACCESS_KEY'),
+ 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
+ ],
+
+];
diff --git a/config/session.php b/config/session.php
new file mode 100644
index 0000000..e738cb3
--- /dev/null
+++ b/config/session.php
@@ -0,0 +1,214 @@
+ env('SESSION_DRIVER', 'file'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Lifetime
+ |--------------------------------------------------------------------------
+ |
+ | Here you may specify the number of minutes that you wish the session
+ | to be allowed to remain idle before it expires. If you want them
+ | to immediately expire on the browser closing, set that option.
+ |
+ */
+
+ 'lifetime' => env('SESSION_LIFETIME', 120),
+
+ 'expire_on_close' => false,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Encryption
+ |--------------------------------------------------------------------------
+ |
+ | This option allows you to easily specify that all of your session data
+ | should be encrypted before it is stored. All encryption will be run
+ | automatically by Laravel and you can use the Session like normal.
+ |
+ */
+
+ 'encrypt' => false,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session File Location
+ |--------------------------------------------------------------------------
+ |
+ | When using the native session driver, we need a location where session
+ | files may be stored. A default has been set for you but a different
+ | location may be specified. This is only needed for file sessions.
+ |
+ */
+
+ 'files' => storage_path('framework/sessions'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Database Connection
+ |--------------------------------------------------------------------------
+ |
+ | When using the "database" or "redis" session drivers, you may specify a
+ | connection that should be used to manage these sessions. This should
+ | correspond to a connection in your database configuration options.
+ |
+ */
+
+ 'connection' => env('SESSION_CONNECTION'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Database Table
+ |--------------------------------------------------------------------------
+ |
+ | When using the "database" session driver, you may specify the table we
+ | should use to manage the sessions. Of course, a sensible default is
+ | provided for you; however, you are free to change this as needed.
+ |
+ */
+
+ 'table' => 'sessions',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Cache Store
+ |--------------------------------------------------------------------------
+ |
+ | While using one of the framework's cache driven session backends you may
+ | list a cache store that should be used for these sessions. This value
+ | must match with one of the application's configured cache "stores".
+ |
+ | Affects: "apc", "dynamodb", "memcached", "redis"
+ |
+ */
+
+ 'store' => env('SESSION_STORE'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Sweeping Lottery
+ |--------------------------------------------------------------------------
+ |
+ | Some session drivers must manually sweep their storage location to get
+ | rid of old sessions from storage. Here are the chances that it will
+ | happen on a given request. By default, the odds are 2 out of 100.
+ |
+ */
+
+ 'lottery' => [2, 100],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Cookie Name
+ |--------------------------------------------------------------------------
+ |
+ | Here you may change the name of the cookie used to identify a session
+ | instance by ID. The name specified here will get used every time a
+ | new session cookie is created by the framework for every driver.
+ |
+ */
+
+ 'cookie' => env(
+ 'SESSION_COOKIE',
+ Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
+ ),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Cookie Path
+ |--------------------------------------------------------------------------
+ |
+ | The session cookie path determines the path for which the cookie will
+ | be regarded as available. Typically, this will be the root path of
+ | your application but you are free to change this when necessary.
+ |
+ */
+
+ 'path' => '/',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Cookie Domain
+ |--------------------------------------------------------------------------
+ |
+ | Here you may change the domain of the cookie used to identify a session
+ | in your application. This will determine which domains the cookie is
+ | available to in your application. A sensible default has been set.
+ |
+ */
+
+ 'domain' => env('SESSION_DOMAIN'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | HTTPS Only Cookies
+ |--------------------------------------------------------------------------
+ |
+ | By setting this option to true, session cookies will only be sent back
+ | to the server if the browser has a HTTPS connection. This will keep
+ | the cookie from being sent to you when it can't be done securely.
+ |
+ */
+
+ 'secure' => env('SESSION_SECURE_COOKIE'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | HTTP Access Only
+ |--------------------------------------------------------------------------
+ |
+ | Setting this value to true will prevent JavaScript from accessing the
+ | value of the cookie and the cookie will only be accessible through
+ | the HTTP protocol. You are free to modify this option if needed.
+ |
+ */
+
+ 'http_only' => true,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Same-Site Cookies
+ |--------------------------------------------------------------------------
+ |
+ | This option determines how your cookies behave when cross-site requests
+ | take place, and can be used to mitigate CSRF attacks. By default, we
+ | will set this value to "lax" since this is a secure default value.
+ |
+ | Supported: "lax", "strict", "none", null
+ |
+ */
+
+ 'same_site' => 'lax',
+
+ /*
+ |--------------------------------------------------------------------------
+ | 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' => false,
+
+];
diff --git a/config/view.php b/config/view.php
new file mode 100644
index 0000000..22b8a18
--- /dev/null
+++ b/config/view.php
@@ -0,0 +1,36 @@
+ [
+ resource_path('views'),
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Compiled View Path
+ |--------------------------------------------------------------------------
+ |
+ | This option determines where all the compiled Blade templates will be
+ | stored for your application. Typically, this is within the storage
+ | directory. However, as usual, you are free to change this value.
+ |
+ */
+
+ 'compiled' => env(
+ 'VIEW_COMPILED_PATH',
+ realpath(storage_path('framework/views'))
+ ),
+
+];
diff --git a/database/.gitignore b/database/.gitignore
new file mode 100644
index 0000000..9b19b93
--- /dev/null
+++ b/database/.gitignore
@@ -0,0 +1 @@
+*.sqlite*
diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php
new file mode 100644
index 0000000..584104c
--- /dev/null
+++ b/database/factories/UserFactory.php
@@ -0,0 +1,44 @@
+
+ */
+class UserFactory extends Factory
+{
+ /**
+ * The current password being used by the factory.
+ */
+ protected static ?string $password;
+
+ /**
+ * Define the model's default state.
+ *
+ * @return array
+ */
+ public function definition(): array
+ {
+ return [
+ 'name' => fake()->name(),
+ 'email' => fake()->unique()->safeEmail(),
+ 'email_verified_at' => now(),
+ 'password' => static::$password ??= Hash::make('password'),
+ 'remember_token' => Str::random(10),
+ ];
+ }
+
+ /**
+ * Indicate that the model's email address should be unverified.
+ */
+ public function unverified(): static
+ {
+ return $this->state(fn (array $attributes) => [
+ 'email_verified_at' => null,
+ ]);
+ }
+}
diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php
new file mode 100644
index 0000000..444fafb
--- /dev/null
+++ b/database/migrations/2014_10_12_000000_create_users_table.php
@@ -0,0 +1,32 @@
+id();
+ $table->string('name');
+ $table->string('email')->unique();
+ $table->timestamp('email_verified_at')->nullable();
+ $table->string('password');
+ $table->rememberToken();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('users');
+ }
+};
diff --git a/database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php b/database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php
new file mode 100644
index 0000000..81a7229
--- /dev/null
+++ b/database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php
@@ -0,0 +1,28 @@
+string('email')->primary();
+ $table->string('token');
+ $table->timestamp('created_at')->nullable();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('password_reset_tokens');
+ }
+};
diff --git a/database/migrations/2019_08_19_000000_create_failed_jobs_table.php b/database/migrations/2019_08_19_000000_create_failed_jobs_table.php
new file mode 100644
index 0000000..249da81
--- /dev/null
+++ b/database/migrations/2019_08_19_000000_create_failed_jobs_table.php
@@ -0,0 +1,32 @@
+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('failed_jobs');
+ }
+};
diff --git a/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php
new file mode 100644
index 0000000..e828ad8
--- /dev/null
+++ b/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php
@@ -0,0 +1,33 @@
+id();
+ $table->morphs('tokenable');
+ $table->string('name');
+ $table->string('token', 64)->unique();
+ $table->text('abilities')->nullable();
+ $table->timestamp('last_used_at')->nullable();
+ $table->timestamp('expires_at')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('personal_access_tokens');
+ }
+};
diff --git a/database/migrations/2026_01_01_000001_create_events_table.php b/database/migrations/2026_01_01_000001_create_events_table.php
new file mode 100644
index 0000000..0dc5b10
--- /dev/null
+++ b/database/migrations/2026_01_01_000001_create_events_table.php
@@ -0,0 +1,29 @@
+id();
+ $table->string('name');
+ $table->date('date')->nullable();
+ $table->string('venue')->nullable();
+ $table->boolean('shotgun_mode')->default(true);
+ $table->json('courses_json')->nullable();
+ $table->timestamp('registration_open_at')->nullable();
+ $table->timestamp('registration_close_at')->nullable();
+ $table->timestamp('pairing_finalized_at')->nullable();
+ $table->timestamp('pairing_published_at')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('events');
+ }
+};
diff --git a/database/migrations/2026_01_01_000002_create_players_table.php b/database/migrations/2026_01_01_000002_create_players_table.php
new file mode 100644
index 0000000..723ff78
--- /dev/null
+++ b/database/migrations/2026_01_01_000002_create_players_table.php
@@ -0,0 +1,26 @@
+id();
+ $table->string('name', 120);
+ $table->string('phone', 40);
+ $table->string('email', 180);
+ $table->string('club', 120)->nullable();
+ $table->string('shirt_size', 10)->nullable();
+ $table->string('gender', 10)->nullable();
+ $table->text('notes')->nullable();
+ $table->timestamps();
+ });
+ }
+ public function down(): void
+ {
+ Schema::dropIfExists('players');
+ }
+};
diff --git a/database/migrations/2026_01_01_000003_create_groups_table.php b/database/migrations/2026_01_01_000003_create_groups_table.php
new file mode 100644
index 0000000..2eabfe8
--- /dev/null
+++ b/database/migrations/2026_01_01_000003_create_groups_table.php
@@ -0,0 +1,25 @@
+id();
+ $table->foreignId('event_id')->constrained('events');
+ $table->string('group_code', 20)->unique();
+ $table->string('group_name', 120)->nullable();
+ $table->unsignedTinyInteger('size_target');
+ $table->unsignedBigInteger('leader_player_id')->nullable();
+ $table->string('status', 20)->default('OPEN'); // OPEN|FULL|LOCKED|CANCELLED
+ $table->timestamps();
+ });
+ }
+ public function down(): void
+ {
+ Schema::dropIfExists('groups');
+ }
+};
diff --git a/database/migrations/2026_01_01_000004_create_registrations_table.php b/database/migrations/2026_01_01_000004_create_registrations_table.php
new file mode 100644
index 0000000..f8e704f
--- /dev/null
+++ b/database/migrations/2026_01_01_000004_create_registrations_table.php
@@ -0,0 +1,33 @@
+id();
+ $table->foreignId('event_id')->constrained('events');
+ $table->foreignId('player_id')->constrained('players');
+ $table->string('type', 20)->default('INDIVIDUAL'); // INDIVIDUAL|GROUP_LEADER|GROUP_MEMBER
+ $table->foreignId('group_id')->nullable()->constrained('groups');
+ $table->string('status', 20)->default('PENDING_PAYMENT'); // DRAFT|PENDING_PAYMENT|CONFIRMED|WAITLIST|CANCELLED
+
+ $table->string('handicap_type', 20); // HI|EVENT_BAND
+ $table->decimal('handicap_value', 5, 1)->nullable();
+ $table->string('handicap_band', 20); // LOW|MID|HIGH|BEGINNER
+
+ $table->string('pairing_mode', 20)->default('BALANCED'); // LEVEL|BALANCED|RANDOM
+ $table->string('course_pref', 10)->default('ANY'); // A|B|ANY
+
+ $table->string('registration_code', 40)->unique();
+ $table->timestamps();
+ });
+ }
+ public function down(): void
+ {
+ Schema::dropIfExists('registrations');
+ }
+};
diff --git a/database/migrations/2026_01_01_000005_create_flights_table.php b/database/migrations/2026_01_01_000005_create_flights_table.php
new file mode 100644
index 0000000..43f74d9
--- /dev/null
+++ b/database/migrations/2026_01_01_000005_create_flights_table.php
@@ -0,0 +1,28 @@
+id();
+ $table->foreignId('event_id')->constrained('events');
+ $table->string('course', 2); // A|B
+ $table->string('code', 10);
+ $table->unsignedTinyInteger('start_hole');
+ $table->string('start_tee', 2); // A|B
+ $table->string('type', 20)->default('NORMAL'); // NORMAL|SPONSOR_LOCKED
+ $table->string('status', 20)->default('OPEN'); // OPEN|FULL|LOCKED|FINAL
+ $table->timestamps();
+
+ $table->unique(['event_id','code']);
+ });
+ }
+ public function down(): void
+ {
+ Schema::dropIfExists('flights');
+ }
+};
diff --git a/database/migrations/2026_01_01_000006_create_flight_members_table.php b/database/migrations/2026_01_01_000006_create_flight_members_table.php
new file mode 100644
index 0000000..93fa2dc
--- /dev/null
+++ b/database/migrations/2026_01_01_000006_create_flight_members_table.php
@@ -0,0 +1,26 @@
+id();
+ $table->foreignId('flight_id')->constrained('flights');
+ $table->foreignId('registration_id')->constrained('registrations');
+ $table->unsignedTinyInteger('seat_no'); // 1..4
+ $table->boolean('lock_flag')->default(false);
+ $table->timestamps();
+
+ $table->unique(['flight_id','seat_no']);
+ $table->unique(['registration_id']);
+ });
+ }
+ public function down(): void
+ {
+ Schema::dropIfExists('flight_members');
+ }
+};
diff --git a/database/migrations/2026_01_01_000007_create_payment_intents_table.php b/database/migrations/2026_01_01_000007_create_payment_intents_table.php
new file mode 100644
index 0000000..a32a7e5
--- /dev/null
+++ b/database/migrations/2026_01_01_000007_create_payment_intents_table.php
@@ -0,0 +1,29 @@
+id();
+ $table->foreignId('registration_id')->constrained('registrations');
+ $table->string('provider', 20)->default('XENDIT');
+ $table->string('channel', 30)->default('INVOICE');
+ $table->unsignedBigInteger('amount');
+ $table->string('currency', 5)->default('IDR');
+ $table->string('status', 20)->default('PENDING'); // PENDING|PAID|EXPIRED|FAILED|CANCELLED
+ $table->string('provider_ref_id', 100)->nullable()->index();
+ $table->text('checkout_url')->nullable();
+ $table->timestamp('paid_at')->nullable();
+ $table->json('raw_payload')->nullable();
+ $table->timestamps();
+ });
+ }
+ public function down(): void
+ {
+ Schema::dropIfExists('payment_intents');
+ }
+};
diff --git a/database/migrations/2026_01_01_000008_create_otp_requests_table.php b/database/migrations/2026_01_01_000008_create_otp_requests_table.php
new file mode 100644
index 0000000..fd35b64
--- /dev/null
+++ b/database/migrations/2026_01_01_000008_create_otp_requests_table.php
@@ -0,0 +1,28 @@
+id();
+ $table->string('purpose', 30)->default('PAIRING_VIEW');
+ $table->foreignId('registration_id')->nullable()->constrained('registrations');
+ $table->string('phone_e164', 30)->index();
+ $table->text('otp_hash');
+ $table->timestamp('expires_at');
+ $table->unsignedTinyInteger('attempts')->default(0);
+ $table->string('status', 20)->default('SENT'); // SENT|VERIFIED|EXPIRED|LOCKED
+ $table->string('request_ip', 60)->nullable();
+ $table->string('user_agent', 250)->nullable();
+ $table->timestamps();
+ });
+ }
+ public function down(): void
+ {
+ Schema::dropIfExists('otp_requests');
+ }
+};
diff --git a/database/migrations/2026_01_01_000009_add_role_to_users_table.php b/database/migrations/2026_01_01_000009_add_role_to_users_table.php
new file mode 100644
index 0000000..ba6daf7
--- /dev/null
+++ b/database/migrations/2026_01_01_000009_add_role_to_users_table.php
@@ -0,0 +1,25 @@
+string('role', 30)->default('committee')->after('password'); // owner|committee|finance
+ }
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::table('users', function (Blueprint $table) {
+ if (Schema::hasColumn('users', 'role')) {
+ $table->dropColumn('role');
+ }
+ });
+ }
+};
diff --git a/database/migrations/2026_01_01_000010_create_checkins_table.php b/database/migrations/2026_01_01_000010_create_checkins_table.php
new file mode 100644
index 0000000..4da0435
--- /dev/null
+++ b/database/migrations/2026_01_01_000010_create_checkins_table.php
@@ -0,0 +1,27 @@
+id();
+ $table->foreignId('event_id')->constrained('events');
+ $table->foreignId('registration_id')->constrained('registrations');
+ $table->timestamp('checked_in_at')->nullable();
+ $table->unsignedBigInteger('checked_in_by')->nullable(); // users.id
+ $table->string('method', 30)->default('manual'); // manual|qr
+ $table->timestamps();
+
+ $table->unique(['event_id','registration_id']);
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('checkins');
+ }
+};
diff --git a/database/migrations/2026_01_01_000011_create_audit_logs_table.php b/database/migrations/2026_01_01_000011_create_audit_logs_table.php
new file mode 100644
index 0000000..440864f
--- /dev/null
+++ b/database/migrations/2026_01_01_000011_create_audit_logs_table.php
@@ -0,0 +1,28 @@
+id();
+ $table->foreignId('event_id')->nullable()->constrained('events');
+ $table->unsignedBigInteger('actor_user_id')->nullable();
+ $table->string('action', 60); // e.g. flight_member_move, flight_member_remove, flight_lock
+ $table->string('entity_type', 60)->nullable(); // registration|flight|flight_member|swap_request
+ $table->unsignedBigInteger('entity_id')->nullable();
+ $table->json('meta')->nullable();
+ $table->timestamps();
+
+ $table->index(['event_id','action']);
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('audit_logs');
+ }
+};
diff --git a/database/migrations/2026_01_01_000012_create_swap_requests_table.php b/database/migrations/2026_01_01_000012_create_swap_requests_table.php
new file mode 100644
index 0000000..234da4d
--- /dev/null
+++ b/database/migrations/2026_01_01_000012_create_swap_requests_table.php
@@ -0,0 +1,29 @@
+id();
+ $table->foreignId('event_id')->constrained('events');
+ $table->foreignId('flight_member_id')->constrained('flight_members');
+ $table->foreignId('to_flight_id')->constrained('flights');
+ $table->unsignedTinyInteger('to_seat_no');
+ $table->string('reason', 250)->nullable();
+ $table->string('status', 20)->default('PENDING'); // PENDING|APPROVED|REJECTED|APPLIED
+ $table->unsignedBigInteger('requested_by')->nullable(); // users.id
+ $table->unsignedBigInteger('approved_by')->nullable(); // users.id
+ $table->timestamp('approved_at')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('swap_requests');
+ }
+};
diff --git a/database/seeders/AdminUserSeeder.php b/database/seeders/AdminUserSeeder.php
new file mode 100644
index 0000000..e761d77
--- /dev/null
+++ b/database/seeders/AdminUserSeeder.php
@@ -0,0 +1,22 @@
+ 'admin@pxg.local'],
+ [
+ 'name' => 'PXG Admin',
+ 'password' => Hash::make('pxg2026admin'),
+ 'role' => 'owner',
+ ]
+ );
+ }
+}
diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php
new file mode 100644
index 0000000..a9f4519
--- /dev/null
+++ b/database/seeders/DatabaseSeeder.php
@@ -0,0 +1,22 @@
+create();
+
+ // \App\Models\User::factory()->create([
+ // 'name' => 'Test User',
+ // 'email' => 'test@example.com',
+ // ]);
+ }
+}
diff --git a/database/seeders/EventSeeder.php b/database/seeders/EventSeeder.php
new file mode 100644
index 0000000..bab1c36
--- /dev/null
+++ b/database/seeders/EventSeeder.php
@@ -0,0 +1,26 @@
+ 1],
+ [
+ 'name' => 'TOURNAMENT PXG 2026',
+ 'date' => now()->addMonths(1)->toDateString(),
+ 'venue' => 'TBD',
+ 'shotgun_mode' => true,
+ 'courses_json' => [
+ ['code' => 'A', 'name' => 'Course A'],
+ ['code' => 'B', 'name' => 'Course B'],
+ ],
+ ]
+ );
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..56f5ddc
--- /dev/null
+++ b/package.json
@@ -0,0 +1,13 @@
+{
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build"
+ },
+ "devDependencies": {
+ "axios": "^1.6.4",
+ "laravel-vite-plugin": "^1.0.0",
+ "vite": "^5.0.0"
+ }
+}
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..bc86714
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,32 @@
+
+
+
+
+ tests/Unit
+
+
+ tests/Feature
+
+
+
+
+ app
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/.htaccess b/public/.htaccess
new file mode 100644
index 0000000..3aec5e2
--- /dev/null
+++ b/public/.htaccess
@@ -0,0 +1,21 @@
+
+
+ Options -MultiViews -Indexes
+
+
+ RewriteEngine On
+
+ # Handle Authorization Header
+ RewriteCond %{HTTP:Authorization} .
+ RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+
+ # Redirect Trailing Slashes If Not A Folder...
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteCond %{REQUEST_URI} (.+)/$
+ RewriteRule ^ %1 [L,R=301]
+
+ # Send Requests To Front Controller...
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteRule ^ index.php [L]
+
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..e69de29
diff --git a/public/index.php b/public/index.php
new file mode 100644
index 0000000..1d69f3a
--- /dev/null
+++ b/public/index.php
@@ -0,0 +1,55 @@
+make(Kernel::class);
+
+$response = $kernel->handle(
+ $request = Request::capture()
+)->send();
+
+$kernel->terminate($request, $response);
diff --git a/public/robots.txt b/public/robots.txt
new file mode 100644
index 0000000..eb05362
--- /dev/null
+++ b/public/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow:
diff --git a/resources/css/app.css b/resources/css/app.css
new file mode 100644
index 0000000..e69de29
diff --git a/resources/js/app.js b/resources/js/app.js
new file mode 100644
index 0000000..e59d6a0
--- /dev/null
+++ b/resources/js/app.js
@@ -0,0 +1 @@
+import './bootstrap';
diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js
new file mode 100644
index 0000000..846d350
--- /dev/null
+++ b/resources/js/bootstrap.js
@@ -0,0 +1,32 @@
+/**
+ * We'll load the axios HTTP library which allows us to easily issue requests
+ * to our Laravel back-end. This library automatically handles sending the
+ * CSRF token as a header based on the value of the "XSRF" token cookie.
+ */
+
+import axios from 'axios';
+window.axios = axios;
+
+window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
+
+/**
+ * Echo exposes an expressive API for subscribing to channels and listening
+ * for events that are broadcast by Laravel. Echo and event broadcasting
+ * allows your team to easily build robust real-time web applications.
+ */
+
+// import Echo from 'laravel-echo';
+
+// import Pusher from 'pusher-js';
+// window.Pusher = Pusher;
+
+// window.Echo = new Echo({
+// broadcaster: 'pusher',
+// key: import.meta.env.VITE_PUSHER_APP_KEY,
+// cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1',
+// wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
+// wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
+// wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
+// forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
+// enabledTransports: ['ws', 'wss'],
+// });
diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php
new file mode 100644
index 0000000..3353350
--- /dev/null
+++ b/resources/views/welcome.blade.php
@@ -0,0 +1,133 @@
+
+
+
+
+
+
+ Laravel
+
+
+
+
+
+
+
+
+
+
+ @if (Route::has('login'))
+
+ @auth
+
Home
+ @else
+
Log in
+
+ @if (Route::has('register'))
+
Register
+ @endif
+ @endauth
+
+ @endif
+
+
+
+
+
+
+
+
+
+
+
Documentation
+
+
+ Laravel has wonderful documentation covering every aspect of the framework. Whether you are a newcomer or have prior experience with Laravel, we recommend reading our documentation from beginning to end.
+
+
+
+
+
+
+
+
+
+
+
+
+
Laracasts
+
+
+ Laracasts offers thousands of video tutorials on Laravel, PHP, and JavaScript development. Check them out, see for yourself, and massively level up your development skills in the process.
+
+
+
+
+
+
+
+
+
+
+
+
+
Laravel News
+
+
+ Laravel News is a community driven portal and newsletter aggregating all of the latest and most important news in the Laravel ecosystem, including new package releases and tutorials.
+
+
+
+
+
+
+
+
+
+
+
+
+
Vibrant Ecosystem
+
+
+ Laravel's robust library of first-party tools and libraries, such as Forge , Vapor , Nova , and Envoyer help you take your projects to the next level. Pair them with powerful open source libraries like Cashier , Dusk , Echo , Horizon , Sanctum , Telescope , and more.
+
+
+
+
+
+
+
+
+
+
+
+
+ Laravel v{{ Illuminate\Foundation\Application::VERSION }} (PHP v{{ PHP_VERSION }})
+
+
+
+
+
+
diff --git a/routes/api.php b/routes/api.php
new file mode 100644
index 0000000..ca9a2e9
--- /dev/null
+++ b/routes/api.php
@@ -0,0 +1,76 @@
+group(function () {
+ Route::get('/events/{event}', [EventController::class, 'show']);
+
+ Route::post('/events/{event}/registrations', [RegistrationController::class, 'store']);
+
+ Route::post('/events/{event}/groups', [GroupController::class, 'store']);
+ Route::get('/groups/{code}', [GroupController::class, 'show']);
+ Route::post('/groups/{code}/join', [GroupController::class, 'join']);
+
+ Route::post('/registrations/{registration}/pay', [PaymentController::class, 'pay']);
+
+ Route::post('/pairing/lookup', [PairingController::class, 'lookup']);
+ Route::post('/pairing/otp/request', [PairingController::class, 'requestOtp']);
+ Route::post('/pairing/otp/verify', [PairingController::class, 'verifyOtp']);
+ Route::get('/pairing/me', [PairingController::class, 'me']);
+
+ Route::post('/webhooks/xendit', [WebhookController::class, 'xendit']);
+
+// Admin (Sanctum)
+Route::post('/admin/login', [AdminAuthController::class, 'login']);
+Route::middleware('auth:sanctum')->group(function () {
+ Route::post('/admin/logout', [AdminAuthController::class, 'logout']);
+
+ Route::get('/admin/events/{event}/dashboard', [DashboardController::class, 'show']);
+ Route::get('/admin/events/{event}/registrations', [AdminRegistrationsController::class, 'index']);
+ Route::patch('/admin/registrations/{registration}', [AdminRegistrationsController::class, 'update']);
+
+ Route::get('/admin/events/{event}/flights', [AdminFlightsController::class, 'index']);
+ Route::get('/admin/events/{event}/payments', [AdminPaymentsController::class, 'index']);
+
+ // Flight member ops
+ Route::get('/admin/flights/{flight}', [AdminFlightsController::class, 'show']);
+ Route::post('/admin/flights/{flight}/assign', [AdminFlightsController::class, 'assign']);
+ Route::post('/admin/flight-members/{flightMember}/move', [AdminFlightsController::class, 'moveMember']);
+ Route::delete('/admin/flight-members/{flightMember}', [AdminFlightsController::class, 'removeMember']);
+ Route::post('/admin/flights/{flight}/lock', [AdminFlightsController::class, 'lock']);
+ Route::post('/admin/flights/{flight}/unlock', [AdminFlightsController::class, 'unlock']);
+
+ // Exports (CSV - Excel friendly)
+ Route::get('/admin/events/{event}/exports/start-sheet.csv', [DashboardController::class, 'exportStartSheet']);
+ Route::get('/admin/events/{event}/exports/pairing-list.csv', [DashboardController::class, 'exportPairingList']);
+
+
+ Route::post('/admin/events/{event}/pairing/run', [PairingAdminController::class, 'run']);
+
+ // Finalize/Publish (owner only)
+ Route::post('/admin/events/{event}/pairing/finalize', [EventAdminController::class, 'finalize'])->middleware('role:owner');
+ Route::post('/admin/events/{event}/pairing/unfinalize', [EventAdminController::class, 'unfinalize'])->middleware('role:owner');
+ Route::post('/admin/events/{event}/pairing/publish', [EventAdminController::class, 'publish'])->middleware('role:owner');
+
+ // Bulk import (committee+owner)
+ Route::post('/admin/events/{event}/registrations/import', [ImportController::class, 'importRegistrations'])->middleware('role:owner,committee');
+
+});
+
+});
diff --git a/routes/channels.php b/routes/channels.php
new file mode 100644
index 0000000..5d451e1
--- /dev/null
+++ b/routes/channels.php
@@ -0,0 +1,18 @@
+id === (int) $id;
+});
diff --git a/routes/console.php b/routes/console.php
new file mode 100644
index 0000000..e05f4c9
--- /dev/null
+++ b/routes/console.php
@@ -0,0 +1,19 @@
+comment(Inspiring::quote());
+})->purpose('Display an inspiring quote');
diff --git a/routes/web.php b/routes/web.php
new file mode 100644
index 0000000..d259f33
--- /dev/null
+++ b/routes/web.php
@@ -0,0 +1,18 @@
+make(Kernel::class)->bootstrap();
+
+ return $app;
+ }
+}
diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php
new file mode 100644
index 0000000..8364a84
--- /dev/null
+++ b/tests/Feature/ExampleTest.php
@@ -0,0 +1,19 @@
+get('/');
+
+ $response->assertStatus(200);
+ }
+}
diff --git a/tests/TestCase.php b/tests/TestCase.php
new file mode 100644
index 0000000..2932d4a
--- /dev/null
+++ b/tests/TestCase.php
@@ -0,0 +1,10 @@
+assertTrue(true);
+ }
+}
diff --git a/vite.config.js b/vite.config.js
new file mode 100644
index 0000000..421b569
--- /dev/null
+++ b/vite.config.js
@@ -0,0 +1,11 @@
+import { defineConfig } from 'vite';
+import laravel from 'laravel-vite-plugin';
+
+export default defineConfig({
+ plugins: [
+ laravel({
+ input: ['resources/css/app.css', 'resources/js/app.js'],
+ refresh: true,
+ }),
+ ],
+});