diff --git a/.env.example b/.env.example index 2a4a8b7..8ccf90b 100644 --- a/.env.example +++ b/.env.example @@ -1,17 +1,21 @@ APP_NAME=Laravel APP_ENV=local -APP_KEY= +APP_KEY=base64:tMiKdI/3GuqLAKyBIe+Mv8cdrM04xfP/Onbvbtofc7A= APP_DEBUG=true -APP_TIMEZONE=UTC -APP_URL=http://localhost +APP_TIMEZONE=Asia/Jakarta +APP_URL=http://localhost:8000 +FRONTEND_URL=http://localhost:3000 +SANCTUM_STATEFUL_DOMAIN=localhost:3000 -APP_LOCALE=en -APP_FALLBACK_LOCALE=en -APP_FAKER_LOCALE=en_US +APP_LOCALE=id +APP_FALLBACK_LOCALE=id +APP_FAKER_LOCALE=id_ID APP_MAINTENANCE_DRIVER=file # APP_MAINTENANCE_STORE=database +PHP_CLI_SERVER_WORKERS=4 + BCRYPT_ROUNDS=12 LOG_CHANNEL=stack @@ -61,4 +65,4 @@ AWS_DEFAULT_REGION=us-east-1 AWS_BUCKET= AWS_USE_PATH_STYLE_ENDPOINT=false -VITE_APP_NAME="${APP_NAME}" +VITE_APP_NAME="${APP_NAME}" \ No newline at end of file diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml new file mode 100644 index 0000000..9634a0e --- /dev/null +++ b/.github/workflows/issues.yml @@ -0,0 +1,12 @@ +name: issues + +on: + issues: + types: [labeled] + +permissions: + issues: write + +jobs: + help-wanted: + uses: laravel/.github/.github/workflows/issues.yml@main diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml new file mode 100644 index 0000000..18b32b3 --- /dev/null +++ b/.github/workflows/pull-requests.yml @@ -0,0 +1,12 @@ +name: pull requests + +on: + pull_request_target: + types: [opened] + +permissions: + pull-requests: write + +jobs: + uneditable: + uses: laravel/.github/.github/workflows/pull-requests.yml@main diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..03601bd --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,47 @@ +name: Tests + +on: + push: + branches: + - master + - '*.x' + pull_request: + schedule: + - cron: '0 0 * * *' + +permissions: + contents: read + +jobs: + tests: + runs-on: ubuntu-latest + + strategy: + fail-fast: true + matrix: + php: [8.2, 8.3] + + name: PHP ${{ matrix.php }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite + coverage: none + + - name: Install Composer dependencies + run: composer install --prefer-dist --no-interaction --no-progress + + - name: Copy environment file + run: cp .env.example .env + + - name: Generate app key + run: php artisan key:generate + + - name: Execute tests + run: vendor/bin/phpunit diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml new file mode 100644 index 0000000..ebda620 --- /dev/null +++ b/.github/workflows/update-changelog.yml @@ -0,0 +1,13 @@ +name: update changelog + +on: + release: + types: [released] + +permissions: {} + +jobs: + update: + permissions: + contents: write + uses: laravel/.github/.github/workflows/update-changelog.yml@main diff --git a/.gitignore b/.gitignore index afa306b..f263447 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,9 @@ /public/hot /public/storage /storage/*.key +/storage/pail /vendor +/dist .env .env.backup .env.production @@ -15,6 +17,8 @@ Homestead.yaml auth.json npm-debug.log yarn-error.log +package-lock.json +vscode /.fleet /.idea /.vscode diff --git a/.styleci.yml b/.styleci.yml new file mode 100644 index 0000000..9daadf1 --- /dev/null +++ b/.styleci.yml @@ -0,0 +1,9 @@ +php: + preset: laravel + disabled: + - no_unused_imports + finder: + not-name: + - index.php +js: true +css: true diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..201a0d3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,92 @@ +# Release Notes + +## [Unreleased](https://github.com/laravel/laravel/compare/v11.3.1...11.x) + +## [v11.3.1](https://github.com/laravel/laravel/compare/v11.3.0...v11.3.1) - 2024-10-15 + +**Full Changelog**: https://github.com/laravel/laravel/compare/v11.3.0...v11.3.1 + +## [v11.3.0](https://github.com/laravel/laravel/compare/v11.2.1...v11.3.0) - 2024-10-14 + +* Add Tailwind, "composer run dev" by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/laravel/pull/6463 + +## [v11.2.1](https://github.com/laravel/laravel/compare/v11.2.0...v11.2.1) - 2024-10-08 + +* [11.x] Collision Version Upgrade by [@amdad121](https://github.com/amdad121) in https://github.com/laravel/laravel/pull/6454 +* [11.x] factory-generics-in-user-model by [@MrPunyapal](https://github.com/MrPunyapal) in https://github.com/laravel/laravel/pull/6453 +* Update welcome.blade.php to add missing alt tag by [@mezotv](https://github.com/mezotv) in https://github.com/laravel/laravel/pull/6462 + +## [v11.2.0](https://github.com/laravel/laravel/compare/v11.1.5...v11.2.0) - 2024-09-11 + +* Update .gitignore with Zed Editor by [@fahadkhan1740](https://github.com/fahadkhan1740) in https://github.com/laravel/laravel/pull/6449 +* Laracon 2024 feature update by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/laravel/pull/6450 + +## [v11.1.5](https://github.com/laravel/laravel/compare/v11.1.4...v11.1.5) - 2024-08-14 + +* Update axios by [@laserhybiz](https://github.com/laserhybiz) in https://github.com/laravel/laravel/pull/6440 + +## [v11.1.4](https://github.com/laravel/laravel/compare/v11.1.3...v11.1.4) - 2024-07-16 + +**Full Changelog**: https://github.com/laravel/laravel/compare/v11.1.3...v11.1.4 + +## [v11.1.3](https://github.com/laravel/laravel/compare/v11.1.2...v11.1.3) - 2024-07-03 + +* [11.x] Comment maintenance store by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/laravel/pull/6429 + +## [v11.1.2](https://github.com/laravel/laravel/compare/v11.1.1...v11.1.2) - 2024-06-20 + +* Expose lock table name by [@nhedger](https://github.com/nhedger) in https://github.com/laravel/laravel/pull/6423 + +## [v11.1.1](https://github.com/laravel/laravel/compare/v11.1.0...v11.1.1) - 2024-06-04 + +* Format the first letter of `drivers` to lowercase by [@maru0914](https://github.com/maru0914) in https://github.com/laravel/laravel/pull/6413 + +## [v11.1.0](https://github.com/laravel/laravel/compare/v11.0.9...v11.1.0) - 2024-05-28 + +* [11.x] Removes `--dev` dependencies by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/laravel/pull/6406 + +## [v11.0.9](https://github.com/laravel/laravel/compare/v11.0.8...v11.0.9) - 2024-05-16 + +* Updated SMTP mail config to use a valid EHLO domain by [@rcerljenko](https://github.com/rcerljenko) in https://github.com/laravel/laravel/pull/6402 + +## [v11.0.8](https://github.com/laravel/laravel/compare/v11.0.7...v11.0.8) - 2024-05-13 + +* Add .phpactor.json to .gitignore by [@princejohnsantillan](https://github.com/princejohnsantillan) in https://github.com/laravel/laravel/pull/6400 + +## [v11.0.7](https://github.com/laravel/laravel/compare/v11.0.6...v11.0.7) - 2024-05-03 + +* Remove obsolete driver option by [@u01jmg3](https://github.com/u01jmg3) in https://github.com/laravel/laravel/pull/6395 + +## [v11.0.6](https://github.com/laravel/laravel/compare/v11.0.5...v11.0.6) - 2024-04-09 + +* Fix PHPUnit constraint by [@szepeviktor](https://github.com/szepeviktor) in https://github.com/laravel/laravel/pull/6389 +* [11.x] Add missing roundrobin transport driver config by [@u01jmg3](https://github.com/u01jmg3) in https://github.com/laravel/laravel/pull/6392 + +## [v11.0.5](https://github.com/laravel/laravel/compare/v11.0.4...v11.0.5) - 2024-03-26 + +* [11.x] Use PHPUnit v11 by [@philbates35](https://github.com/philbates35) in https://github.com/laravel/laravel/pull/6385 + +## [v11.0.4](https://github.com/laravel/laravel/compare/v11.0.3...v11.0.4) - 2024-03-15 + +* [11.x] Removed useless null parameter for env helper (cache.php) by [@siarheipashkevich](https://github.com/siarheipashkevich) in https://github.com/laravel/laravel/pull/6374 +* [11.x] Removed useless null parameter for env helper (queue.php) by [@siarheipashkevich](https://github.com/siarheipashkevich) in https://github.com/laravel/laravel/pull/6373 +* [11.x] Fix retry_after to be an integer by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/laravel/pull/6377 +* [11.x] Fix on hover animation and ring by [@michaelnabil230](https://github.com/michaelnabil230) in https://github.com/laravel/laravel/pull/6376 + +## [v11.0.3](https://github.com/laravel/laravel/compare/v11.0.2...v11.0.3) - 2024-03-14 + +* [11.x] Revert collation change by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/laravel/pull/6372 + +## [v11.0.2](https://github.com/laravel/laravel/compare/v11.0.1...v11.0.2) - 2024-03-13 + +* [11.x] Remove branch alias from composer.json by [@zepfietje](https://github.com/zepfietje) in https://github.com/laravel/laravel/pull/6366 +* [11.x] Fixes typo in welcome page by [@jrd-lewis](https://github.com/jrd-lewis) in https://github.com/laravel/laravel/pull/6363 +* change mariadb default by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/laravel/commit/79969c99c6456a6d6edfbe78d241575fe1f65594 + +## [v11.0.1](https://github.com/laravel/laravel/compare/v11.0.0...v11.0.1) - 2024-03-12 + +* [11.x] Fixes SQLite driver missing by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/laravel/pull/6361 + +## [v11.0.0 (2023-02-17)](https://github.com/laravel/laravel/compare/v10.3.2...v11.0.0) + +Laravel 11 includes a variety of changes to the application skeleton. Please consult the diff to see what's new. diff --git a/app/Http/Controllers/Auth/AuthenticatedSessionController.php b/app/Http/Controllers/Auth/AuthenticatedSessionController.php new file mode 100644 index 0000000..a33d282 --- /dev/null +++ b/app/Http/Controllers/Auth/AuthenticatedSessionController.php @@ -0,0 +1,38 @@ +authenticate(); + + $request->session()->regenerate(); + + return response()->noContent(); + } + + /** + * Destroy an authenticated session. + */ + public function destroy(Request $request): Response + { + Auth::guard('web')->logout(); + + $request->session()->invalidate(); + + $request->session()->regenerateToken(); + + return response()->noContent(); + } +} diff --git a/app/Http/Controllers/Auth/EmailVerificationNotificationController.php b/app/Http/Controllers/Auth/EmailVerificationNotificationController.php new file mode 100644 index 0000000..3f42fcd --- /dev/null +++ b/app/Http/Controllers/Auth/EmailVerificationNotificationController.php @@ -0,0 +1,25 @@ +user()->hasVerifiedEmail()) { + return redirect()->intended('/dashboard'); + } + + $request->user()->sendEmailVerificationNotification(); + + return response()->json(['status' => 'verification-link-sent']); + } +} diff --git a/app/Http/Controllers/Auth/NewPasswordController.php b/app/Http/Controllers/Auth/NewPasswordController.php new file mode 100644 index 0000000..4277c32 --- /dev/null +++ b/app/Http/Controllers/Auth/NewPasswordController.php @@ -0,0 +1,53 @@ +validate([ + 'token' => ['required'], + 'email' => ['required', 'email'], + 'password' => ['required', 'confirmed', Rules\Password::defaults()], + ]); + + // Here we will attempt to reset the user's password. If it is successful we + // will update the password on an actual user model and persist it to the + // database. Otherwise we will parse the error and return the response. + $status = Password::reset( + $request->only('email', 'password', 'password_confirmation', 'token'), + function ($user) use ($request) { + $user->forceFill([ + 'password' => Hash::make($request->string('password')), + 'remember_token' => Str::random(60), + ])->save(); + + event(new PasswordReset($user)); + } + ); + + if ($status != Password::PASSWORD_RESET) { + throw ValidationException::withMessages([ + 'email' => [__($status)], + ]); + } + + return response()->json(['status' => __($status)]); + } +} diff --git a/app/Http/Controllers/Auth/PasswordResetLinkController.php b/app/Http/Controllers/Auth/PasswordResetLinkController.php new file mode 100644 index 0000000..95cde8e --- /dev/null +++ b/app/Http/Controllers/Auth/PasswordResetLinkController.php @@ -0,0 +1,39 @@ +validate([ + 'email' => ['required', 'email'], + ]); + + // We will send the password reset link to this user. Once we have attempted + // to send the link, we will examine the response then see the message we + // need to show to the user. Finally, we'll send out a proper response. + $status = Password::sendResetLink( + $request->only('email') + ); + + if ($status != Password::RESET_LINK_SENT) { + throw ValidationException::withMessages([ + 'email' => [__($status)], + ]); + } + + return response()->json(['status' => __($status)]); + } +} diff --git a/app/Http/Controllers/Auth/ProfileController.php b/app/Http/Controllers/Auth/ProfileController.php new file mode 100644 index 0000000..24c7ea6 --- /dev/null +++ b/app/Http/Controllers/Auth/ProfileController.php @@ -0,0 +1,64 @@ +json($user); + } + + public function show($id) + { + $user = User::find($id); + return response()->json($user); + } + public function update(ProfileUpdateRequest $request) + { + $user = $request->user(); + $validated = $request->validated(); + + // Menangani avatar + if ($request->hasFile('avatar')) { + // Hapus avatar lama jika ada + if ($user->avatar) { + Storage::disk('public')->delete($user->avatar); + } + $validated['avatar'] = $request->file('avatar')->store('avatars', 'public'); + $validated['avatar'] = asset('storage/' . $validated['avatar']); + } else { + // Jika tidak ada file avatar, gunakan avatar yang ada + $validated['avatar'] = $user->avatar; + } + + // Hanya update password jika diisi + if (isset($validated['password']) && !empty($validated['password'])) { + $validated['password'] = bcrypt($validated['password']); + } else { + // Hapus password dari array validasi agar tidak diupdate + unset($validated['password']); + } + + //abaikan role jika diisi selain oleh is_admin + if (isset($validated['role']) && $user->is_admin !== 1) { + unset($validated['role']); + } + + // Update profil pengguna + $user->update($validated); + $user = $user->refresh(); + + return response()->json([ + 'user' => $user, + 'success' => true, + ], 200); + } +} diff --git a/app/Http/Controllers/Auth/RegisteredUserController.php b/app/Http/Controllers/Auth/RegisteredUserController.php new file mode 100644 index 0000000..c6edf9c --- /dev/null +++ b/app/Http/Controllers/Auth/RegisteredUserController.php @@ -0,0 +1,41 @@ +validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class], + 'password' => ['required', 'confirmed', Rules\Password::defaults()], + ]); + + $user = User::create([ + 'name' => $request->name, + 'email' => $request->email, + 'password' => Hash::make($request->string('password')), + ]); + + event(new Registered($user)); + + Auth::login($user); + + return response()->noContent(); + } +} diff --git a/app/Http/Controllers/Auth/VerifyEmailController.php b/app/Http/Controllers/Auth/VerifyEmailController.php new file mode 100644 index 0000000..33cbed4 --- /dev/null +++ b/app/Http/Controllers/Auth/VerifyEmailController.php @@ -0,0 +1,31 @@ +user()->hasVerifiedEmail()) { + return redirect()->intended( + config('app.frontend_url').'/dashboard?verified=1' + ); + } + + if ($request->user()->markEmailAsVerified()) { + event(new Verified($request->user())); + } + + return redirect()->intended( + config('app.frontend_url').'/dashboard?verified=1' + ); + } +} diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php deleted file mode 100644 index 7ac5475..0000000 --- a/app/Http/Controllers/AuthController.php +++ /dev/null @@ -1,60 +0,0 @@ -validate([ - 'name' => 'required|max:255', - 'email' => 'required|email|max:255|unique:users', - 'password' => 'required|min:8|confirmed', - ]); - - $user = User::create($fields); - - $token = $user->createToken($request->name); - - return [ - 'user' => $user, - 'token' => $token - ]; - } - - public function login(Request $request) - { - $fields = $request->validate([ - 'email' => 'required|email|exists:users,email', - 'password' => 'required|min:8' - ]); - - $user = User::where('email', $request->email)->first(); - - if (!$user || !Hash::check($request->password, $user->password)) { - return response([ - 'message' => 'Bad creds' - ], 401); - } - - $token = $user->createToken($user->name); - - return [ - 'user' => $user, - 'token' => $token->plainTextToken - ]; - } - - public function logout(Request $request) - { - $request->user()->tokens()->delete(); - - return [ - 'message' => 'Logged out' - ]; - } -} diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php new file mode 100644 index 0000000..b3e810f --- /dev/null +++ b/app/Http/Controllers/CategoryController.php @@ -0,0 +1,71 @@ +json(Category::all()); + } + + /** + * Store a newly created category in storage. + */ + public function store(Request $request) + { + $request->validate([ + 'name' => 'required|string|max:255|unique:categories,name', + ]); + + $slug = str_replace(' ', '-', strtolower($request->name)); + $category = Category::create([ + 'name' => $request->name, + 'slug' => $slug + ]); + + return response()->json($category, Response::HTTP_CREATED); + } + + /** + * Display the specified category. + */ + public function show(Category $category) + { + return response()->json($category); + } + + /** + * Update the specified category in storage. + */ + public function update(Request $request, Category $category) + { + $request->validate([ + 'name' => 'required|string|max:255|unique:categories,name,' . $category->id, + ]); + $slug = str_replace(' ', '-', strtolower($request->name)); + $category->update([ + 'name' => $request->name, + 'slug' => $slug + ]); + + return response()->json($category); + } + + /** + * Remove the specified category from storage. + */ + public function destroy(Category $category) + { + $category->delete(); + + return response()->json(null, Response::HTTP_NO_CONTENT); + } +} diff --git a/app/Http/Controllers/CustomerController.php b/app/Http/Controllers/CustomerController.php new file mode 100644 index 0000000..c4a28bd --- /dev/null +++ b/app/Http/Controllers/CustomerController.php @@ -0,0 +1,269 @@ + 'required|string|max:255', + 'phone' => 'required|string|max:20', + 'bank' => 'string', + 'dari' => 'date', + 'sampai' => 'date', + 'address' => 'required|string', + ]; + + public function index(Request $request) + { + $paginate = $request->query('paginate'); + $validated = $request->validate( + [ + 'name' => 'nullable|string|min:3', + 'phone' => 'nullable|string|min:4', + 'bank' => 'nullable|string', + 'dari' => 'nullable|date', + 'sampai' => 'nullable|date', + ], + [ + 'name.min' => 'Minimal 3 karakter', + 'phone.min' => 'Minimal 4 karakter', + 'dari.date' => 'Format tanggal salah', + 'sampai.date' => 'Format tanggal salah', + ] + ); + + $query = Customer::with('orders', 'meta'); + + if (!empty($validated['name'])) { + $query->where('name', 'like', '%' . $validated['name'] . '%'); + } + + if (!empty($validated['phone'])) { + $query->where('phone', 'like', '%' . $validated['phone'] . '%'); + } + + if (!empty($validated['bank'])) { + if ($validated['bank'] === 'Perorangan') { + $query->where(function ($q) { + $q->whereHas('meta', function ($subQuery) { + $subQuery->where('meta_key', 'bank') + ->where('meta_value', 'Perorangan'); + })->orWhereDoesntHave('meta', function ($subQuery) { + $subQuery->where('meta_key', 'bank'); + }); + }); + } else { + $query->whereHas('meta', function ($subQuery) use ($validated) { + $subQuery->where('meta_key', 'bank') + ->where('meta_value', 'like', '%' . $validated['bank'] . '%'); + }); + } + } + + if (!empty($validated['dari']) || !empty($validated['sampai'])) { + $query->when(!empty($validated['dari']) && !empty($validated['sampai']), function ($q) use ($validated) { + $q->whereBetween('created_at', [$validated['dari'], $validated['sampai']]); + }) + ->when(!empty($validated['dari']) && empty($validated['sampai']), function ($q) use ($validated) { + $q->where('created_at', '>=', $validated['dari']); + }) + ->when(empty($validated['dari']) && !empty($validated['sampai']), function ($q) use ($validated) { + $q->where('created_at', '<=', $validated['sampai']); + }); + } + + $query->orderBy('created_at', 'desc'); + + + if ($paginate === 'false') { + $customers = $query->get()->map(function ($data) { + return [ + 'id' => $data->id, + 'name' => $data->name, + 'phone' => $data->phone, + 'address' => $data->address, + 'order_count' => $data->orders->count(), + 'orders' => $data->orders, + 'meta' => $data->meta, + 'created_at' => $data->created_at + ]; + }); + } else { + $customers = $query->paginate(25); + $customers->getCollection()->transform(function ($data) { + return [ + 'id' => $data->id, + 'name' => $data->name, + 'phone' => $data->phone, + 'address' => $data->address, + 'order_count' => $data->orders->count(), + 'orders' => $data->orders, + 'meta' => $data->meta, + 'created_at' => $data->created_at + ]; + }); + } + return response()->json($customers); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) + { + $validatedData = $request->validate( + [ + 'name' => 'required|string|max:255', + 'phone' => 'required|string|max:20|unique:customers,phone', + 'address' => 'required|string', + 'meta' => 'nullable|array', + ], + [ + 'name.required' => 'Nama harus diisi.', + 'phone.required' => 'Nomor telepon harus diisi.', + 'phone.unique' => 'Nomor telepon sudah ada.', + 'address.required' => 'Alamat harus diisi.', + 'meta.array' => 'Meta harus berupa array.', + ] + ); + + $customer = Customer::create($validatedData); + if (isset($validatedData['meta'])) { + $customer->meta()->createMany($validatedData['meta']); + } + $response = [ + 'data' => [ + 'id' => $customer->id, + 'name' => $customer->name, + 'phone' => $customer->phone, + 'address' => $customer->address, + 'order_count' => $customer->orders->count(), + 'orders' => $customer->orders, + 'meta' => $customer->meta, + 'created_at' => $customer->created_at + ] + ]; + return response()->json($response); + } + + /** + * Display the specified resource. + */ + public function show(Customer $customer) + { + $customer = Customer::with('orders.jobdesks', 'meta')->find($customer->id); + + $response = [ + 'id' => $customer->id, + 'name' => $customer->name, + 'phone' => $customer->phone, + 'email' => $customer->email, + 'address' => $customer->address, + 'order_count' => $customer->orders->count(), + 'orders' => $customer->orders, + 'meta' => $customer->meta, + 'created_at' => $customer->created_at, + 'updated_at' => $customer->updated_at + ]; + + return response()->json($response); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, Customer $customer) + { + $customer = Customer::find($customer->id); + $validatedData = $request->validate( + [ + 'name' => 'required|string|max:255', + 'phone' => 'required|string|max:20|unique:customers,phone,' . $customer->id, + 'address' => 'required|string', + 'meta' => 'nullable|array', + ], + [ + 'name.required' => 'Nama harus diisi.', + 'phone.required' => 'Nomor telepon harus diisi.', + 'phone.unique' => 'Nomor telepon sudah ada.', + 'address.required' => 'Alamat harus diisi.', + 'meta.array' => 'Meta harus berupa array.', + ] + ); + $customer->update($validatedData); + if (isset($validatedData['meta'])) { + // tanpa delete meta yang ada, update jika key sama + foreach ($validatedData['meta'] as $metaData) { + $meta = CustomerMeta::updateOrCreate( + ['customer_id' => $customer->id, 'meta_key' => $metaData['meta_key']] + ); + $meta->meta_value = $metaData['meta_value']; + $meta->save(); + } + } + $customer->load('orders', 'meta'); + + $response = [ + 'data' => [ + 'id' => $customer->id, + 'name' => $customer->name, + 'phone' => $customer->phone, + 'address' => $customer->address, + 'order_count' => $customer->orders->count(), + 'orders' => $customer->orders, + 'meta' => $customer->meta, + 'created_at' => $customer->created_at + ] + ]; + return response()->json($response); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Customer $customer) + { + // Retrieve all orders for the customer + $orders = $customer->orders; + + // Loop through each order and delete related jobdesks + foreach ($orders as $order) { + $order->jobdesks()->delete(); // Ensure jobdesks relationship exists on Order + } + + // Now delete the orders themselves + $customer->orders()->delete(); + + // Finally, delete the customer + $customer->delete(); + + return response()->json($customer); + } + + public function storeMeta(Request $request, Customer $customer) + { + $validatedData = $request->validate([ + 'meta' => 'required|array', + 'meta.*.meta_key' => 'required|string|max:100', + 'meta.*.meta_value' => 'required|string', + ]); + + foreach ($validatedData['meta'] as $metaData) { + $meta = new CustomerMeta(); + $meta->customer_id = $customer->id; + $meta->meta_key = $metaData['meta_key']; + $meta->meta_value = $metaData['meta_value']; + $meta->save(); + } + + return response()->json(['message' => 'Data meta berhasil disimpan.']); + } +} diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php new file mode 100644 index 0000000..b577bda --- /dev/null +++ b/app/Http/Controllers/DashboardController.php @@ -0,0 +1,129 @@ +user(); + + // Get date filters from request + $dateFrom = $request->get('date_from'); + $dateTo = $request->get('date_to'); + + // If no dates provided, use current month as default + if (!$dateFrom || !$dateTo) { + $dateFrom = now()->startOfMonth()->format('Y-m-d'); + $dateTo = now()->format('Y-m-d'); + } + + // Base queries with date filters + $orderQuery = Order::whereBetween('order_date', [$dateFrom, $dateTo]); + $jobdeskQuery = Jobdesk::whereBetween('created_at', [$dateFrom . ' 00:00:00', $dateTo . ' 23:59:59']); + + // Total counts (not filtered by date for context) + $totalCustomers = Customer::count(); + $totalOrders = Order::count(); + $totalKaryawan = User::count(); + + // Filtered data based on date range + $orderBulanIni = $orderQuery->count(); + $totalPendapatan = intval($orderQuery->sum('paid')); + $pendapatanBulanIni = intval($orderQuery->sum('paid')); + + // Previous period comparison (same date range but previous period) + $dateDiff = \Carbon\Carbon::parse($dateFrom)->diffInDays(\Carbon\Carbon::parse($dateTo)); + $previousDateFrom = \Carbon\Carbon::parse($dateFrom)->subDays($dateDiff + 1)->format('Y-m-d'); + $previousDateTo = \Carbon\Carbon::parse($dateFrom)->subDay()->format('Y-m-d'); + $pendapatanBulanSebelumnya = intval(Order::whereBetween('order_date', [$previousDateFrom, $previousDateTo])->sum('paid')); + + $totalTagihanPeriode = intval($orderQuery->sum('price')); + $totalTagihanBulanIni = $totalTagihanPeriode; + $totalTagihan = intval(Order::sum('price')) - intval(Order::sum('paid')); + + // Menghitung jobdesk berdasarkan status dengan filter tanggal + $totalJobdesk = $jobdeskQuery->select('status', DB::raw('count(*) as count')) + ->groupBy('status') + ->pluck('count', 'status'); + + // Data tren jobdesk 30 hari terakhir + $thirtyDaysAgo = now()->subDays(30)->format('Y-m-d'); + $today = now()->format('Y-m-d'); + + $jobdeskTrend = []; + for ($i = 29; $i >= 0; $i--) { + $date = now()->subDays($i)->format('Y-m-d'); + $dateStart = $date . ' 00:00:00'; + $dateEnd = $date . ' 23:59:59'; + + $dailyJobdesk = Jobdesk::whereBetween('created_at', [$dateStart, $dateEnd]) + ->select('status', DB::raw('count(*) as count')) + ->groupBy('status') + ->pluck('count', 'status'); + + $jobdeskTrend[] = [ + 'date' => $date, + 'masuk' => (int) $dailyJobdesk->get('Masuk', 0), + 'progress' => (int) $dailyJobdesk->get('Progress', 0), + 'selesai' => (int) $dailyJobdesk->get('Selesai', 0), + ]; + } + + // Data tren pendapatan dan tagihan 12 bulan terakhir + $monthlyTrend = []; + for ($i = 11; $i >= 0; $i--) { + $monthStart = now()->subMonths($i)->startOfMonth(); + $monthEnd = now()->subMonths($i)->endOfMonth(); + + $monthlyRevenue = Order::whereBetween('order_date', [ + $monthStart->format('Y-m-d'), + $monthEnd->format('Y-m-d') + ])->sum('paid'); + + $monthlyBilling = Order::whereBetween('order_date', [ + $monthStart->format('Y-m-d'), + $monthEnd->format('Y-m-d') + ])->sum('price'); + + $monthlyTrend[] = [ + 'month' => $monthStart->format('Y-m'), + 'month_name' => $monthStart->translatedFormat('M'), + 'revenue' => (int) $monthlyRevenue, + 'billing' => (int) $monthlyBilling, + ]; + } + + // Menyiapkan data untuk response + $data = [ + 'total_customers' => $totalCustomers, + 'order_bulan_ini' => $orderBulanIni, + 'total_orders' => $user->role !== 'staff' ? $totalOrders : 0, + 'total_pendapatan' => $user->role !== 'staff' ? $totalPendapatan : 0, + 'pendapatan_bulan_ini' => $user->role !== 'staff' ? $pendapatanBulanIni : 0, + 'pendapatan_bulan_sebelumnya' => $user->role !== 'staff' ? $pendapatanBulanSebelumnya : 0, + 'total_tagihan' => $user->role !== 'staff' ? $totalTagihan : 0, + 'total_tagihan_bulan_ini' => $user->role !== 'staff' ? $totalTagihanBulanIni : 0, + 'total_karyawan' => $totalKaryawan, + 'total_jobdesks' => [ + 'Masuk' => (int) $totalJobdesk->get('Masuk', 0), + 'Progress' => (int) $totalJobdesk->get('Progress', 0), + 'Selesai' => (int) $totalJobdesk->get('Selesai', 0), + ], + 'jobdesk_trend' => $jobdeskTrend, + 'monthly_trend' => $monthlyTrend, + ]; + + return response()->json($data); + } +} diff --git a/app/Http/Controllers/JobdeskController.php b/app/Http/Controllers/JobdeskController.php new file mode 100644 index 0000000..7238b79 --- /dev/null +++ b/app/Http/Controllers/JobdeskController.php @@ -0,0 +1,494 @@ +query('order_id'); + $status = $request->query('status'); + $name = $request->query('name'); + $userId = $request->query('user_id'); + $dari = $request->query('dari'); + $sampai = $request->query('sampai'); + $user = $request->user(); + + // Optimized eager loading with selective fields + $query = Jobdesk::with([ + 'order:id,no_order,customer_id,product_id,order_date', + 'order.customer:id,name,phone,address', + 'order.product:id,name,category,description', + 'user:id,name,email' // Removed 'role' column + ]) + ->select([ + 'id', + 'order_id', + 'user_id', + 'description', + 'status', + 'tanggal_pengerjaan', + 'tanggal_selesai', + 'created_at', + 'updated_at' + ]); + + // Filter by order_id if provided + if ($orderId && $orderId !== '') { + $query->where('order_id', $orderId); + } + + // Role-based filtering + if (in_array($user->role, ['staff'])) { + $query->where('user_id', $user->id); + } + + // Filter by status if provided + if ($status && $status !== '') { + $query->where('status', $status); + } + + // Filter by user_id if provided + if ($userId && $userId !== '') { + $query->where('user_id', $userId); + } + + // Search by customer name (minimum 3 characters) + if ($name && strlen($name) > 2) { + $query->whereHas('order.customer', function ($query) use ($name) { + $query->where('name', 'like', '%' . $name . '%'); + }); + } + + // Date range filters + if ($dari && $sampai) { + $query->whereHas('order', function ($q) use ($dari, $sampai) { + $q->whereBetween('order_date', [ + date('Y-m-d', strtotime($dari)), + date('Y-m-d', strtotime($sampai)) + ]); + }); + } elseif ($dari) { + $query->whereHas('order', function ($q) use ($dari) { + $q->where('order_date', '>=', date('Y-m-d', strtotime($dari))); + }); + } elseif ($sampai) { + $query->whereHas('order', function ($q) use ($sampai) { + $q->where('order_date', '<=', date('Y-m-d', strtotime($sampai))); + }); + } + + // Get total counts for stats (without filters for global stats) + try { + $totalCounts = $this->getGlobalStatusCounts($user); + } catch (\Exception $e) { + // Fallback to simple counts if there's an error + $totalCounts = [ + 'total' => 0, + 'masuk' => 0, + 'progress' => 0, + 'selesai' => 0, + 'completion_rate' => 0 + ]; + } + + // Order and paginate + $jobdesks = $query->orderBy('id', 'desc')->paginate(25); + + // Transform data for frontend + $jobdesks->through(function ($jobdesk) use ($user) { + try { + return $this->formatJobdeskResponse($jobdesk, $user); + } catch (\Exception $e) { + // Fallback to basic response if formatting fails + return [ + 'id' => $jobdesk->id, + 'order_id' => $jobdesk->order_id, + 'user_id' => $jobdesk->user_id, + 'description' => $jobdesk->description, + 'status' => $jobdesk->status, + 'tanggal_pengerjaan' => $jobdesk->tanggal_pengerjaan, + 'tanggal_selesai' => $jobdesk->tanggal_selesai, + 'created_at' => $jobdesk->created_at, + 'updated_at' => $jobdesk->updated_at, + 'order' => $jobdesk->order, + 'user' => $jobdesk->user, + 'error' => 'Formatting error: ' . $e->getMessage() + ]; + } + }); + + // Just return the paginated jobdesks like OrderController + // We'll add the extra data in the frontend from status_counts endpoint + return response()->json($jobdesks); + } catch (\Exception $e) { + return response()->json([ + 'message' => 'Server Error', + 'error' => $e->getMessage(), + 'line' => $e->getLine(), + 'file' => basename($e->getFile()) + ], 500); + } + } + public function store(Request $request) + { + try { + $validatedData = $request->validate([ + 'order_id' => 'required|exists:orders,id', + 'user_id' => 'required|exists:users,id', + 'description' => 'required|string', + 'tanggal_pengerjaan' => 'nullable|date', + 'tanggal_selesai' => 'nullable|date', + 'status' => 'nullable|string', + ]); + + $validatedData['tanggal_pengerjaan'] = $validatedData['tanggal_pengerjaan'] ? Carbon::parse($validatedData['tanggal_pengerjaan'])->setTimezone('Asia/Jakarta')->startOfDay() : null; + $validatedData['tanggal_selesai'] = $validatedData['tanggal_selesai'] ? Carbon::parse($validatedData['tanggal_selesai'])->setTimezone('Asia/Jakarta')->endOfDay() : null; + + $jobdesk = Jobdesk::create($validatedData); + // Load relations without role field + $jobdesk->load([ + 'order', + 'order.customer', + 'user:id,name,email', + 'order.product' + ]); + return response()->json($jobdesk, 201); + } catch (\Exception $e) { + return response()->json(['message' => 'Server Error', 'error' => $e->getMessage()], 500); + } + } + public function update(Request $request, $id) + { + try { + $jobdesk = Jobdesk::find($id); + $validatedData = $request->validate([ + 'order_id' => 'required|exists:orders,id', + 'user_id' => 'required|exists:users,id', + 'description' => 'required|string', + 'tanggal_pengerjaan' => 'nullable|date', + 'tanggal_selesai' => 'nullable|date', + 'status' => 'nullable|string', + ]); + + $validatedData['tanggal_pengerjaan'] = $validatedData['tanggal_pengerjaan'] ? Carbon::parse($validatedData['tanggal_pengerjaan'])->setTimezone('Asia/Jakarta')->startOfDay() : null; + $validatedData['tanggal_selesai'] = $validatedData['tanggal_selesai'] ? Carbon::parse($validatedData['tanggal_selesai'])->setTimezone('Asia/Jakarta')->endOfDay() : null; + + $jobdesk->update($validatedData); + $jobdesk->load([ + 'order', + 'order.customer', + 'user:id,name,email', + 'order.product' + ]); + return response()->json($jobdesk); + } catch (\Exception $e) { + return response()->json(['message' => 'Server Error', 'error' => $e->getMessage()], 500); + } + } + + public function show(Jobdesk $jobdesk) + { + $jobdesk = Jobdesk::find($jobdesk->id)->load([ + 'order', + 'order.customer', + 'user:id,name,email', + 'order.product' + ]); + return response()->json($jobdesk); + } + + public function destroy(Jobdesk $jobdesk) + { + try { + $jobdesk->delete(); + return response()->json(['message' => 'Jobdesk deleted successfully']); + } catch (\Exception $e) { + return response()->json([ + 'message' => 'Error deleting jobdesk', + 'error' => $e->getMessage() + ], 500); + } + } + + /** + * Get global status counts (ignoring all filters) for dashboard stats + */ + private function getGlobalStatusCounts($user) + { + try { + // Create a fresh query without any filters + $baseQuery = Jobdesk::query(); + + // Only apply role-based filtering if user is staff + if (in_array($user->role, ['staff'])) { + $baseQuery->where('user_id', $user->id); + } + + // Get total count + $totalCount = $baseQuery->count(); + + // Count by status - handle null status gracefully + $masukCount = (clone $baseQuery)->where('status', 'Masuk')->count(); + $progressCount = (clone $baseQuery)->where('status', 'Progress')->count(); + $selesaiCount = (clone $baseQuery)->where('status', 'Selesai')->count(); + + return [ + 'total' => $totalCount, + 'masuk' => $masukCount, + 'progress' => $progressCount, + 'selesai' => $selesaiCount, + 'completion_rate' => $totalCount > 0 ? round(($selesaiCount / $totalCount) * 100, 2) : 0 + ]; + } catch (\Exception $e) { + // Return zero counts if there's an error + return [ + 'total' => 0, + 'masuk' => 0, + 'progress' => 0, + 'selesai' => 0, + 'completion_rate' => 0 + ]; + } + } + + /** + * Get status counts for dashboard stats + */ + private function getStatusCounts($query, $user) + { + try { + // Clone query for each status count to avoid conflicts + $baseQuery = clone $query; + + // Remove pagination and get total count + $totalCount = $baseQuery->count(); + + // Count by status - handle null status gracefully + $masukCount = (clone $query)->where('status', 'Masuk')->count(); + $progressCount = (clone $query)->where('status', 'Progress')->count(); + $selesaiCount = (clone $query)->where('status', 'Selesai')->count(); + + return [ + 'total' => $totalCount, + 'masuk' => $masukCount, + 'progress' => $progressCount, + 'selesai' => $selesaiCount, + 'completion_rate' => $totalCount > 0 ? round(($selesaiCount / $totalCount) * 100, 2) : 0 + ]; + } catch (\Exception $e) { + // Return zero counts if there's an error + return [ + 'total' => 0, + 'masuk' => 0, + 'progress' => 0, + 'selesai' => 0, + 'completion_rate' => 0 + ]; + } + } + + /** + * Format jobdesk response for frontend + */ + private function formatJobdeskResponse($jobdesk, $user) + { + try { + return [ + 'id' => $jobdesk->id ?? null, + 'order_id' => $jobdesk->order_id ?? null, + 'user_id' => $jobdesk->user_id ?? null, + 'description' => $jobdesk->description ?? '', + 'status' => $jobdesk->status ?? '', + 'tanggal_pengerjaan' => $jobdesk->tanggal_pengerjaan ?? null, + 'tanggal_selesai' => $jobdesk->tanggal_selesai ?? null, + 'created_at' => $jobdesk->created_at ?? null, + 'updated_at' => $jobdesk->updated_at ?? null, + + // Enhanced order information + 'order' => $jobdesk->order ? [ + 'id' => $jobdesk->order->id ?? null, + 'no_order' => $jobdesk->order->no_order ?? '', + 'order_date' => $jobdesk->order->order_date ?? null, + 'customer' => $jobdesk->order->customer ? [ + 'id' => $jobdesk->order->customer->id ?? null, + 'name' => $jobdesk->order->customer->name ?? '', + 'phone' => $jobdesk->order->customer->phone ?? '', + 'address' => $jobdesk->order->customer->address ?? '', + ] : null, + 'product' => $jobdesk->order->product ? [ + 'id' => $jobdesk->order->product->id ?? null, + 'name' => $jobdesk->order->product->name ?? '', + 'category' => $jobdesk->order->product->category ?? '', + 'description' => $jobdesk->order->product->description ?? '', + ] : null, + ] : null, + + // User information + 'user' => $jobdesk->user ? [ + 'id' => $jobdesk->user->id ?? null, + 'name' => $jobdesk->user->name ?? '', + 'email' => $jobdesk->user->email ?? '', + // Removed role field since it doesn't exist in users table + ] : null, + + // Frontend convenience fields + 'customer_name' => $jobdesk->order?->customer?->name ?? 'Tidak diketahui', + 'customer_phone' => $jobdesk->order?->customer?->phone ?? '-', + 'product_name' => $jobdesk->order?->product?->name ?? '-', + 'assigned_to' => $jobdesk->user?->name ?? 'Belum ditugaskan', + 'status_text' => $this->getStatusText($jobdesk->status ?? ''), + 'status_class' => $this->getStatusClass($jobdesk->status ?? ''), + 'is_overdue' => $this->isOverdue($jobdesk), + 'days_remaining' => $this->getDaysRemaining($jobdesk), + ]; + } catch (\Exception $e) { + // Return basic response if formatting fails + return [ + 'id' => $jobdesk->id ?? null, + 'order_id' => $jobdesk->order_id ?? null, + 'user_id' => $jobdesk->user_id ?? null, + 'description' => $jobdesk->description ?? '', + 'status' => $jobdesk->status ?? '', + 'tanggal_pengerjaan' => $jobdesk->tanggal_pengerjaan ?? null, + 'tanggal_selesai' => $jobdesk->tanggal_selesai ?? null, + 'created_at' => $jobdesk->created_at ?? null, + 'updated_at' => $jobdesk->updated_at ?? null, + 'order' => $jobdesk->order ?? null, + 'user' => $jobdesk->user ?? null, + 'customer_name' => 'Error loading data', + 'status_text' => 'Unknown', + 'formatting_error' => $e->getMessage() + ]; + } + } + + /** + * Get human readable status text + */ + private function getStatusText($status) + { + switch ($status) { + case 'Masuk': + return 'Belum Mulai'; + case 'Progress': + return 'Sedang Dikerjakan'; + case 'Selesai': + return 'Selesai'; + default: + return $status ?? 'Tidak Diketahui'; + } + } + + /** + * Get CSS class for status badge + */ + private function getStatusClass($status) + { + switch ($status) { + case 'Masuk': + return 'bg-yellow-100 text-yellow-800 border-yellow-200'; + case 'Progress': + return 'bg-blue-100 text-blue-800 border-blue-200'; + case 'Selesai': + return 'bg-green-100 text-green-800 border-green-200'; + default: + return 'bg-gray-100 text-gray-800 border-gray-200'; + } + } + + /** + * Check if jobdesk is overdue + */ + private function isOverdue($jobdesk) + { + try { + if (!$jobdesk || !$jobdesk->tanggal_selesai || $jobdesk->status === 'Selesai') { + return false; + } + + return Carbon::now()->gt(Carbon::parse($jobdesk->tanggal_selesai)); + } catch (\Exception $e) { + return false; + } + } + + /** + * Get days remaining for completion + */ + private function getDaysRemaining($jobdesk) + { + try { + if (!$jobdesk || !$jobdesk->tanggal_selesai || $jobdesk->status === 'Selesai') { + return null; + } + + $now = Carbon::now(); + $deadline = Carbon::parse($jobdesk->tanggal_selesai); + + return $now->diffInDays($deadline, false); // false = can be negative + } catch (\Exception $e) { + return null; + } + } + + /** + * Get jobdesk statistics - separate endpoint for dashboard + */ + public function stats(Request $request) + { + try { + $user = $request->user(); + + // Get global stats (ignoring filters) for dashboard + $stats = $this->getGlobalStatusCounts($user); + + // Add additional stats with error handling (also global) + try { + $baseQuery = Jobdesk::query(); + + // Only apply role-based filtering if user is staff + if (in_array($user->role, ['staff'])) { + $baseQuery->where('user_id', $user->id); + } + + $stats['overdue_count'] = (clone $baseQuery) + ->where('status', '!=', 'Selesai') + ->where('tanggal_selesai', '<', Carbon::now()) + ->whereNotNull('tanggal_selesai') + ->count(); + } catch (\Exception $e) { + $stats['overdue_count'] = 0; + } + + try { + $baseQuery = Jobdesk::query(); + + // Only apply role-based filtering if user is staff + if (in_array($user->role, ['staff'])) { + $baseQuery->where('user_id', $user->id); + } + + $stats['today_deadline'] = (clone $baseQuery) + ->where('status', '!=', 'Selesai') + ->whereDate('tanggal_selesai', Carbon::today()) + ->count(); + } catch (\Exception $e) { + $stats['today_deadline'] = 0; + } + + return response()->json($stats); + } catch (\Exception $e) { + return response()->json([ + 'message' => 'Server Error', + 'error' => $e->getMessage() + ], 500); + } + } +} diff --git a/app/Http/Controllers/KaryawanController.php b/app/Http/Controllers/KaryawanController.php new file mode 100644 index 0000000..19e74a1 --- /dev/null +++ b/app/Http/Controllers/KaryawanController.php @@ -0,0 +1,222 @@ +authorize('viewAny', User::class); + $paginate = $request->boolean('paginate', true); + $name = $request->query('name'); + + $query = User::with(['roles', 'jobdesk']); + + // Filter nama jika lebih dari 2 karakter + if ($name && strlen($name) > 2) { + $query->where('name', 'like', '%' . $name . '%'); + } + + $query->orderByDesc('created_at'); + + $transformUser = function ($user) { + return [ + 'id' => $user->id, + 'name' => $user->name, + 'email' => $user->email, + 'is_admin' => strval($user->is_admin), + 'avatar' => $user->avatar, + 'phone' => $user->phone, + 'address' => $user->address, + 'role' => $user->roles->pluck('name'), + 'total_jobdesk' => $user->jobdesk->count(), + 'jobdesk_on_progress' => $user->jobdesk->where('status', 'Progress')->count(), + 'jobdesk_selesai' => $user->jobdesk->where('status', 'Selesai')->count(), + ]; + }; + + if (!$paginate) { + $users = $query->get()->map($transformUser); + return response()->json([ + 'data' => $users, + 'total' => $users->count(), + ]); + } + + // Paginate with transform + $paginated = $query->paginate(25); + $paginated->getCollection()->transform($transformUser); + + return response()->json($paginated); + } + + public function show($id) + { + $this->authorize('view', User::find($id)); + $user = User::find($id); + $user->load('jobdesk'); + + $response = [ + 'id' => $user->id, + 'name' => $user->name, + 'email' => $user->email, + 'is_admin' => strval($user->is_admin), + 'avatar' => $user->avatar, + 'phone' => $user->phone, + 'address' => $user->address, + 'role' => $user->roles->pluck('name'), + 'total_jobdesk' => $user->jobdesk->count(), + 'jobdesk_on_progress' => $user->jobdesk->where('status', 'Progress')->count(), + 'jobdesk_selesai' => $user->jobdesk->where('status', 'Selesai')->count(), + ]; + + return response()->json($response); + } + + public function update(Request $request, $id) + { + $user = User::findOrFail($id); + $requested_user = $request->user(); + $this->authorize('update', $user); + + $validated = $request->validate([ + 'name' => 'required|string|max:255', + 'email' => 'required|email|max:255|unique:users,email,' . $id, + 'phone' => 'required|string', + 'address' => 'required|string', + 'role' => 'nullable|string', + 'password' => 'nullable|string|min:8|confirmed', + 'avatar' => 'nullable', + ]); + + // Handle avatar upload + if ($request->hasFile('avatar')) { + if ($user->avatar) { + Storage::disk('public')->delete($user->avatar); + } + $validated['avatar'] = $request->file('avatar')->store('avatars', 'public'); + $validated['avatar'] = asset('storage/' . $validated['avatar']); + } else { + unset($validated['avatar']); + } + + // Handle password hashing + if (!empty($validated['password'])) { + $validated['password'] = Hash::make($validated['password']); + } else { + unset($validated['password']); + } + + // Handle role separately, cek dulu + $roleName = $validated['role'] ?? null; + unset($validated['role']); // jangan ikut update di table users + + // Update user data + $updated = $user->update($validated); + + if ($updated && $roleName) { + $role = Role::where('name', $roleName)->first(); + if (!$role) { + return response()->json(['message' => 'Role not found'], 404); + } + $user->syncRoles([$role->name]); + } + + if ($updated) { + $response = [ + 'id' => $user->id, + 'name' => $user->name, + 'email' => $user->email, + 'is_admin' => strval($user->is_admin), + 'avatar' => $user->avatar, + 'phone' => $user->phone, + 'address' => $user->address, + 'role' => $user->roles->pluck('name'), + 'total_jobdesk' => $user->jobdesk->count(), + 'jobdesk_on_progress' => $user->jobdesk->where('status', 'Progress')->count(), + 'jobdesk_selesai' => $user->jobdesk->where('status', 'Selesai')->count(), + ]; + return response()->json($response, 200); + } + + return response()->json(['message' => 'Update failed'], 400); + } + + + public function store(Request $request) + { + $this->authorize('create', User::class); + $validated = $request->validate([ + 'name' => 'required|string|max:255', + 'email' => 'required|email|max:255|unique:users,email', + 'phone' => 'required|string', + 'address' => 'required|string', + 'role' => 'nullable|string', + 'password' => 'required|string|min:8|confirmed', + 'avatar' => 'nullable|image|max:2048', // disarankan validasi image + ]); + + // Handle avatar upload + if ($request->hasFile('avatar')) { + $path = $request->file('avatar')->store('avatars', 'public'); + $validated['avatar'] = asset('storage/' . $path); + } else { + unset($validated['avatar']); + } + + // Hash password + $validated['password'] = Hash::make($validated['password']); + + // Tangani role secara terpisah + $roleName = $validated['role'] ?? null; + unset($validated['role']); // jangan masuk ke User::create + + // Create user + $user = User::create($validated); + + // Assign role jika tersedia + if ($roleName) { + $role = Role::where('name', $roleName)->first(); + if (!$role) { + return response()->json(['message' => 'Role not found'], 404); + } + $user->assignRole($role->name); + } + + // Buat response + $response = [ + 'id' => $user->id, + 'name' => $user->name, + 'email' => $user->email, + 'is_admin' => strval($user->is_admin), + 'avatar' => $user->avatar, + 'phone' => $user->phone, + 'address' => $user->address, + 'role' => $user->roles->pluck('name'), + 'total_jobdesk' => $user->jobdesk->count(), + 'jobdesk_on_progress' => $user->jobdesk->where('status', 'Progress')->count(), + 'jobdesk_selesai' => $user->jobdesk->where('status', 'Selesai')->count(), + ]; + + return response()->json($response, 201); + } + + public function destroy($id) + { + $user = User::findOrFail($id); + $this->authorize('delete', $user); + $user->delete(); + return response()->json($user); + } +} diff --git a/app/Http/Controllers/MetaController.php b/app/Http/Controllers/MetaController.php new file mode 100644 index 0000000..ec4b8ef --- /dev/null +++ b/app/Http/Controllers/MetaController.php @@ -0,0 +1,72 @@ +query('paginate'); + + if ($paginate === 'false') { + $meta = Meta::orderByDesc('id')->get(); + } else { + $meta = Meta::orderByDesc('id')->paginate(25); + } + + return response()->json($meta); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) + { + $request->validate([ + 'meta_key' => 'string|max:255|nullable', + 'meta_value' => 'string|nullable', + ]); + + $meta = Meta::create($request->all()); + + return response()->json($meta, 201); + } + + /** + * Display the specified resource. + */ + public function show(Meta $meta) + { + return response()->json($meta); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, Meta $meta) + { + $request->validate([ + 'meta_key' => 'string|max:255|nullable', + 'meta_value' => 'string|nullable', + ]); + + $meta->update($request->all()); + + return response()->json($meta); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Meta $meta) + { + $meta->delete(); + return response()->json(null, 204); + } +} diff --git a/app/Http/Controllers/NotificationController.php b/app/Http/Controllers/NotificationController.php new file mode 100644 index 0000000..06c779b --- /dev/null +++ b/app/Http/Controllers/NotificationController.php @@ -0,0 +1,58 @@ +notifications; + return response()->json($notifications); + } + + public function update($id) + { + $notification = Auth::user()->notifications()->find($id); + $notification->markAsRead(); + + return response()->json($notification); + } + + public function read($id) + { + $notification = Auth::user()->notifications()->find($id); + $notification->markAsRead(); + + return response()->json($notification); + } + + public function readAll() + { + $notifications = Auth::user()->notifications()->get(); + $notifications->each(function ($notification) { + $notification->markAsRead(); + }); + + return response()->json($notifications); + } + public function unread() + { + $notifications = Auth::user()->unreadNotifications->map(function ($data) { + return [ + 'id' => $data->id, + 'message' => $data->data['message'], + 'time' => $data->created_at, + 'url' => isset($data->data['order']['customer']['id']) + ? '/order?konsumen_id=' . $data->data['order']['customer']['id'] + : null, + 'order_id' => $data->data['order']['id'] ?? null + ]; + }); + + return response()->json($notifications); + } +} diff --git a/app/Http/Controllers/OrderController.php b/app/Http/Controllers/OrderController.php new file mode 100644 index 0000000..194f0cb --- /dev/null +++ b/app/Http/Controllers/OrderController.php @@ -0,0 +1,473 @@ + 'required', + 'product_id' => 'required', + 'price' => 'required', + 'paid' => 'nullable', + 'payment_method' => 'required', + 'meta' => 'nullable', + 'customer_id' => 'required|exists:customers,id', + ]; + + public function index(Request $request) + { + $customerId = $request->query('customer_id'); + $paginate = $request->query('paginate', true); + $name = $request->query('name'); + $productQuery = $request->query('product'); + $bank = $request->query('bank'); + $dari = $request->query('dari'); + $sampai = $request->query('sampai'); + $status = $request->query('status'); + $user = $request->user(); + + // Optimized eager loading based on frontend needs + $query = Order::with([ + 'customer:id,name,phone,address', + 'customer.meta:id,customer_id,meta_key,meta_value', + 'jobdesks:id,order_id,status,description', + 'product:id,name,category,description', + 'product.metaProducts:id,product_id,meta_id', + 'product.metaProducts.meta:id,name,type' + ]) + ->select([ + 'id', + 'no_order', + 'customer_id', + 'product_id', + 'order_date', + 'price', + 'payment_method', + 'paid', + 'meta', + 'lampiran', + 'created_at' + ]); + + // Apply customer filter + if (isset($customerId) && $customerId !== 'undefined') { + $query->where('customer_id', $customerId); + } + + // Apply search and date filters + $this->applyFilters($query, $name, $productQuery, $bank, $dari, $sampai); + + // Apply status filter - optimized for frontend tabs + $this->applyStatusFilter($query, $status); + + // Default behavior: show oldest unfinished orders first, then newest finished orders + if (!$status) { + // When no status filter is applied, prioritize unfinished orders (oldest first) + $query->orderBy( + \DB::raw("CASE + WHEN ( + SELECT COUNT(*) + FROM jobdesks + WHERE jobdesks.order_id = orders.id + AND jobdesks.status != 'Selesai' + ) > 0 + OR ( + SELECT COUNT(*) + FROM jobdesks + WHERE jobdesks.order_id = orders.id + ) = 0 + THEN 0 + ELSE 1 + END") + ) + ->orderBy('created_at', 'asc'); // Oldest unfinished first + } else { + // When status filter is applied, use latest first + $query->orderBy('created_at', 'desc'); + } + + // Get results with or without pagination + return $this->getOrderResults($query, $paginate, $user); + } + + private function applyFilters($query, $name, $productQuery, $bank, $dari, $sampai) + { + // Search by customer name (minimum 3 characters as per frontend) + if ($name && strlen($name) >= 3) { + $query->whereHas('customer', function ($q) use ($name) { + $q->where('name', 'like', '%' . $name . '%'); + }); + } + + // Search by product name (minimum 3 characters as per frontend) + if ($productQuery && strlen($productQuery) >= 3) { + $query->whereHas('product', function ($q) use ($productQuery) { + $q->where('name', 'like', '%' . $productQuery . '%'); + }); + } + + // Bank/category filter optimized for frontend dropdown + if ($bank) { + if ($bank === 'Perorangan') { + $query->where(function ($q) { + $q->whereHas('customer.meta', function ($subQuery) { + $subQuery->where('meta_key', 'bank') + ->where('meta_value', 'Perorangan'); + })->orWhereDoesntHave('customer.meta', function ($subQuery) { + $subQuery->where('meta_key', 'bank'); + }); + }); + } else { + $query->whereHas('customer.meta', function ($subQuery) use ($bank) { + $subQuery->where('meta_key', 'bank') + ->where('meta_value', $bank); + }); + } + } + + // Date range filter optimized for frontend date picker + if ($dari && $sampai) { + $query->whereBetween('order_date', [ + date('Y-m-d', strtotime($dari)), + date('Y-m-d', strtotime($sampai)) + ]); + } elseif ($dari) { + $query->where('order_date', '>=', date('Y-m-d', strtotime($dari))); + } elseif ($sampai) { + $query->where('order_date', '<=', date('Y-m-d', strtotime($sampai))); + } + } + + private function applyStatusFilter($query, $status) + { + if (!$status) { + return; // Show all orders by default + } + + switch ($status) { + case 'Masuk': + $query->where(function ($q) { + $q->whereHas('jobdesks', function ($query) { + $query->where('status', '!=', 'Selesai'); + })->orWhereDoesntHave('jobdesks'); + }); + break; + + case 'Selesai': + $query->whereDoesntHave('jobdesks', function ($query) { + $query->where('status', '!=', 'Selesai'); + })->whereNotNull('lampiran')->whereHas('jobdesks'); + break; + + case 'Arsip': + $query->whereDoesntHave('jobdesks', function ($query) { + $query->where('status', '!=', 'Selesai'); + })->whereNull('lampiran')->whereHas('jobdesks'); + break; + } + } + + private function getOrderResults($query, $paginate, $user) + { + if ($paginate === 'false' || $paginate === false) { + $orders = $query->get(); + return response()->json($orders->map(function ($order) use ($user) { + return $this->formatOrderResponse($order, $user); + })); + } + + $orders = $query->paginate(25); + $orders->through(function ($order) use ($user) { + return $this->formatOrderResponse($order, $user); + }); + + return response()->json($orders); + } + + private function formatOrderResponse($order, $user) + { + // Calculate jobdesk count from loaded relation instead of separate query + $jobdeskCount = $order->jobdesks ? $order->jobdesks->count() : 0; + $completedJobdesks = $order->jobdesks ? $order->jobdesks->where('status', 'Selesai')->count() : 0; + + return [ + 'id' => $order->id, + 'no_order' => $order->no_order, + 'customer_id' => $order->customer?->id, + 'order_date' => $order->order_date, + 'product_id' => $order->product?->id, + 'price' => $user->role !== 'staff' ? $order->price : 0, + 'payment_method' => $order->payment_method, + 'paid' => $user->role !== 'staff' ? $order->paid : 0, + 'meta' => $order->meta, + 'lampiran' => $order->lampiran, + 'jobdesk_count' => $jobdeskCount, + 'completed_jobdesks' => $completedJobdesks, // Added for frontend progress calculation + 'progress_percentage' => $jobdeskCount > 0 ? round(($completedJobdesks / $jobdeskCount) * 100) : 0, + 'created_at' => $order->created_at, + 'customer' => $order->customer ? [ + 'id' => $order->customer->id, + 'name' => $order->customer->name, + 'phone' => $order->customer->phone, + 'address' => $order->customer->address, + 'meta' => $order->customer->meta ? $order->customer->meta->map(function ($meta) { + return [ + 'id' => $meta->id, + 'meta_key' => $meta->meta_key, + 'meta_value' => $meta->meta_value + ]; + }) : [] + ] : null, + 'jobdesks' => $order->jobdesks ? $order->jobdesks->map(function ($jobdesk) { + return [ + 'id' => $jobdesk->id, + 'status' => $jobdesk->status, + 'description' => $jobdesk->description ?? '-' + ]; + }) : [], + 'product' => $order->product ? [ + 'id' => $order->product->id, + 'name' => $order->product->name, + 'category' => $order->product->category, + 'description' => $order->product->description, + 'meta_products' => $order->product->metaProducts ? $order->product->metaProducts->map(function ($metaProduct) { + return $metaProduct->meta ? [ + 'id' => $metaProduct->meta->id, + 'name' => $metaProduct->meta->name, + 'type' => $metaProduct->meta->type + ] : null; + })->filter() : [], + ] : null, + 'role' => $user->role, + // Added fields for frontend convenience + 'is_completed' => $jobdeskCount > 0 && $completedJobdesks === $jobdeskCount, + 'has_lampiran' => !empty($order->lampiran), + 'last_jobdesk_status' => $order->jobdesks && $order->jobdesks->isNotEmpty() + ? $this->getLastJobdeskStatus($order->jobdesks) + : null, + ]; + } + + private function getLastJobdeskStatus($jobdesks) + { + // Priority order as per frontend logic + $priority = ['Progress', 'Masuk', 'Selesai']; + + foreach ($priority as $status) { + $lastJobdesk = $jobdesks->where('status', $status)->last(); + if ($lastJobdesk) { + return [ + 'status' => $status, + 'description' => $lastJobdesk->description ?? '-' + ]; + } + } + + return [ + 'status' => null, + 'description' => '-' + ]; + } + + public function show(Order $order) + { + $order = Order::find($order->id)->load('customer'); + return response()->json($order); + } + + public function update(Request $request, Order $order) + { + $user = $request->user(); + + // Jika ada lampiran, validasi hanya file lampiran + if ($request->hasFile('lampiran')) { + $request->validate([ + 'lampiran' => 'required|mimes:pdf|max:15000', + ], [ + 'lampiran.required' => 'Lampiran harus diisi.', + 'lampiran.mimes' => 'Lampiran harus berupa file PDF.', + 'lampiran.max' => 'Lampiran tidak boleh lebih besar dari 15MB.', + ]); + + // Simpan file lampiran baru + $filePath = $request->file('lampiran')->store('lampiran', 'public'); + // simpan lengkap dengan asset + $filePath = asset('storage/' . $filePath); + + // Hapus dokumen lama jika ada + if ($order->lampiran) { + Storage::disk('public')->delete($order->lampiran); + } + + // Update order dengan lampiran baru + $order->update(['lampiran' => $filePath]); + } else { + // Jika tidak ada lampiran, validasi field lain + $validatedData = $request->validate([ + 'order_date' => 'required', + 'product_id' => 'required', + 'price' => 'required', + 'paid' => 'nullable', + 'payment_method' => 'required', + 'meta' => 'nullable', + 'customer' => 'required', + ], [ + 'order_date.required' => 'Tanggal pesanan harus diisi.', + 'product_id.required' => 'Produk harus dipilih.', + 'price.required' => 'Harga harus diisi.', + 'payment_method.required' => 'Metode pembayaran harus dipilih.', + 'customer.required' => 'Pelanggan harus diisi.', + ]); + + // Update order dengan data yang sudah divalidasi + $order->update($validatedData); + } + + // Load relasi dan siapkan respons + $order->load('customer', 'jobdesks', 'product', 'product.metaProducts.meta'); + $response = [ + 'id' => $order->id, + 'no_order' => $order->no_order, + 'customer_id' => $order->customer->id, + 'order_date' => $order->order_date, + 'product_id' => $order->product->id, + 'price' => $user->role !== 'staff' ? $order->price : 0, + 'payment_method' => $order->payment_method, + 'paid' => $user->role !== 'staff' ? $order->paid : 0, + 'meta' => $order->meta, + 'lampiran' => $order->lampiran, + 'jobdesk_count' => $order->jobdesks()->count(), + 'created_at' => $order->created_at, + 'customer' => [ + 'id' => $order->customer->id, + 'name' => $order->customer->name, + 'phone' => $order->customer->phone, + 'address' => $order->customer->address, + ], + 'jobdesks' => $order->jobdesks, + 'product' => [ + 'id' => $order->product->id, + 'name' => $order->product->name, + 'category' => $order->product->category, + 'description' => $order->product->description, + 'meta_products' => $order->product->metaProducts->pluck('meta'), + ] + ]; + + return response()->json($response); + } + + public function store(Request $request) + { + $user = $request->user(); + $validator = Validator::make($request->all(), [ + 'order_date' => 'required', + 'product_id' => 'required', + 'price' => 'required', + 'paid' => 'required', + 'payment_method' => 'required', + 'meta' => 'nullable', + 'customer_id' => 'required|exists:customers,id', + ]); + + if ($validator->fails()) { + return response()->json([ + 'message' => 'Validation errors', + 'errors' => $validator->errors(), + ], 422); + } + + $order = Order::create($validator->validated()); + $order->load('customer', 'jobdesks', 'product', 'product.metaProducts.meta'); + + $users = User::whereHas('permissions', function ($query) { + $query->where('name', 'menu:settings'); + })->get(); + Notification::send($users, new NewOrderNotification($order)); + $response = [ + 'id' => $order->id, + 'no_order' => $order->no_order, + 'customer_id' => $order->customer->id, + 'order_date' => $order->order_date, + 'product_id' => $order->product->id, + 'price' => $user->role !== 'staff' ? $order->price : 0, + 'payment_method' => $order->payment_method, + 'paid' => $user->role !== 'staff' ? $order->paid : 0, + 'meta' => $order->meta, + 'lampiran' => $order->lampiran, + 'jobdesk_count' => $order->jobdesks()->count(), + 'created_at' => $order->created_at, + 'customer' => [ + 'id' => $order->customer->id, + 'name' => $order->customer->name, + 'phone' => $order->customer->phone, + 'address' => $order->customer->address, + ], + 'jobdesks' => $order->jobdesks, + 'product' => [ + 'id' => $order->product->id, + 'name' => $order->product->name, + 'category' => $order->product->category, + 'description' => $order->product->description, + 'meta_products' => $order->product->metaProducts->pluck('meta'), + ] + ]; + return response()->json($response); + } + + public function destroy(Order $order) + { + $order = Order::find($order->id); + $order->jobdesks()->delete(); + $order->delete(); + return response()->json($order); + } + + /** + * Get order statistics for dashboard + */ + public function stats(Request $request) + { + $customerId = $request->query('customer_id'); + + $query = Order::query(); + + if (isset($customerId) && $customerId !== 'undefined') { + $query->where('customer_id', $customerId); + } + + $totalOrders = $query->count(); + + // Count orders by status based on jobdesk completion + $masukCount = (clone $query)->where(function ($q) { + $q->whereHas('jobdesks', function ($query) { + $query->where('status', '!=', 'Selesai'); + })->orWhereDoesntHave('jobdesks'); + })->count(); + + $selesaiCount = (clone $query)->whereDoesntHave('jobdesks', function ($query) { + $query->where('status', '!=', 'Selesai'); + })->whereNotNull('lampiran')->whereHas('jobdesks')->count(); + + $arsipCount = (clone $query)->whereDoesntHave('jobdesks', function ($query) { + $query->where('status', '!=', 'Selesai'); + })->whereNull('lampiran')->whereHas('jobdesks')->count(); + + return response()->json([ + 'total_orders' => $totalOrders, + 'masuk' => $masukCount, + 'selesai' => $selesaiCount, + 'arsip' => $arsipCount, + 'completion_rate' => $totalOrders > 0 ? round((($selesaiCount + $arsipCount) / $totalOrders) * 100, 2) : 0 + ]); + } +} diff --git a/app/Http/Controllers/PermissionController.php b/app/Http/Controllers/PermissionController.php new file mode 100644 index 0000000..ba131e2 --- /dev/null +++ b/app/Http/Controllers/PermissionController.php @@ -0,0 +1,22 @@ +json($permissions); + } + + public function update(Request $request, User $user) + { + $permissions = $request->input('permissions'); + $user->syncPermissions($permissions); // Menggunakan Spatie + return response()->json(['message' => 'Permissions updated']); + } +} diff --git a/app/Http/Controllers/PostController.php b/app/Http/Controllers/PostController.php index 5bceec4..4cd25f8 100644 --- a/app/Http/Controllers/PostController.php +++ b/app/Http/Controllers/PostController.php @@ -2,78 +2,123 @@ namespace App\Http\Controllers; -use App\Models\Post; use Illuminate\Http\Request; -use Illuminate\Routing\Controllers\HasMiddleware; -use Illuminate\Routing\Controllers\Middleware as ControllersMiddleware; -use Illuminate\Support\Facades\Gate; +use Illuminate\Support\Facades\Storage; +use App\Models\Post; -class PostController extends Controller implements HasMiddleware +class PostController extends Controller { + public function index() + { + $posts = Post::latest()->paginate(10); + $posts->load('category'); + return response()->json($posts); + } - public static function middleware() - { - return [ - new ControllersMiddleware('auth:sanctum', except: ['index', 'show']) - ]; - } - /** - * Display a listing of the resource. - */ - public function index() - { - return Post::all(); + public function show(Post $post) + { + return response()->json($post); + } + + public function store(Request $request) + { + // Validasi input + $validated = $request->validate( + [ + 'title' => 'required|string|max:255', + 'content' => 'required|string', + 'category_id' => 'required|exists:categories,id', + 'featured_image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048', // Validasi file gambar + ], + [ + 'title.required' => 'Judul harus diisi.', + 'content.required' => 'Isi konten harus diisi.', + 'category_id.required' => 'Kategori harus dipilih.', + 'category_id.exists' => 'Kategori tidak ditemukan.', + 'featured_image.image' => 'File harus berupa gambar.', + 'featured_image.mimes' => 'Format file harus JPEG, PNG, JPG, atau GIF.', + 'featured_image.max' => 'Ukuran file maksimal 2MB.', + ] + ); + + // Handle Featured Image + if ($request->hasFile('featured_image')) { + $validated['featured_image'] = $request->file('featured_image')->store('images', 'public'); + $validated['featured_image'] = asset('storage/' . $validated['featured_image']); + } else { + unset($validated['featured_image']); // Jika tidak ada file, hapus key ini } - /** - * Store a newly created resource in storage. - */ - public function store(Request $request) - { + // Tambahkan slug dan user_id + $slug = str_replace(' ', '-', strtolower($validated['title'])); + $validated['slug'] = $slug; + $validated['user_id'] = $request->user()->id; - $fdields = $request->validate([ - 'title' => 'required|max:255', - 'body' => 'required', - ]); + // Simpan data ke database + $post = Post::create($validated); - $post = $request->user()->posts()->create($fdields); + // Return respons JSON + return response()->json($post, 201); + } - return $post; - } + public function update(Request $request, Post $post) + { + // Validasi input + $validated = $request->validate( + [ + 'title' => 'sometimes|string|max:255', + 'content' => 'sometimes|string', + 'category_id' => 'sometimes|exists:categories,id', + 'featured_image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048', // Validasi file gambar + ], + [ + 'title.required' => 'Judul harus diisi.', + 'content.required' => 'Isi konten harus diisi.', + 'category_id.required' => 'Kategori harus dipilih.', + 'category_id.exists' => 'Kategori tidak ditemukan.', + 'featured_image.image' => 'File harus berupa gambar.', + 'featured_image.mimes' => 'Format file harus JPEG, PNG, JPG, atau GIF.', + 'featured_image.max' => 'Ukuran file maksimal 2MB.', + ] + ); - /** - * Display the specified resource. - */ - public function show(Post $post) - { - return $post; + // Handle Featured Image + if ($request->hasFile('featured_image')) { + // Hapus gambar lama jika ada + if ($post->featured_image) { + Storage::disk('public')->delete($post->featured_image); + } + // Simpan gambar baru + $validated['featured_image'] = $request->file('featured_image')->store('images', 'public'); + $validated['featured_image'] = asset('storage/' . $validated['featured_image']); + } elseif (is_string($request->featured_image)) { + unset($validated['featured_image']); // Jika featured_image adalah string, abaikan } - /** - * Update the specified resource in storage. - */ - public function update(Request $request, Post $post) - { - Gate::authorize('modify', $post); + // Update slug jika title berubah + if (isset($validated['title'])) { + $slug = str_replace(' ', '-', strtolower($validated['title'])); + $validated['slug'] = $slug; + } - $fdields = $request->validate([ - 'title' => 'required|max:255', - 'body' => 'required', - ]); + // Update data post + $post->update($validated); - $post->update($fdields); + // Return respons JSON + return response()->json($post); + } - return $post; + public function destroy(Post $post) + { + // Hapus gambar dari storage jika ada + if ($post->featured_image) { + Storage::disk('public')->delete($post->featured_image); } - /** - * Remove the specified resource from storage. - */ - public function destroy(Post $post) - { - Gate::authorize('modify', $post); - $post->delete(); + // Hapus data post + $post->delete(); - return ['message' => 'Post Deleted']; - } + // Return respons JSON + return response()->json(['message' => 'Post deleted successfully']); + } } diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php new file mode 100644 index 0000000..50c057a --- /dev/null +++ b/app/Http/Controllers/ProductController.php @@ -0,0 +1,148 @@ +query('paginate'); + $name = $request->query('name'); + + // Query dasar semua products + $query = Product::with('metaProducts.meta', 'orders'); + + // Filter berdasarkan name jika ada + if ($name && strlen($name) > 2) { + $query->where('name', 'like', '%' . $name . '%'); + } + + // Shorting descending + $query->orderBy('created_at', 'desc'); + + // Paginate the results + if ($paginate === 'false') { + $products = $query->get()->map(function ($data) { + return [ + 'id' => $data->id, + 'name' => $data->name, + 'description' => $data->description, + 'category' => $data->category, + 'meta' => $data->metaProducts->pluck('meta')->pluck('id'), + 'meta_products' => $data->metaProducts->pluck('meta'), + 'order_count' => $data->orders->count(), + ]; + }); + } else { + $products = $query->paginate(25); + $products->getCollection()->transform(function ($data) { + return [ + 'id' => $data->id, + 'name' => $data->name, + 'description' => $data->description, + 'category' => $data->category, + 'meta_products' => $data->metaProducts->pluck('meta')->pluck('id'), + 'order_count' => $data->orders->count(), + ]; + }); + } + + return response()->json($products, 200); + } + + /** + * Store a newly created product in storage. + */ + public function store(Request $request) + { + $validatedData = $request->validate([ + 'name' => 'string|max:255|nullable', + 'category' => 'string', + 'description' => 'string|nullable', + 'meta_products' => 'array|nullable', + ]); + + $product = Product::create($validatedData); + + if ($request->has('meta_products')) { + foreach ($request->input('meta_products') as $metaId) { + $product->metaProducts()->create(['meta_id' => $metaId]); + } + } + $response = [ + 'id' => $product->id, + 'name' => $product->name, + 'description' => $product->description, + 'category' => $product->category, + 'meta_products' => $product->metaProducts->pluck('meta')->pluck('id'), + 'order_count' => $product->orders->count(), + ]; + return response()->json($response, 201); + } + + /** + * Display the specified product. + */ + public function show($id) + { + $product = Product::findOrFail($id); + return response()->json($product); + } + + /** + * Update the specified product in storage. + */ + public function update(Request $request, $id) + { + $validatedData = $request->validate([ + 'name' => 'string|max:255|nullable', + 'category' => 'string', + 'description' => 'string|nullable', + 'meta_products' => 'array|nullable', + 'meta_id' => 'array|nullable', + ]); + + $product = Product::with('metaProducts.meta', 'orders')->findOrFail($id); + + $product->update($validatedData); + + // Update meta_id + if ($request->has('meta_products')) { + $product->metaProducts()->delete(); + foreach ($request->input('meta_products') as $metaId) { + $product->metaProducts()->create(['meta_id' => $metaId]); + } + } + $product->load('metaProducts.meta', 'orders'); + + $response = [ + 'id' => $product->id, + 'name' => $product->name, + 'description' => $product->description, + 'category' => $product->category, + 'meta_products' => $product->metaProducts->pluck('meta')->pluck('id'), + 'order_count' => $product->orders->count(), + ]; + return response()->json($response, 200); + } + + /** + * Remove the specified product from storage. + */ + public function destroy($id) + { + $product = Product::findOrFail($id); + $product->delete(); + + // hapus meta_id + $product->metaProducts()->delete(); + + return response()->json(null, 204); + } +} diff --git a/app/Http/Controllers/RoleController.php b/app/Http/Controllers/RoleController.php new file mode 100644 index 0000000..2ee8df3 --- /dev/null +++ b/app/Http/Controllers/RoleController.php @@ -0,0 +1,75 @@ +get(); + return $roles; + } + + public function store(Request $request) + { + $validated = $request->validate([ + 'name' => 'required|string|unique:roles,name', + 'guard_name' => 'nullable|string', + 'capabilities' => 'nullable|array', // tambahkan validasi capabilities + 'capabilities.*' => 'string|exists:permissions,name', // pastikan permission ada + ]); + + $role = Role::create([ + 'name' => $validated['name'], + 'guard_name' => $validated['guard_name'] ?? 'web', + ]); + + // Sinkronkan capabilities (permissions) jika tersedia + if (isset($validated['capabilities'])) { + $permissions = Permission::whereIn('name', $validated['capabilities'])->get(); + $role->syncPermissions($permissions); + } + + return $role; + } + + public function show(Role $role) + { + $role->load('permissions'); // <= tambahkan ini juga + return $role; + } + + public function update(Request $request, Role $role) + { + // Validasi nama role + $validated = $request->validate([ + 'name' => 'required|string|unique:roles,name,' . $role->id, + 'capabilities' => 'nullable|array', + 'capabilities.*' => 'string|exists:permissions,name', + ]); + + // Update nama role + $role->update([ + 'name' => $validated['name'], + 'guard_name' => $validated['guard_name'] ?? 'web', + ]); + + // Sinkronkan capabilities (permissions) jika tersedia + if (isset($validated['capabilities'])) { + $permissions = Permission::whereIn('name', $validated['capabilities'])->get(); + $role->syncPermissions($permissions); + } + + return $role; + } + + public function destroy(Role $role) + { + $role->delete(); + return response()->noContent(); + } +} diff --git a/app/Http/Controllers/SendNotificationController.php b/app/Http/Controllers/SendNotificationController.php new file mode 100644 index 0000000..5ff708c --- /dev/null +++ b/app/Http/Controllers/SendNotificationController.php @@ -0,0 +1,36 @@ +validate([ + 'jobdesk_id' => 'required|integer|exists:jobdesks,id', + 'message' => 'nullable|string', + ]); + + // Ambil jobdesk dan pengguna terkait + $jobdesk = Jobdesk::find($request->jobdesk_id); + $user = User::find($jobdesk->user_id); + + // Kirim notifikasi + Notification::send($user, new NewOrderNotification($request->message)); + + return response()->json(['message' => 'Notifikasi pengingat jobdesk berhasil dikirim!']); + } +} diff --git a/app/Http/Controllers/SettingBankController.php b/app/Http/Controllers/SettingBankController.php new file mode 100644 index 0000000..9ef5eda --- /dev/null +++ b/app/Http/Controllers/SettingBankController.php @@ -0,0 +1,50 @@ +first(); + $banks = $setting ? json_decode($setting->setting_value, true) : []; + + return response()->json($banks); + } + + /** + * Store or update the bank settings. + */ + public function store(Request $request) + { + $validatedData = $request->validate([ + 'banks' => 'required|array', + 'banks.*.name' => 'required|string|max:255', + ]); + + $banksJson = json_encode($validatedData['banks']); + + Setting::updateOrCreate( + ['setting_key' => 'banks'], + ['setting_value' => $banksJson] + ); + + return response()->json(['message' => 'Bank settings saved successfully.']); + } + + /** + * Remove the bank settings. + */ + public function destroy() + { + Setting::where('setting_key', 'banks')->delete(); + + return response()->json(['message' => 'Bank settings deleted successfully.']); + } +} diff --git a/app/Http/Controllers/SettingController.php b/app/Http/Controllers/SettingController.php new file mode 100644 index 0000000..464e5b5 --- /dev/null +++ b/app/Http/Controllers/SettingController.php @@ -0,0 +1,207 @@ +pluck('setting_value', 'setting_key')->toArray(); + + if ($settingKey = $request->get('setting_key')) { + return response()->json([$settingKey => $settings[$settingKey] ?? null]); + } + + return response()->json($settings); + } + + /** + * Store or update settings in storage. + */ + public function store(Request $request) + { + // Define validation rules + $validatedData = $request->validate($this->validationRules()); + + // Handle file upload if exists + if ($request->hasFile('pdf_sample')) { + $validatedData['pdf_sample'] = $request->file('pdf_sample')->store('pdf_samples', 'public'); + } + + // Save or update settings + $this->saveSettings($validatedData); + + return $this->responseSuccess('Settings saved successfully.', $validatedData); + } + + /** + * Display the specified setting. + */ + public function show($key) + { + $setting = Setting::where('setting_key', $key)->firstOrFail(); + + return response()->json($setting); + } + + /** + * Update the specified setting in storage. + */ + public function update(Request $request, $key) + { + $setting = Setting::where('setting_key', $key)->firstOrFail(); + + // Define validation rules + $validatedData = $request->validate($this->validationRules()); + + // Handle file upload and delete previous file if necessary + if ($request->hasFile('pdf_sample')) { + Storage::disk('public')->delete($setting->setting_value); + $validatedData['pdf_sample'] = $request->file('pdf_sample')->store('pdf_samples', 'public'); + } + + // Update the setting + $setting->update($validatedData); + + return $this->responseSuccess('Setting updated successfully.', $setting); + } + + /** + * Remove the specified setting from storage. + */ + public function destroy($key) + { + $setting = Setting::where('setting_key', $key)->firstOrFail(); + + // Delete associated file if exists + if ($setting->setting_value) { + Storage::disk('public')->delete($setting->setting_value); + } + + // Delete the setting record + $setting->delete(); + + return $this->responseSuccess('Setting deleted successfully.'); + } + + /** + * Get background settings specifically + */ + public function getBackground() + { + $backgroundSettings = Setting::whereIn('setting_key', ['background_color', 'background_image', 'login_style']) + ->pluck('setting_value', 'setting_key') + ->toArray(); + + return response()->json([ + 'color' => $backgroundSettings['background_color'] ?? '', + 'image' => $backgroundSettings['background_image'] ? asset('storage/' . $backgroundSettings['background_image']) : null, + 'style' => $backgroundSettings['login_style'] ?? 'center' + ]); + } + + /** + * Store background settings specifically + */ + public function storeBackground(Request $request) + { + $validatedData = $request->validate([ + 'color' => 'nullable|string|max:50', + 'style' => 'nullable|string|in:center,side', + 'image' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:1024', // 1MB max + ]); + + $settings = []; + + // Handle color + if (isset($validatedData['color'])) { + $settings['background_color'] = $validatedData['color']; + } + + // Handle style + if (isset($validatedData['style'])) { + $settings['login_style'] = $validatedData['style']; + } + + // Handle image upload + if ($request->hasFile('image')) { + // Delete old image if exists + $oldImageSetting = Setting::where('setting_key', 'background_image')->first(); + if ($oldImageSetting && $oldImageSetting->setting_value) { + Storage::disk('public')->delete($oldImageSetting->setting_value); + } + + $imagePath = $request->file('image')->store('backgrounds', 'public'); + $settings['background_image'] = $imagePath; + } + + // Save settings + $this->saveSettings($settings); + + return $this->responseSuccess('Background settings saved successfully.', [ + 'color' => $settings['background_color'] ?? '', + 'image' => isset($settings['background_image']) ? asset('storage/' . $settings['background_image']) : null, + 'style' => $settings['login_style'] ?? 'center' + ]); + } + + /** + * Validation rules for storing/updating settings. + */ + protected function validationRules() + { + return [ + 'app_name' => 'nullable|string|max:255', + 'app_code' => 'nullable|string|max:255', + 'app_description' => 'nullable|string|max:500', + 'address' => 'nullable|string|max:255', + 'banks' => 'nullable|string|max:2000', + 'pekerjaan' => 'nullable|string|max:10000', + 'pdf_sample' => 'nullable', + 'email' => 'nullable|email|max:255', + 'new_order' => 'nullable|string', + 'project_assignment' => 'nullable|string', + 'followup_project' => 'nullable|string', + + // Background/Login settings + 'color' => 'nullable|string|max:50', + 'style' => 'nullable|string|max:50', + 'image' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:1024', // 1MB max + ]; + } + + /** + * Save settings to the database. + */ + protected function saveSettings(array $settings) + { + foreach ($settings as $key => $value) { + Setting::updateOrCreate( + ['setting_key' => $key], + ['setting_value' => $value] + ); + } + } + + /** + * Create a success response. + */ + protected function responseSuccess(string $message, $data = null) + { + return response()->json([ + 'success' => true, + 'message' => $message, + 'data' => $data + ], 200); + } +} diff --git a/app/Http/Controllers/SettingFaviconController.php b/app/Http/Controllers/SettingFaviconController.php new file mode 100644 index 0000000..85f986b --- /dev/null +++ b/app/Http/Controllers/SettingFaviconController.php @@ -0,0 +1,76 @@ +first(); + $favicon = $setting ? json_decode($setting->setting_value, true) : []; + + return response()->json([ + 'favicon' => $favicon['favicon'] ?? null, + ]); + } + + /** + * Store or update the favicon settings. + */ + public function store(Request $request) + { + $validatedData = $request->validate([ + 'favicon' => 'nullable|image|mimes:png,ico,jpg|max:1024', // Maksimal 1MB, hanya PNG atau ICO + ]); + + $favicon = []; + + // Ambil data lama + $setting = Setting::where('setting_key', 'favicon')->first(); + $oldFavicon = $setting ? json_decode($setting->setting_value, true) : []; + + // Hapus favicon lama jika ada favicon baru yang diunggah + if ($request->hasFile('favicon') && $request->file('favicon')->isValid()) { + if (!empty($oldFavicon['favicon'])) { + Storage::disk('public')->delete(str_replace(asset('storage/'), '', $oldFavicon['favicon'])); + } + + $faviconPath = $request->file('favicon')->store('favicons', 'public'); + $favicon['favicon'] = asset('storage/' . $faviconPath); + } else { + $favicon['favicon'] = $oldFavicon['favicon'] ?? null; + } + + // Simpan ke database + Setting::updateOrCreate( + ['setting_key' => 'favicon'], + ['setting_value' => json_encode($favicon)] + ); + + return response()->json(['message' => 'Favicon berhasil disimpan.']); + } + + /** + * Remove the favicon settings. + */ + public function destroy() + { + $setting = Setting::where('setting_key', 'favicon')->first(); + if ($setting) { + $favicon = json_decode($setting->setting_value, true); + if (!empty($favicon['favicon'])) { + Storage::disk('public')->delete(str_replace(asset('storage/'), '', $favicon['favicon'])); + } + $setting->delete(); + } + + return response()->json(['message' => 'Favicon berhasil dihapus.']); + } +} diff --git a/app/Http/Controllers/SettingLoginController.php b/app/Http/Controllers/SettingLoginController.php new file mode 100644 index 0000000..5f0c9c8 --- /dev/null +++ b/app/Http/Controllers/SettingLoginController.php @@ -0,0 +1,102 @@ +first(); + $background = $setting ? json_decode($setting->setting_value, true) : []; + + return response()->json([ + 'color' => $background['color'] ?? '#ffffff', + 'image' => $background['image'] ?? null, + 'style' => $background['style'] ?? 'center' + ]); + } + + /** + * Store or update the background settings. + */ + public function store(Request $request) + { + $validatedData = $request->validate([ + 'color' => 'nullable|string|max:50', // Lebih fleksibel untuk hex, rgba, dll + 'image' => 'nullable|image|mimes:jpeg,png,jpg,gif,webp|max:1024', // Maksimal 1MB, tambah webp + 'style' => 'nullable|string|in:center,side', + ]); + + $background = []; + + // Simpan warna jika ada + if ($request->has('color')) { + $background['color'] = $request->input('color'); + } + + // Simpan style jika ada + if ($request->has('style')) { + $background['style'] = $request->input('style'); + } + + // Hapus gambar lama dari storage jika ada gambar baru + if ($request->hasFile('image') && $request->file('image')->isValid()) { + $oldImage = Setting::where('setting_key', 'background')->first(); + if ($oldImage) { + $oldImageData = json_decode($oldImage->setting_value, true); + if (isset($oldImageData['image'])) { + // Extract path dari URL untuk menghapus file + $oldImageUrl = $oldImageData['image']; + $oldImagePath = str_replace(asset('storage/'), '', $oldImageUrl); + if ($oldImagePath !== $oldImageUrl) { // Pastikan ini file dari storage + Storage::disk('public')->delete($oldImagePath); + } + } + } + + $imagePath = $request->file('image')->store('backgrounds', 'public'); + $background['image'] = asset('storage/' . $imagePath); + } + + // Jika tidak ada gambar baru, gunakan gambar lama + if (!isset($background['image'])) { + $oldImage = Setting::where('setting_key', 'background')->first(); + if ($oldImage) { + $oldImageData = json_decode($oldImage->setting_value, true); + $background['image'] = $oldImageData['image'] ?? null; + } + } + + // Simpan ke database + Setting::updateOrCreate( + ['setting_key' => 'background'], + ['setting_value' => json_encode($background)], + ); + + return response()->json([ + 'success' => true, + 'message' => 'Pengaturan latar belakang berhasil disimpan.', + 'data' => [ + 'color' => $background['color'] ?? '', + 'image' => $background['image'] ?? null, + 'style' => $background['style'] ?? 'center' + ] + ]); + } + + /** + * Remove the background settings. + */ + public function destroy() + { + Setting::where('setting_key', 'background')->delete(); + return response()->json(['message' => 'Pengaturan latar belakang berhasil dihapus.']); + } +} diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php new file mode 100644 index 0000000..2953a16 --- /dev/null +++ b/app/Http/Controllers/UserController.php @@ -0,0 +1,30 @@ +user(); + $data = [ + 'id' => $user->id, + 'name' => $user->name, + 'email' => $user->email, + 'email_verified_at' => $user->email_verified_at, + 'is_admin' => strval($user->is_admin), + 'avatar' => $user->avatar, + 'phone' => $user->phone, + 'address' => $user->address, + 'role' => $user->roles->pluck('name'), + 'capabilities' => $user->getAllPermissions()->pluck('name'), + 'created_at' => $user->created_at, + 'updated_at' => $user->updated_at + ]; + return response()->json($data); + } +} diff --git a/app/Http/Middleware/EnsureEmailIsVerified.php b/app/Http/Middleware/EnsureEmailIsVerified.php new file mode 100644 index 0000000..f562059 --- /dev/null +++ b/app/Http/Middleware/EnsureEmailIsVerified.php @@ -0,0 +1,27 @@ +user() || + ($request->user() instanceof MustVerifyEmail && + ! $request->user()->hasVerifiedEmail())) { + return response()->json(['message' => 'Your email address is not verified.'], 409); + } + + return $next($request); + } +} diff --git a/app/Http/Requests/Auth/LoginRequest.php b/app/Http/Requests/Auth/LoginRequest.php new file mode 100644 index 0000000..4d3637a --- /dev/null +++ b/app/Http/Requests/Auth/LoginRequest.php @@ -0,0 +1,85 @@ + + */ + public function rules(): array + { + return [ + 'email' => ['required', 'string', 'email'], + 'password' => ['required', 'string'], + ]; + } + + /** + * Attempt to authenticate the request's credentials. + * + * @throws \Illuminate\Validation\ValidationException + */ + public function authenticate(): void + { + $this->ensureIsNotRateLimited(); + + if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) { + RateLimiter::hit($this->throttleKey()); + + throw ValidationException::withMessages([ + 'email' => __('auth.failed'), + ]); + } + + RateLimiter::clear($this->throttleKey()); + } + + /** + * Ensure the login request is not rate limited. + * + * @throws \Illuminate\Validation\ValidationException + */ + public function ensureIsNotRateLimited(): void + { + if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) { + return; + } + + event(new Lockout($this)); + + $seconds = RateLimiter::availableIn($this->throttleKey()); + + throw ValidationException::withMessages([ + 'email' => trans('auth.throttle', [ + 'seconds' => $seconds, + 'minutes' => ceil($seconds / 60), + ]), + ]); + } + + /** + * Get the rate limiting throttle key for the request. + */ + public function throttleKey(): string + { + return Str::transliterate(Str::lower($this->input('email')).'|'.$this->ip()); + } +} diff --git a/app/Http/Requests/Auth/ProfileUpdateRequest.php b/app/Http/Requests/Auth/ProfileUpdateRequest.php new file mode 100644 index 0000000..58e8196 --- /dev/null +++ b/app/Http/Requests/Auth/ProfileUpdateRequest.php @@ -0,0 +1,62 @@ +|string> + */ + public function rules(): array + { + $rules = [ + 'name' => ['required', 'string', 'max:255'], + 'email' => [ + 'required', + 'email', + 'max:255', + 'unique:' . User::class . ',email,' . $this->user()->id + ], + 'phone' => ['required', 'string'], + 'address' => ['required', 'string'], + 'role' => ['nullable', 'string'], + 'password' => ['nullable', 'string', 'min:8', 'confirmed'], + 'avatar' => ['nullable'], + 'email_notifications' => ['boolean'], + ]; + + if (is_string($this->avatar)) { + unset($rules['avatar']); + } elseif ($this->hasFile('avatar')) { + $rules['avatar'] = ['image', 'max:2048', 'mimes:jpeg,png,jpg']; + } + + return $rules; + } + + /** + * Prepare the data for validation. + */ + protected function prepareForValidation() + { + if ($this->has('email_notifications')) { + $this->merge([ + 'email_notifications' => filter_var($this->email_notifications, FILTER_VALIDATE_BOOLEAN), + ]); + } + } +} diff --git a/app/Models/Category.php b/app/Models/Category.php new file mode 100644 index 0000000..da98535 --- /dev/null +++ b/app/Models/Category.php @@ -0,0 +1,18 @@ +hasMany(Post::class); + } +} diff --git a/app/Models/Customer.php b/app/Models/Customer.php new file mode 100644 index 0000000..77f8d45 --- /dev/null +++ b/app/Models/Customer.php @@ -0,0 +1,27 @@ +hasMany(Order::class); + } + + public function meta() + { + return $this->hasMany(CustomerMeta::class); + } +} diff --git a/app/Models/CustomerMeta.php b/app/Models/CustomerMeta.php new file mode 100644 index 0000000..941dec8 --- /dev/null +++ b/app/Models/CustomerMeta.php @@ -0,0 +1,25 @@ +belongsTo(Customer::class); + } +} diff --git a/app/Models/Jobdesk.php b/app/Models/Jobdesk.php new file mode 100644 index 0000000..24f6e2c --- /dev/null +++ b/app/Models/Jobdesk.php @@ -0,0 +1,36 @@ + 'date', // Automatic casting to date + 'tanggal_selesai' => 'date', // Automatic casting to date + ]; + + public function order(): BelongsTo + { + return $this->belongsTo(Order::class); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/Meta.php b/app/Models/Meta.php new file mode 100644 index 0000000..fba0408 --- /dev/null +++ b/app/Models/Meta.php @@ -0,0 +1,23 @@ + */ + use HasFactory; + + protected $fillable = [ + 'name', + 'value', + 'type' + ]; + + public function metaProducts() + { + return $this->hasMany(MetaProduct::class, 'meta_id'); + } +} diff --git a/app/Models/MetaProduct.php b/app/Models/MetaProduct.php new file mode 100644 index 0000000..f97389a --- /dev/null +++ b/app/Models/MetaProduct.php @@ -0,0 +1,44 @@ +belongsTo(Meta::class); + } + + /** + * Get the related product. + */ + public function product() + { + return $this->belongsTo(Product::class); + } +} diff --git a/app/Models/Order.php b/app/Models/Order.php new file mode 100644 index 0000000..399902a --- /dev/null +++ b/app/Models/Order.php @@ -0,0 +1,72 @@ +value('setting_value') ?? 'AN'; + + static::creating(function ($model) { + $lastOrder = self::where('no_order', 'like', self::$appCode . '%') + ->orderBy('no_order', 'desc') + ->first(); + + // Mengambil angka dari no_order terakhir + $newIdNumber = 1; // Default jika tidak ada order sebelumnya + + if ($lastOrder) { + // Ambil angka dari no_order terakhir + $lastId = substr($lastOrder->no_order, strlen(self::$appCode)); + $newIdNumber = intval($lastId) + 1; // Tambahkan 1 + } + + // Buat no_order baru + $model->no_order = self::$appCode . str_pad($newIdNumber, 5, '0', STR_PAD_LEFT); + }); + } + + protected $fillable = [ + 'no_order', + 'customer_id', + 'order_date', + 'product_id', + 'price', + 'payment_method', + 'paid', + 'meta', + 'lampiran', + ]; + + protected $casts = [ + 'meta' => 'array', + ]; + + public function customer() + { + return $this->belongsTo(Customer::class); + } + + public function product() + { + return $this->belongsTo(Product::class); + } + + public function jobdesks() + { + return $this->hasMany(Jobdesk::class); + } +} diff --git a/app/Models/Post.php b/app/Models/Post.php index e721176..8509a37 100644 --- a/app/Models/Post.php +++ b/app/Models/Post.php @@ -9,13 +9,15 @@ class Post extends Model { use HasFactory; - protected $fillable = [ - 'title', - 'body', - ]; + protected $fillable = ['title', 'content', 'slug', 'user_id', 'featured_image', 'category_id']; public function user() { return $this->belongsTo(User::class); } + + public function category() + { + return $this->belongsTo(Category::class); + } } diff --git a/app/Models/Product.php b/app/Models/Product.php new file mode 100644 index 0000000..55220e1 --- /dev/null +++ b/app/Models/Product.php @@ -0,0 +1,34 @@ +hasMany(MetaProduct::class, 'product_id'); + } + + // Relationship to get metas through pivot table with meta details + public function metas() + { + return $this->belongsToMany(Meta::class, 'meta_product', 'product_id', 'meta_id') + ->select(['metas.id', 'metas.name', 'metas.type']); + } + + public function orders() + { + return $this->hasMany(Order::class); + } +} diff --git a/app/Models/Setting.php b/app/Models/Setting.php new file mode 100644 index 0000000..0dade44 --- /dev/null +++ b/app/Models/Setting.php @@ -0,0 +1,16 @@ + */ + use HasFactory, Notifiable, HasRoles; /** * The attributes that are mass assignable. * * @var array + * $table->id(); */ protected $fillable = [ 'name', 'email', 'password', + 'avatar', + 'phone', + 'address', + 'is_admin', + 'email_notifications', ]; /** @@ -43,11 +50,12 @@ protected function casts(): array return [ 'email_verified_at' => 'datetime', 'password' => 'hashed', + 'email_notifications' => 'boolean', ]; } - public function posts() + public function jobdesk() { - return $this->hasMany(Post::class); + return $this->hasMany(Jobdesk::class); } } diff --git a/app/Notifications/NewOrderNotification.php b/app/Notifications/NewOrderNotification.php new file mode 100644 index 0000000..e2a0d39 --- /dev/null +++ b/app/Notifications/NewOrderNotification.php @@ -0,0 +1,88 @@ +message = Setting::where('setting_key', 'new_order')->value('setting_value') ?? 'Pesan default jika tidak ada setting'; + + // Simpan informasi order + $this->order = $order; + + $this->message = str_replace( + [ + '[nama_klien]', + '[tanggal_order]', + '[no_telp]', + '[address]', + '[order_number]', + '[product]', + '[tim_manajemen]', + ], + [ + $order->customer->name, + $order->order_date, + $order->customer->phone, + $order->customer->address, + $order->order_number, + $order->product->name . ' (' . $order->product->category . ')', + 'Tim Manajemen ' . Setting::where('setting_key', 'app_name')->value('setting_value'), + ], + $this->message + ); + } + + /** + * Tentukan saluran mana yang akan digunakan untuk mengirim notifikasi. + */ + public function via($notifiable) + { + $channels = ['database']; + + // Hanya kirim email jika user mengaktifkan notifikasi email + if ($notifiable->email_notifications ?? true) { + $channels[] = 'mail'; + } + + return $channels; + } + + /** + * Siapkan pesan email untuk notifikasi. + */ + public function toMail($notifiable) + { + return (new MailMessage) + ->subject('Notifikasi Pesanan Baru') + ->view('emails.new_order', [ + 'messageContent' => $this->message, + 'order' => $this->order + ]); + } + + /** + * Siapkan pesan database untuk notifikasi. + */ + public function toDatabase($notifiable) + { + return [ + 'message' => 'Pesanan baru telah diterima dari ' . $this->order->customer->name, + 'notifiable' => $notifiable, + 'order' => $this->order + ]; + } +} diff --git a/app/Notifications/PendingJobdesk.php b/app/Notifications/PendingJobdesk.php new file mode 100644 index 0000000..cff2f36 --- /dev/null +++ b/app/Notifications/PendingJobdesk.php @@ -0,0 +1,80 @@ +message = Setting::where('setting_key', 'pending_jobdesk')->value('setting_value') ?? 'Pesan default jika tidak ada setting'; + + // Simpan data jobdesk + $this->data = $data; + + $this->message = str_replace( + [ + '[nama_klien]', + '[jobdesk_id]', + '[tim_manajemen]', + ], + [ + $data['client_name'], + $data['jobdesk_id'], + 'Tim Manajemen ' . Setting::where('setting_key', 'app_name')->value('setting_value'), + ], + $this->message + ); + } + + /** + * Tentukan saluran mana yang akan digunakan untuk mengirim notifikasi. + */ + public function via($notifiable) + { + $channels = ['database']; + + // Hanya kirim email jika user mengaktifkan notifikasi email + if ($notifiable->email_notifications ?? true) { + $channels[] = 'mail'; + } + + return $channels; + } + + /** + * Siapkan pesan email untuk notifikasi. + */ + public function toMail($notifiable) + { + return (new MailMessage) + ->subject('Pemberitahuan: Jobdesk Belum Diambil') + ->view('emails.pending_jobdesk', [ + 'messageContent' => $this->message, + 'data' => $this->data, + 'notifiable' => $notifiable, + ]); + } + + /** + * Siapkan pesan database untuk notifikasi. + */ + public function toDatabase($notifiable) + { + return [ + 'message' => 'Jobdesk #' . $this->data['jobdesk_id'] . ' belum diambil oleh ' . $notifiable->name, + 'notifiable' => $notifiable, + 'jobdesk' => $this->data + ]; + } +} diff --git a/app/Policies/CustomerPolicy.php b/app/Policies/CustomerPolicy.php new file mode 100644 index 0000000..8db5942 --- /dev/null +++ b/app/Policies/CustomerPolicy.php @@ -0,0 +1,76 @@ +is_admin === 1; + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, Customer $customer): bool + { + // Allow admin users to view any customer + // Allow regular users to view their own customers + return $user->is_admin === 1 || $user->id === $customer->user_id; + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + // Allow admin users to create customers + return $user->is_admin === 1; + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, Customer $customer): bool + { + // Allow admin users to update any customer + // Allow regular users to update their own customers + return $user->is_admin === 1 || $user->id === $customer->user_id; + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, Customer $customer): bool + { + // Allow admin users to delete any customer + // You may also want to allow the customer’s owner to delete their own record + return $user->is_admin === 1 || $user->id === $customer->user_id; + } + + /** + * Determine whether the user can restore the model. + */ + public function restore(User $user, Customer $customer): bool + { + // Typically allow only admins to restore customers + return $user->is_admin === 1; + } + + /** + * Determine whether the user can permanently delete the model. + */ + public function forceDelete(User $user, Customer $customer): bool + { + // Typically allow only admins to permanently delete customers + return $user->is_admin === 1; + } +} diff --git a/app/Policies/DataPolicy.php b/app/Policies/DataPolicy.php new file mode 100644 index 0000000..cc09249 --- /dev/null +++ b/app/Policies/DataPolicy.php @@ -0,0 +1,66 @@ +id === $post->user_id - ? Response::allow() - : Response::deny('You can only modify your own posts.'); - } -} diff --git a/app/Policies/SettingPolicy.php b/app/Policies/SettingPolicy.php new file mode 100644 index 0000000..024a764 --- /dev/null +++ b/app/Policies/SettingPolicy.php @@ -0,0 +1,66 @@ +is_admin === 1; + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, Setting $setting): bool + { + // + } + + /** + * Determine whether the user can restore the model. + */ + public function restore(User $user, Setting $setting): bool + { + // + } + + /** + * Determine whether the user can permanently delete the model. + */ + public function forceDelete(User $user, Setting $setting): bool + { + // + } +} diff --git a/app/Policies/UserPolicy.php b/app/Policies/UserPolicy.php new file mode 100644 index 0000000..f6b96dc --- /dev/null +++ b/app/Policies/UserPolicy.php @@ -0,0 +1,34 @@ +can('user:read'); + } + + public function view(User $user, User $model) + { + return $user->can('user:read'); + } + + public function create(User $user) + { + return $user->can('user:create'); + } + + public function update(User $user, User $model) + { + return $user->can('user:update'); + } + + public function delete(User $user, User $model) + { + return $user->can('user:delete'); + } +} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 452e6b6..b32cc00 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,10 +2,19 @@ namespace App\Providers; -use Illuminate\Support\ServiceProvider; +use App\Models\User; +use App\Policies\UserPolicy; +use Illuminate\Auth\Notifications\ResetPassword; +// use Illuminate\Support\ServiceProvider; +use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; + class AppServiceProvider extends ServiceProvider { + protected $policies = [ + User::class => UserPolicy::class, + ]; + /** * Register any application services. */ @@ -19,6 +28,10 @@ public function register(): void */ public function boot(): void { - // + ResetPassword::createUrlUsing(function (object $notifiable, string $token) { + return config('app.frontend_url') . "/password-reset/$token?email={$notifiable->getEmailForPasswordReset()}"; + }); + + $this->registerPolicies(); } } diff --git a/bootstrap/app.php b/bootstrap/app.php index d654276..641f9df 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -6,13 +6,23 @@ return Application::configure(basePath: dirname(__DIR__)) ->withRouting( - web: __DIR__.'/../routes/web.php', - api: __DIR__.'/../routes/api.php', - commands: __DIR__.'/../routes/console.php', + web: __DIR__ . '/../routes/web.php', + api: __DIR__ . '/../routes/api.php', + commands: __DIR__ . '/../routes/console.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware) { - // + $middleware->api(prepend: [ + \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, + ]); + + $middleware->alias([ + 'verified' => \App\Http\Middleware\EnsureEmailIsVerified::class, + ]); + + // $middleware->validateCsrfTokens(except: [ + // '*', + // ]); }) ->withExceptions(function (Exceptions $exceptions) { // diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..fc15f14 --- /dev/null +++ b/bun.lock @@ -0,0 +1,117 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "larapi", + "dependencies": { + "archiver": "^5.3.1", + "fs": "0.0.1-security", + }, + }, + }, + "packages": { + "archiver": ["archiver@5.3.2", "", { "dependencies": { "archiver-utils": "^2.1.0", "async": "^3.2.4", "buffer-crc32": "^0.2.1", "readable-stream": "^3.6.0", "readdir-glob": "^1.1.2", "tar-stream": "^2.2.0", "zip-stream": "^4.1.0" } }, "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw=="], + + "archiver-utils": ["archiver-utils@2.1.0", "", { "dependencies": { "glob": "^7.1.4", "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", "lodash.defaults": "^4.2.0", "lodash.difference": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^2.0.0" } }, "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw=="], + + "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="], + + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + + "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], + + "compress-commons": ["compress-commons@4.1.2", "", { "dependencies": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" } }, "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], + + "crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="], + + "crc32-stream": ["crc32-stream@4.0.3", "", { "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" } }, "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw=="], + + "end-of-stream": ["end-of-stream@1.4.4", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q=="], + + "fs": ["fs@0.0.1-security", "", {}, "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w=="], + + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], + + "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], + + "lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="], + + "lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="], + + "lodash.difference": ["lodash.difference@4.5.0", "", {}, "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA=="], + + "lodash.flatten": ["lodash.flatten@4.4.0", "", {}, "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g=="], + + "lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="], + + "lodash.union": ["lodash.union@4.6.0", "", {}, "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw=="], + + "minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], + + "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], + + "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "readdir-glob": ["readdir-glob@1.1.3", "", { "dependencies": { "minimatch": "^5.1.0" } }, "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA=="], + + "safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + + "zip-stream": ["zip-stream@4.1.1", "", { "dependencies": { "archiver-utils": "^3.0.4", "compress-commons": "^4.1.2", "readable-stream": "^3.6.0" } }, "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ=="], + + "archiver-utils/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + + "string_decoder/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "zip-stream/archiver-utils": ["archiver-utils@3.0.4", "", { "dependencies": { "glob": "^7.2.3", "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", "lodash.defaults": "^4.2.0", "lodash.difference": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" } }, "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw=="], + + "archiver-utils/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + + "glob/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + + "lazystream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + + "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + } +} diff --git a/composer.json b/composer.json index 2a656a1..d94e408 100644 --- a/composer.json +++ b/composer.json @@ -8,14 +8,17 @@ "php": "^8.2", "laravel/framework": "^11.9", "laravel/sanctum": "^4.0", - "laravel/tinker": "^2.9" + "laravel/tinker": "^2.9", + "spatie/laravel-permission": "^6.18" }, "require-dev": { "fakerphp/faker": "^1.23", + "laravel/breeze": "^2.2", + "laravel/pail": "^1.1", "laravel/pint": "^1.13", "laravel/sail": "^1.26", "mockery/mockery": "^1.6", - "nunomaduro/collision": "^8.0", + "nunomaduro/collision": "^8.1", "phpunit/phpunit": "^11.0.1" }, "autoload": { @@ -45,6 +48,10 @@ "@php artisan key:generate --ansi", "@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"", "@php artisan migrate --graceful --ansi" + ], + "dev": [ + "Composer\\Config::disableProcessTimeout", + "npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite" ] }, "extra": { diff --git a/composer.lock b/composer.lock index 3fe5e0f..4092db9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "eba04bbb24b3180d2a6614993a886c4b", + "content-hash": "cade82523504e7c46a624c9282288cd0", "packages": [ { "name": "brick/math", @@ -380,16 +380,16 @@ }, { "name": "dragonmantank/cron-expression", - "version": "v3.3.3", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a" + "reference": "8c784d071debd117328803d86b2097615b457500" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", - "reference": "adfb1f505deb6384dc8b39804c5065dd3c8c8c0a", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/8c784d071debd117328803d86b2097615b457500", + "reference": "8c784d071debd117328803d86b2097615b457500", "shasum": "" }, "require": { @@ -402,10 +402,14 @@ "require-dev": { "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^1.0", - "phpstan/phpstan-webmozart-assert": "^1.0", "phpunit/phpunit": "^7.0|^8.0|^9.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, "autoload": { "psr-4": { "Cron\\": "src/Cron/" @@ -429,7 +433,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v3.3.3" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.4.0" }, "funding": [ { @@ -437,20 +441,20 @@ "type": "github" } ], - "time": "2023-08-10T19:36:49+00:00" + "time": "2024-10-09T13:47:03+00:00" }, { "name": "egulias/email-validator", - "version": "4.0.2", + "version": "4.0.3", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e" + "reference": "b115554301161fa21467629f1e1391c1936de517" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/ebaaf5be6c0286928352e054f2d5125608e5405e", - "reference": "ebaaf5be6c0286928352e054f2d5125608e5405e", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/b115554301161fa21467629f1e1391c1936de517", + "reference": "b115554301161fa21467629f1e1391c1936de517", "shasum": "" }, "require": { @@ -496,7 +500,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/4.0.2" + "source": "https://github.com/egulias/EmailValidator/tree/4.0.3" }, "funding": [ { @@ -504,7 +508,7 @@ "type": "github" } ], - "time": "2023-10-06T06:47:41+00:00" + "time": "2024-12-27T00:36:43+00:00" }, { "name": "fruitcake/php-cors", @@ -767,16 +771,16 @@ }, { "name": "guzzlehttp/promises", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8" + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", - "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", + "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", "shasum": "" }, "require": { @@ -830,7 +834,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.3" + "source": "https://github.com/guzzle/promises/tree/2.0.4" }, "funding": [ { @@ -846,7 +850,7 @@ "type": "tidelift" } ], - "time": "2024-07-18T10:29:17+00:00" + "time": "2024-10-17T10:06:22+00:00" }, { "name": "guzzlehttp/psr7", @@ -1052,23 +1056,23 @@ }, { "name": "laravel/framework", - "version": "v11.26.0", + "version": "v11.36.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "b8cb8998701d5b3cfe68539d3c3da1fc59ddd82b" + "reference": "df06f5163f4550641fdf349ebc04916a61135a64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/b8cb8998701d5b3cfe68539d3c3da1fc59ddd82b", - "reference": "b8cb8998701d5b3cfe68539d3c3da1fc59ddd82b", + "url": "https://api.github.com/repos/laravel/framework/zipball/df06f5163f4550641fdf349ebc04916a61135a64", + "reference": "df06f5163f4550641fdf349ebc04916a61135a64", "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", + "dragonmantank/cron-expression": "^3.4", "egulias/email-validator": "^3.2.1|^4.0", "ext-ctype": "*", "ext-filter": "*", @@ -1078,35 +1082,37 @@ "ext-session": "*", "ext-tokenizer": "*", "fruitcake/php-cors": "^1.3", - "guzzlehttp/guzzle": "^7.8", + "guzzlehttp/guzzle": "^7.8.2", "guzzlehttp/uri-template": "^1.0", "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0", - "laravel/serializable-closure": "^1.3", - "league/commonmark": "^2.2.1", - "league/flysystem": "^3.8.0", + "laravel/serializable-closure": "^1.3|^2.0", + "league/commonmark": "^2.6", + "league/flysystem": "^3.25.1", + "league/flysystem-local": "^3.25.1", + "league/uri": "^7.5.1", "monolog/monolog": "^3.0", - "nesbot/carbon": "^2.72.2|^3.0", + "nesbot/carbon": "^2.72.2|^3.4", "nunomaduro/termwind": "^2.0", "php": "^8.2", "psr/container": "^1.1.1|^2.0.1", "psr/log": "^1.0|^2.0|^3.0", "psr/simple-cache": "^1.0|^2.0|^3.0", "ramsey/uuid": "^4.7", - "symfony/console": "^7.0", - "symfony/error-handler": "^7.0", - "symfony/finder": "^7.0", - "symfony/http-foundation": "^7.0", - "symfony/http-kernel": "^7.0", - "symfony/mailer": "^7.0", - "symfony/mime": "^7.0", - "symfony/polyfill-php83": "^1.28", - "symfony/process": "^7.0", - "symfony/routing": "^7.0", - "symfony/uid": "^7.0", - "symfony/var-dumper": "^7.0", + "symfony/console": "^7.0.3", + "symfony/error-handler": "^7.0.3", + "symfony/finder": "^7.0.3", + "symfony/http-foundation": "^7.2.0", + "symfony/http-kernel": "^7.0.3", + "symfony/mailer": "^7.0.3", + "symfony/mime": "^7.0.3", + "symfony/polyfill-php83": "^1.31", + "symfony/process": "^7.0.3", + "symfony/routing": "^7.0.3", + "symfony/uid": "^7.0.3", + "symfony/var-dumper": "^7.0.3", "tijsverkoyen/css-to-inline-styles": "^2.2.5", - "vlucas/phpdotenv": "^5.4.1", - "voku/portable-ascii": "^2.0" + "vlucas/phpdotenv": "^5.6.1", + "voku/portable-ascii": "^2.0.2" }, "conflict": { "mockery/mockery": "1.6.8", @@ -1156,29 +1162,32 @@ }, "require-dev": { "ably/ably-php": "^1.0", - "aws/aws-sdk-php": "^3.235.5", + "aws/aws-sdk-php": "^3.322.9", "ext-gmp": "*", - "fakerphp/faker": "^1.23", - "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.6", - "nyholm/psr7": "^1.2", - "orchestra/testbench-core": "^9.5", - "pda/pheanstalk": "^5.0", + "fakerphp/faker": "^1.24", + "guzzlehttp/promises": "^2.0.3", + "guzzlehttp/psr7": "^2.4", + "league/flysystem-aws-s3-v3": "^3.25.1", + "league/flysystem-ftp": "^3.25.1", + "league/flysystem-path-prefixing": "^3.25.1", + "league/flysystem-read-only": "^3.25.1", + "league/flysystem-sftp-v3": "^3.25.1", + "mockery/mockery": "^1.6.10", + "orchestra/testbench-core": "^9.6", + "pda/pheanstalk": "^5.0.6", + "php-http/discovery": "^1.15", "phpstan/phpstan": "^1.11.5", - "phpunit/phpunit": "^10.5|^11.0", - "predis/predis": "^2.0.2", + "phpunit/phpunit": "^10.5.35|^11.3.6", + "predis/predis": "^2.3", "resend/resend-php": "^0.10.0", - "symfony/cache": "^7.0", - "symfony/http-client": "^7.0", - "symfony/psr-http-message-bridge": "^7.0" + "symfony/cache": "^7.0.3", + "symfony/http-client": "^7.0.3", + "symfony/psr-http-message-bridge": "^7.0.3", + "symfony/translation": "^7.0.3" }, "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).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.322.9).", "brianium/paratest": "Required to run tests in parallel (^7.0|^8.0).", "ext-apcu": "Required to use the APC cache driver.", "ext-fileinfo": "Required to use the Filesystem class.", @@ -1192,16 +1201,16 @@ "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", "filp/whoops": "Required for friendly error pages in development (^2.14.3).", "laravel/tinker": "Required to use the tinker console command (^2.0).", - "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.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).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", + "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).", + "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.25.1).", + "league/flysystem-read-only": "Required to use read-only disks (^3.25.1)", + "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).", "mockery/mockery": "Required to use mocking (^1.6).", - "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", "pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).", + "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", "phpunit/phpunit": "Required to use assertions and run tests (^10.5|^11.0).", - "predis/predis": "Required to use the predis connector (^2.0.2).", + "predis/predis": "Required to use the predis connector (^2.3).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).", @@ -1220,6 +1229,7 @@ }, "autoload": { "files": [ + "src/Illuminate/Collections/functions.php", "src/Illuminate/Collections/helpers.php", "src/Illuminate/Events/functions.php", "src/Illuminate/Filesystem/functions.php", @@ -1257,20 +1267,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-10-01T14:29:34+00:00" + "time": "2024-12-17T22:32:08+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.0", + "version": "v0.3.2", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "ea57a2261093986721d4a5f4f9524d76f21f9fa0" + "reference": "0e0535747c6b8d6d10adca8b68293cf4517abb0f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/ea57a2261093986721d4a5f4f9524d76f21f9fa0", - "reference": "ea57a2261093986721d4a5f4f9524d76f21f9fa0", + "url": "https://api.github.com/repos/laravel/prompts/zipball/0e0535747c6b8d6d10adca8b68293cf4517abb0f", + "reference": "0e0535747c6b8d6d10adca8b68293cf4517abb0f", "shasum": "" }, "require": { @@ -1286,7 +1296,7 @@ "require-dev": { "illuminate/collections": "^10.0|^11.0", "mockery/mockery": "^1.5", - "pestphp/pest": "^2.3", + "pestphp/pest": "^2.3|^3.4", "phpstan/phpstan": "^1.11", "phpstan/phpstan-mockery": "^1.1" }, @@ -1314,22 +1324,22 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.0" + "source": "https://github.com/laravel/prompts/tree/v0.3.2" }, - "time": "2024-09-30T14:27:51+00:00" + "time": "2024-11-12T14:59:47+00:00" }, { "name": "laravel/sanctum", - "version": "v4.0.3", + "version": "v4.0.7", "source": { "type": "git", "url": "https://github.com/laravel/sanctum.git", - "reference": "54aea9d13743ae8a6cdd3c28dbef128a17adecab" + "reference": "698064236a46df016e64a7eb059b1414e0b281df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sanctum/zipball/54aea9d13743ae8a6cdd3c28dbef128a17adecab", - "reference": "54aea9d13743ae8a6cdd3c28dbef128a17adecab", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/698064236a46df016e64a7eb059b1414e0b281df", + "reference": "698064236a46df016e64a7eb059b1414e0b281df", "shasum": "" }, "require": { @@ -1380,36 +1390,36 @@ "issues": "https://github.com/laravel/sanctum/issues", "source": "https://github.com/laravel/sanctum" }, - "time": "2024-09-27T14:55:41+00:00" + "time": "2024-12-11T16:40:21+00:00" }, { "name": "laravel/serializable-closure", - "version": "v1.3.5", + "version": "v2.0.1", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c" + "reference": "613b2d4998f85564d40497e05e89cb6d9bd1cbe8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", - "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/613b2d4998f85564d40497e05e89cb6d9bd1cbe8", + "reference": "613b2d4998f85564d40497e05e89cb6d9bd1cbe8", "shasum": "" }, "require": { - "php": "^7.3|^8.0" + "php": "^8.1" }, "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" + "illuminate/support": "^10.0|^11.0", + "nesbot/carbon": "^2.67|^3.0", + "pestphp/pest": "^2.36", + "phpstan/phpstan": "^2.0", + "symfony/var-dumper": "^6.2.0|^7.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { @@ -1441,7 +1451,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2024-09-23T13:33:08+00:00" + "time": "2024-12-16T15:26:28+00:00" }, { "name": "laravel/tinker", @@ -1511,16 +1521,16 @@ }, { "name": "league/commonmark", - "version": "2.5.3", + "version": "2.6.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "b650144166dfa7703e62a22e493b853b58d874b0" + "reference": "d990688c91cedfb69753ffc2512727ec646df2ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/b650144166dfa7703e62a22e493b853b58d874b0", - "reference": "b650144166dfa7703e62a22e493b853b58d874b0", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d990688c91cedfb69753ffc2512727ec646df2ad", + "reference": "d990688c91cedfb69753ffc2512727ec646df2ad", "shasum": "" }, "require": { @@ -1545,8 +1555,9 @@ "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/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 || ^7.0", + "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" }, @@ -1556,7 +1567,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.6-dev" + "dev-main": "2.7-dev" } }, "autoload": { @@ -1613,7 +1624,7 @@ "type": "tidelift" } ], - "time": "2024-08-16T11:46:16+00:00" + "time": "2024-12-29T14:10:59+00:00" }, { "name": "league/config", @@ -1699,16 +1710,16 @@ }, { "name": "league/flysystem", - "version": "3.29.0", + "version": "3.29.1", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "0adc0d9a51852e170e0028a60bd271726626d3f0" + "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/0adc0d9a51852e170e0028a60bd271726626d3f0", - "reference": "0adc0d9a51852e170e0028a60bd271726626d3f0", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/edc1bb7c86fab0776c3287dbd19b5fa278347319", + "reference": "edc1bb7c86fab0776c3287dbd19b5fa278347319", "shasum": "" }, "require": { @@ -1776,9 +1787,9 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.29.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.29.1" }, - "time": "2024-09-29T11:59:11+00:00" + "time": "2024-10-08T08:58:34+00:00" }, { "name": "league/flysystem-local", @@ -1885,18 +1896,192 @@ ], "time": "2024-09-21T08:32:55+00:00" }, + { + "name": "league/uri", + "version": "7.5.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri.git", + "reference": "81fb5145d2644324614cc532b28efd0215bda430" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/81fb5145d2644324614cc532b28efd0215bda430", + "reference": "81fb5145d2644324614cc532b28efd0215bda430", + "shasum": "" + }, + "require": { + "league/uri-interfaces": "^7.5", + "php": "^8.1" + }, + "conflict": { + "league/uri-schemes": "^1.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-fileinfo": "to create Data URI from file contennts", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain", + "league/uri-components": "Needed to easily manipulate URI objects components", + "php-64bit": "to improve IPV4 host parsing", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "URI manipulation library", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "middleware", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "uri-template", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri/tree/7.5.1" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2024-12-08T08:40:02+00:00" + }, + { + "name": "league/uri-interfaces", + "version": "7.5.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/uri-interfaces.git", + "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^8.1", + "psr/http-factory": "^1", + "psr/http-message": "^1.1 || ^2.0" + }, + "suggest": { + "ext-bcmath": "to improve IPV4 host parsing", + "ext-gmp": "to improve IPV4 host parsing", + "ext-intl": "to handle IDN host with the best performance", + "php-64bit": "to improve IPV4 host parsing", + "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Uri\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ignace Nyamagana Butera", + "email": "nyamsprod@gmail.com", + "homepage": "https://nyamsprod.com" + } + ], + "description": "Common interfaces and classes for URI representation and interaction", + "homepage": "https://uri.thephpleague.com", + "keywords": [ + "data-uri", + "file-uri", + "ftp", + "hostname", + "http", + "https", + "parse_str", + "parse_url", + "psr-7", + "query-string", + "querystring", + "rfc3986", + "rfc3987", + "rfc6570", + "uri", + "url", + "ws" + ], + "support": { + "docs": "https://uri.thephpleague.com", + "forum": "https://thephpleague.slack.com", + "issues": "https://github.com/thephpleague/uri-src/issues", + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.5.0" + }, + "funding": [ + { + "url": "https://github.com/sponsors/nyamsprod", + "type": "github" + } + ], + "time": "2024-12-08T08:18:47+00:00" + }, { "name": "monolog/monolog", - "version": "3.7.0", + "version": "3.8.1", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8" + "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f4393b648b78a5408747de94fca38beb5f7e9ef8", - "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/aef6ee73a77a66e404dd6540934a9ef1b3c855b4", + "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4", "shasum": "" }, "require": { @@ -1916,12 +2101,14 @@ "guzzlehttp/psr7": "^2.2", "mongodb/mongodb": "^1.8", "php-amqplib/php-amqplib": "~2.4 || ^3", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-strict-rules": "^1.4", - "phpunit/phpunit": "^10.5.17", + "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", - "ruflin/elastica": "^7", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", "symfony/mailer": "^5.4 || ^6", "symfony/mime": "^5.4 || ^6" }, @@ -1972,7 +2159,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.7.0" + "source": "https://github.com/Seldaek/monolog/tree/3.8.1" }, "funding": [ { @@ -1984,24 +2171,24 @@ "type": "tidelift" } ], - "time": "2024-06-28T09:40:51+00:00" + "time": "2024-12-05T17:15:07+00:00" }, { "name": "nesbot/carbon", - "version": "3.8.0", + "version": "3.8.4", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "bbd3eef89af8ba66a3aa7952b5439168fbcc529f" + "reference": "129700ed449b1f02d70272d2ac802357c8c30c58" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/bbd3eef89af8ba66a3aa7952b5439168fbcc529f", - "reference": "bbd3eef89af8ba66a3aa7952b5439168fbcc529f", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/129700ed449b1f02d70272d2ac802357c8c30c58", + "reference": "129700ed449b1f02d70272d2ac802357c8c30c58", "shasum": "" }, "require": { - "carbonphp/carbon-doctrine-types": "*", + "carbonphp/carbon-doctrine-types": "<100.0", "ext-json": "*", "php": "^8.1", "psr/clock": "^1.0", @@ -2029,10 +2216,6 @@ ], "type": "library", "extra": { - "branch-alias": { - "dev-master": "3.x-dev", - "dev-2.x": "2.x-dev" - }, "laravel": { "providers": [ "Carbon\\Laravel\\ServiceProvider" @@ -2042,6 +2225,10 @@ "includes": [ "extension.neon" ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" } }, "autoload": { @@ -2090,28 +2277,28 @@ "type": "tidelift" } ], - "time": "2024-08-19T06:22:39+00:00" + "time": "2024-12-27T09:25:35+00:00" }, { "name": "nette/schema", - "version": "v1.3.0", + "version": "v1.3.2", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188" + "reference": "da801d52f0354f70a638673c4a0f04e16529431d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", - "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", + "url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d", + "reference": "da801d52f0354f70a638673c4a0f04e16529431d", "shasum": "" }, "require": { "nette/utils": "^4.0", - "php": "8.1 - 8.3" + "php": "8.1 - 8.4" }, "require-dev": { - "nette/tester": "^2.4", + "nette/tester": "^2.5.2", "phpstan/phpstan-nette": "^1.0", "tracy/tracy": "^2.8" }, @@ -2150,9 +2337,9 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.0" + "source": "https://github.com/nette/schema/tree/v1.3.2" }, - "time": "2023-12-11T11:54:22+00:00" + "time": "2024-10-06T23:10:23+00:00" }, { "name": "nette/utils", @@ -2242,16 +2429,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.3.0", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a" + "reference": "447a020a1f875a434d62f2a401f53b82a396e494" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3abf7425cd284141dc5d8d14a9ee444de3345d1a", - "reference": "3abf7425cd284141dc5d8d14a9ee444de3345d1a", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", + "reference": "447a020a1f875a434d62f2a401f53b82a396e494", "shasum": "" }, "require": { @@ -2294,38 +2481,37 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" }, - "time": "2024-09-29T13:56:26+00:00" + "time": "2024-12-30T11:07:19+00:00" }, { "name": "nunomaduro/termwind", - "version": "v2.1.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "e5f21eade88689536c0cdad4c3cd75f3ed26e01a" + "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/e5f21eade88689536c0cdad4c3cd75f3ed26e01a", - "reference": "e5f21eade88689536c0cdad4c3cd75f3ed26e01a", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/52915afe6a1044e8b9cee1bcff836fb63acf9cda", + "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.0.4" + "symfony/console": "^7.1.8" }, "require-dev": { - "ergebnis/phpstan-rules": "^2.2.0", - "illuminate/console": "^11.1.1", - "laravel/pint": "^1.15.0", - "mockery/mockery": "^1.6.11", - "pestphp/pest": "^2.34.6", - "phpstan/phpstan": "^1.10.66", - "phpstan/phpstan-strict-rules": "^1.5.2", - "symfony/var-dumper": "^7.0.4", + "illuminate/console": "^11.33.2", + "laravel/pint": "^1.18.2", + "mockery/mockery": "^1.6.12", + "pestphp/pest": "^2.36.0", + "phpstan/phpstan": "^1.12.11", + "phpstan/phpstan-strict-rules": "^1.6.1", + "symfony/var-dumper": "^7.1.8", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -2368,7 +2554,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.1.0" + "source": "https://github.com/nunomaduro/termwind/tree/v2.3.0" }, "funding": [ { @@ -2384,7 +2570,7 @@ "type": "github" } ], - "time": "2024-09-05T15:25:50+00:00" + "time": "2024-11-21T10:39:51+00:00" }, { "name": "phpoption/phpoption", @@ -2875,16 +3061,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.4", + "version": "v0.12.7", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "2fd717afa05341b4f8152547f142cd2f130f6818" + "reference": "d73fa3c74918ef4522bb8a3bf9cab39161c4b57c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/2fd717afa05341b4f8152547f142cd2f130f6818", - "reference": "2fd717afa05341b4f8152547f142cd2f130f6818", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/d73fa3c74918ef4522bb8a3bf9cab39161c4b57c", + "reference": "d73fa3c74918ef4522bb8a3bf9cab39161c4b57c", "shasum": "" }, "require": { @@ -2911,12 +3097,12 @@ ], "type": "library", "extra": { - "branch-alias": { - "dev-main": "0.12.x-dev" - }, "bamarni-bin": { "bin-links": false, "forward-command": false + }, + "branch-alias": { + "dev-main": "0.12.x-dev" } }, "autoload": { @@ -2948,9 +3134,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.4" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.7" }, - "time": "2024-06-10T01:18:23+00:00" + "time": "2024-12-10T01:58:33+00:00" }, { "name": "ralouphie/getallheaders", @@ -3177,18 +3363,101 @@ ], "time": "2024-04-27T21:32:50+00:00" }, + { + "name": "spatie/laravel-permission", + "version": "6.18.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-permission.git", + "reference": "3c05f04d12275dfbe462c8b4aae3290e586c2dde" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/3c05f04d12275dfbe462c8b4aae3290e586c2dde", + "reference": "3c05f04d12275dfbe462c8b4aae3290e586c2dde", + "shasum": "" + }, + "require": { + "illuminate/auth": "^8.12|^9.0|^10.0|^11.0|^12.0", + "illuminate/container": "^8.12|^9.0|^10.0|^11.0|^12.0", + "illuminate/contracts": "^8.12|^9.0|^10.0|^11.0|^12.0", + "illuminate/database": "^8.12|^9.0|^10.0|^11.0|^12.0", + "php": "^8.0" + }, + "require-dev": { + "laravel/passport": "^11.0|^12.0", + "laravel/pint": "^1.0", + "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0|^10.0", + "phpunit/phpunit": "^9.4|^10.1|^11.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Permission\\PermissionServiceProvider" + ] + }, + "branch-alias": { + "dev-main": "6.x-dev", + "dev-master": "6.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Permission\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Permission handling for Laravel 8.0 and up", + "homepage": "https://github.com/spatie/laravel-permission", + "keywords": [ + "acl", + "laravel", + "permission", + "permissions", + "rbac", + "roles", + "security", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-permission/issues", + "source": "https://github.com/spatie/laravel-permission/tree/6.18.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-05-14T03:32:23+00:00" + }, { "name": "symfony/clock", - "version": "v7.1.1", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", - "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7" + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/3dfc8b084853586de51dd1441c6242c76a28cbe7", - "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7", + "url": "https://api.github.com/repos/symfony/clock/zipball/b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", "shasum": "" }, "require": { @@ -3233,7 +3502,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v7.1.1" + "source": "https://github.com/symfony/clock/tree/v7.2.0" }, "funding": [ { @@ -3249,20 +3518,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/console", - "version": "v7.1.5", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee" + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0fa539d12b3ccf068a722bbbffa07ca7079af9ee", - "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee", + "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", "shasum": "" }, "require": { @@ -3326,7 +3595,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.5" + "source": "https://github.com/symfony/console/tree/v7.2.1" }, "funding": [ { @@ -3342,20 +3611,20 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:28:38+00:00" + "time": "2024-12-11T03:49:26+00:00" }, { "name": "symfony/css-selector", - "version": "v7.1.1", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4" + "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/1c7cee86c6f812896af54434f8ce29c8d94f9ff4", - "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2", + "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2", "shasum": "" }, "require": { @@ -3391,7 +3660,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.1.1" + "source": "https://github.com/symfony/css-selector/tree/v7.2.0" }, "funding": [ { @@ -3407,20 +3676,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", "shasum": "" }, "require": { @@ -3428,12 +3697,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -3458,7 +3727,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" }, "funding": [ { @@ -3474,20 +3743,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/error-handler", - "version": "v7.1.3", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "432bb369952795c61ca1def65e078c4a80dad13c" + "reference": "6150b89186573046167796fa5f3f76601d5145f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/432bb369952795c61ca1def65e078c4a80dad13c", - "reference": "432bb369952795c61ca1def65e078c4a80dad13c", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/6150b89186573046167796fa5f3f76601d5145f8", + "reference": "6150b89186573046167796fa5f3f76601d5145f8", "shasum": "" }, "require": { @@ -3533,7 +3802,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.1.3" + "source": "https://github.com/symfony/error-handler/tree/v7.2.1" }, "funding": [ { @@ -3549,20 +3818,20 @@ "type": "tidelift" } ], - "time": "2024-07-26T13:02:51+00:00" + "time": "2024-12-07T08:50:44+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.1.1", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7" + "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", - "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/910c5db85a5356d0fea57680defec4e99eb9c8c1", + "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1", "shasum": "" }, "require": { @@ -3613,7 +3882,7 @@ "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/v7.1.1" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.2.0" }, "funding": [ { @@ -3629,20 +3898,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" + "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", - "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", + "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", "shasum": "" }, "require": { @@ -3651,12 +3920,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -3689,7 +3958,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1" }, "funding": [ { @@ -3705,20 +3974,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/finder", - "version": "v7.1.4", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "d95bbf319f7d052082fb7af147e0f835a695e823" + "reference": "87a71856f2f56e4100373e92529eed3171695cfb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/d95bbf319f7d052082fb7af147e0f835a695e823", - "reference": "d95bbf319f7d052082fb7af147e0f835a695e823", + "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb", + "reference": "87a71856f2f56e4100373e92529eed3171695cfb", "shasum": "" }, "require": { @@ -3753,7 +4022,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.1.4" + "source": "https://github.com/symfony/finder/tree/v7.2.2" }, "funding": [ { @@ -3769,35 +4038,36 @@ "type": "tidelift" } ], - "time": "2024-08-13T14:28:19+00:00" + "time": "2024-12-30T19:00:17+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.1.5", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "e30ef73b1e44eea7eb37ba69600a354e553f694b" + "reference": "62d1a43796ca3fea3f83a8470dfe63a4af3bc588" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e30ef73b1e44eea7eb37ba69600a354e553f694b", - "reference": "e30ef73b1e44eea7eb37ba69600a354e553f694b", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/62d1a43796ca3fea3f83a8470dfe63a4af3bc588", + "reference": "62d1a43796ca3fea3f83a8470dfe63a4af3bc588", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-mbstring": "~1.1", "symfony/polyfill-php83": "^1.27" }, "conflict": { "doctrine/dbal": "<3.6", - "symfony/cache": "<6.4" + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" }, "require-dev": { "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", - "symfony/cache": "^6.4|^7.0", + "symfony/cache": "^6.4.12|^7.1.5", "symfony/dependency-injection": "^6.4|^7.0", "symfony/expression-language": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", @@ -3830,7 +4100,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.1.5" + "source": "https://github.com/symfony/http-foundation/tree/v7.2.2" }, "funding": [ { @@ -3846,20 +4116,20 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:28:38+00:00" + "time": "2024-12-30T19:00:17+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.1.5", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "44204d96150a9df1fc57601ec933d23fefc2d65b" + "reference": "3c432966bd8c7ec7429663105f5a02d7e75b4306" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/44204d96150a9df1fc57601ec933d23fefc2d65b", - "reference": "44204d96150a9df1fc57601ec933d23fefc2d65b", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/3c432966bd8c7ec7429663105f5a02d7e75b4306", + "reference": "3c432966bd8c7ec7429663105f5a02d7e75b4306", "shasum": "" }, "require": { @@ -3888,7 +4158,7 @@ "symfony/twig-bridge": "<6.4", "symfony/validator": "<6.4", "symfony/var-dumper": "<6.4", - "twig/twig": "<3.0.4" + "twig/twig": "<3.12" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" @@ -3916,7 +4186,7 @@ "symfony/validator": "^6.4|^7.0", "symfony/var-dumper": "^6.4|^7.0", "symfony/var-exporter": "^6.4|^7.0", - "twig/twig": "^3.0.4" + "twig/twig": "^3.12" }, "type": "library", "autoload": { @@ -3944,7 +4214,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.1.5" + "source": "https://github.com/symfony/http-kernel/tree/v7.2.2" }, "funding": [ { @@ -3960,20 +4230,20 @@ "type": "tidelift" } ], - "time": "2024-09-21T06:09:21+00:00" + "time": "2024-12-31T14:59:40+00:00" }, { "name": "symfony/mailer", - "version": "v7.1.5", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "bbf21460c56f29810da3df3e206e38dfbb01e80b" + "reference": "e4d358702fb66e4c8a2af08e90e7271a62de39cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/bbf21460c56f29810da3df3e206e38dfbb01e80b", - "reference": "bbf21460c56f29810da3df3e206e38dfbb01e80b", + "url": "https://api.github.com/repos/symfony/mailer/zipball/e4d358702fb66e4c8a2af08e90e7271a62de39cc", + "reference": "e4d358702fb66e4c8a2af08e90e7271a62de39cc", "shasum": "" }, "require": { @@ -3982,7 +4252,7 @@ "psr/event-dispatcher": "^1", "psr/log": "^1|^2|^3", "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/mime": "^6.4|^7.0", + "symfony/mime": "^7.2", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -4024,7 +4294,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.1.5" + "source": "https://github.com/symfony/mailer/tree/v7.2.0" }, "funding": [ { @@ -4040,20 +4310,20 @@ "type": "tidelift" } ], - "time": "2024-09-08T12:32:26+00:00" + "time": "2024-11-25T15:21:05+00:00" }, { "name": "symfony/mime", - "version": "v7.1.5", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "711d2e167e8ce65b05aea6b258c449671cdd38ff" + "reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/711d2e167e8ce65b05aea6b258c449671cdd38ff", - "reference": "711d2e167e8ce65b05aea6b258c449671cdd38ff", + "url": "https://api.github.com/repos/symfony/mime/zipball/7f9617fcf15cb61be30f8b252695ed5e2bfac283", + "reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283", "shasum": "" }, "require": { @@ -4108,7 +4378,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.1.5" + "source": "https://github.com/symfony/mime/tree/v7.2.1" }, "funding": [ { @@ -4124,7 +4394,7 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:28:38+00:00" + "time": "2024-12-07T08:50:44+00:00" }, { "name": "symfony/polyfill-ctype", @@ -4152,8 +4422,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -4228,8 +4498,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -4307,8 +4577,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -4389,8 +4659,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -4473,8 +4743,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -4547,8 +4817,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -4627,8 +4897,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -4709,8 +4979,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -4764,16 +5034,16 @@ }, { "name": "symfony/process", - "version": "v7.1.5", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "5c03ee6369281177f07f7c68252a280beccba847" + "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/5c03ee6369281177f07f7c68252a280beccba847", - "reference": "5c03ee6369281177f07f7c68252a280beccba847", + "url": "https://api.github.com/repos/symfony/process/zipball/d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", + "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", "shasum": "" }, "require": { @@ -4805,7 +5075,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.1.5" + "source": "https://github.com/symfony/process/tree/v7.2.0" }, "funding": [ { @@ -4821,20 +5091,20 @@ "type": "tidelift" } ], - "time": "2024-09-19T21:48:23+00:00" + "time": "2024-11-06T14:24:19+00:00" }, { "name": "symfony/routing", - "version": "v7.1.4", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "1500aee0094a3ce1c92626ed8cf3c2037e86f5a7" + "reference": "e10a2450fa957af6c448b9b93c9010a4e4c0725e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/1500aee0094a3ce1c92626ed8cf3c2037e86f5a7", - "reference": "1500aee0094a3ce1c92626ed8cf3c2037e86f5a7", + "url": "https://api.github.com/repos/symfony/routing/zipball/e10a2450fa957af6c448b9b93c9010a4e4c0725e", + "reference": "e10a2450fa957af6c448b9b93c9010a4e4c0725e", "shasum": "" }, "require": { @@ -4886,7 +5156,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.1.4" + "source": "https://github.com/symfony/routing/tree/v7.2.0" }, "funding": [ { @@ -4902,20 +5172,20 @@ "type": "tidelift" } ], - "time": "2024-08-29T08:16:25+00:00" + "time": "2024-11-25T11:08:51+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", "shasum": "" }, "require": { @@ -4928,12 +5198,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -4969,7 +5239,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" }, "funding": [ { @@ -4985,20 +5255,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/string", - "version": "v7.1.5", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306" + "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/d66f9c343fa894ec2037cc928381df90a7ad4306", - "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306", + "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82", + "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82", "shasum": "" }, "require": { @@ -5056,7 +5326,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.5" + "source": "https://github.com/symfony/string/tree/v7.2.0" }, "funding": [ { @@ -5072,24 +5342,25 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:28:38+00:00" + "time": "2024-11-13T13:31:26+00:00" }, { "name": "symfony/translation", - "version": "v7.1.5", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "235535e3f84f3dfbdbde0208ede6ca75c3a489ea" + "reference": "e2674a30132b7cc4d74540d6c2573aa363f05923" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/235535e3f84f3dfbdbde0208ede6ca75c3a489ea", - "reference": "235535e3f84f3dfbdbde0208ede6ca75c3a489ea", + "url": "https://api.github.com/repos/symfony/translation/zipball/e2674a30132b7cc4d74540d6c2573aa363f05923", + "reference": "e2674a30132b7cc4d74540d6c2573aa363f05923", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/translation-contracts": "^2.5|^3.0" }, @@ -5150,7 +5421,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.1.5" + "source": "https://github.com/symfony/translation/tree/v7.2.2" }, "funding": [ { @@ -5166,20 +5437,20 @@ "type": "tidelift" } ], - "time": "2024-09-16T06:30:38+00:00" + "time": "2024-12-07T08:18:10+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a" + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", - "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", "shasum": "" }, "require": { @@ -5187,12 +5458,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -5228,7 +5499,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1" }, "funding": [ { @@ -5244,20 +5515,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/uid", - "version": "v7.1.5", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "8c7bb8acb933964055215d89f9a9871df0239317" + "reference": "2d294d0c48df244c71c105a169d0190bfb080426" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/8c7bb8acb933964055215d89f9a9871df0239317", - "reference": "8c7bb8acb933964055215d89f9a9871df0239317", + "url": "https://api.github.com/repos/symfony/uid/zipball/2d294d0c48df244c71c105a169d0190bfb080426", + "reference": "2d294d0c48df244c71c105a169d0190bfb080426", "shasum": "" }, "require": { @@ -5302,7 +5573,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.1.5" + "source": "https://github.com/symfony/uid/tree/v7.2.0" }, "funding": [ { @@ -5318,20 +5589,20 @@ "type": "tidelift" } ], - "time": "2024-09-17T09:16:35+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.1.5", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "e20e03889539fd4e4211e14d2179226c513c010d" + "reference": "c6a22929407dec8765d6e2b6ff85b800b245879c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e20e03889539fd4e4211e14d2179226c513c010d", - "reference": "e20e03889539fd4e4211e14d2179226c513c010d", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c6a22929407dec8765d6e2b6ff85b800b245879c", + "reference": "c6a22929407dec8765d6e2b6ff85b800b245879c", "shasum": "" }, "require": { @@ -5347,7 +5618,7 @@ "symfony/http-kernel": "^6.4|^7.0", "symfony/process": "^6.4|^7.0", "symfony/uid": "^6.4|^7.0", - "twig/twig": "^3.0.4" + "twig/twig": "^3.12" }, "bin": [ "Resources/bin/var-dump-server" @@ -5385,7 +5656,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.1.5" + "source": "https://github.com/symfony/var-dumper/tree/v7.2.0" }, "funding": [ { @@ -5401,35 +5672,37 @@ "type": "tidelift" } ], - "time": "2024-09-16T10:07:02+00:00" + "time": "2024-11-08T15:48:14+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", - "version": "v2.2.7", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/tijsverkoyen/CssToInlineStyles.git", - "reference": "83ee6f38df0a63106a9e4536e3060458b74ccedb" + "reference": "0d72ac1c00084279c1816675284073c5a337c20d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/83ee6f38df0a63106a9e4536e3060458b74ccedb", - "reference": "83ee6f38df0a63106a9e4536e3060458b74ccedb", + "url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/0d72ac1c00084279c1816675284073c5a337c20d", + "reference": "0d72ac1c00084279c1816675284073c5a337c20d", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", - "php": "^5.5 || ^7.0 || ^8.0", - "symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0" + "php": "^7.4 || ^8.0", + "symfony/css-selector": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^7.5 || ^8.5.21 || ^9.5.10" + "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.2.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { @@ -5452,9 +5725,9 @@ "homepage": "https://github.com/tijsverkoyen/CssToInlineStyles", "support": { "issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues", - "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.2.7" + "source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/v2.3.0" }, - "time": "2023-12-08T13:03:43+00:00" + "time": "2024-12-21T16:25:41+00:00" }, { "name": "vlucas/phpdotenv", @@ -5542,16 +5815,16 @@ }, { "name": "voku/portable-ascii", - "version": "2.0.1", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/voku/portable-ascii.git", - "reference": "b56450eed252f6801410d810c8e1727224ae0743" + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b56450eed252f6801410d810c8e1727224ae0743", - "reference": "b56450eed252f6801410d810c8e1727224ae0743", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", "shasum": "" }, "require": { @@ -5576,7 +5849,7 @@ "authors": [ { "name": "Lars Moelleken", - "homepage": "http://www.moelleken.org/" + "homepage": "https://www.moelleken.org/" } ], "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", @@ -5588,7 +5861,7 @@ ], "support": { "issues": "https://github.com/voku/portable-ascii/issues", - "source": "https://github.com/voku/portable-ascii/tree/2.0.1" + "source": "https://github.com/voku/portable-ascii/tree/2.0.3" }, "funding": [ { @@ -5612,7 +5885,7 @@ "type": "tidelift" } ], - "time": "2022-03-08T17:03:00+00:00" + "time": "2024-11-21T01:49:47+00:00" }, { "name": "webmozart/assert", @@ -5676,16 +5949,16 @@ "packages-dev": [ { "name": "fakerphp/faker", - "version": "v1.23.1", + "version": "v1.24.1", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b" + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/bfb4fe148adbf78eff521199619b93a52ae3554b", - "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", "shasum": "" }, "require": { @@ -5733,9 +6006,9 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.23.1" + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" }, - "time": "2024-01-02T13:46:09+00:00" + "time": "2024-11-21T13:46:39+00:00" }, { "name": "filp/whoops", @@ -5859,18 +6132,157 @@ }, "time": "2020-07-09T08:09:16+00:00" }, + { + "name": "laravel/breeze", + "version": "v2.3.0", + "source": { + "type": "git", + "url": "https://github.com/laravel/breeze.git", + "reference": "d59702967b9ae21879df905d691a50132966c4ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/breeze/zipball/d59702967b9ae21879df905d691a50132966c4ff", + "reference": "d59702967b9ae21879df905d691a50132966c4ff", + "shasum": "" + }, + "require": { + "illuminate/console": "^11.0", + "illuminate/filesystem": "^11.0", + "illuminate/support": "^11.0", + "illuminate/validation": "^11.0", + "php": "^8.2.0", + "symfony/console": "^7.0" + }, + "require-dev": { + "laravel/framework": "^11.0", + "orchestra/testbench-core": "^9.0", + "phpstan/phpstan": "^2.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Breeze\\BreezeServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Breeze\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Minimal Laravel authentication scaffolding with Blade and Tailwind.", + "keywords": [ + "auth", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/breeze/issues", + "source": "https://github.com/laravel/breeze" + }, + "time": "2024-12-14T21:21:42+00:00" + }, + { + "name": "laravel/pail", + "version": "v1.2.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/pail.git", + "reference": "353ac12134b98e2e7c3333d916bd3e523931e583" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/pail/zipball/353ac12134b98e2e7c3333d916bd3e523931e583", + "reference": "353ac12134b98e2e7c3333d916bd3e523931e583", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "illuminate/console": "^10.24|^11.0", + "illuminate/contracts": "^10.24|^11.0", + "illuminate/log": "^10.24|^11.0", + "illuminate/process": "^10.24|^11.0", + "illuminate/support": "^10.24|^11.0", + "nunomaduro/termwind": "^1.15|^2.0", + "php": "^8.2", + "symfony/console": "^6.0|^7.0" + }, + "require-dev": { + "laravel/framework": "^10.24|^11.0", + "laravel/pint": "^1.13", + "orchestra/testbench-core": "^8.12|^9.0", + "pestphp/pest": "^2.20", + "pestphp/pest-plugin-type-coverage": "^2.3", + "phpstan/phpstan": "^1.10", + "symfony/var-dumper": "^6.3|^7.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Pail\\PailServiceProvider" + ] + }, + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Pail\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "Easily delve into your Laravel application's log files directly from the command line.", + "homepage": "https://github.com/laravel/pail", + "keywords": [ + "laravel", + "logs", + "php", + "tail" + ], + "support": { + "issues": "https://github.com/laravel/pail/issues", + "source": "https://github.com/laravel/pail" + }, + "time": "2024-10-23T12:56:23+00:00" + }, { "name": "laravel/pint", - "version": "v1.18.1", + "version": "v1.19.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9" + "reference": "8169513746e1bac70c85d6ea1524d9225d4886f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/35c00c05ec43e6b46d295efc0f4386ceb30d50d9", - "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9", + "url": "https://api.github.com/repos/laravel/pint/zipball/8169513746e1bac70c85d6ea1524d9225d4886f0", + "reference": "8169513746e1bac70c85d6ea1524d9225d4886f0", "shasum": "" }, "require": { @@ -5881,13 +6293,13 @@ "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.64.0", - "illuminate/view": "^10.48.20", - "larastan/larastan": "^2.9.8", - "laravel-zero/framework": "^10.4.0", + "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.15.1", - "pestphp/pest": "^2.35.1" + "nunomaduro/termwind": "^1.17.0", + "pestphp/pest": "^2.36.0" }, "bin": [ "builds/pint" @@ -5923,20 +6335,20 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2024-09-24T17:22:50+00:00" + "time": "2024-12-30T16:20:10+00:00" }, { "name": "laravel/sail", - "version": "v1.34.0", + "version": "v1.39.1", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "511e9c95b0f3ee778dc9e11e242bcd2af8e002cd" + "reference": "1a3c7291bc88de983b66688919a4d298d68ddec7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/511e9c95b0f3ee778dc9e11e242bcd2af8e002cd", - "reference": "511e9c95b0f3ee778dc9e11e242bcd2af8e002cd", + "url": "https://api.github.com/repos/laravel/sail/zipball/1a3c7291bc88de983b66688919a4d298d68ddec7", + "reference": "1a3c7291bc88de983b66688919a4d298d68ddec7", "shasum": "" }, "require": { @@ -5986,7 +6398,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2024-09-27T14:58:09+00:00" + "time": "2024-11-27T15:42:28+00:00" }, { "name": "mockery/mockery", @@ -6073,16 +6485,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.12.0", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", "shasum": "" }, "require": { @@ -6121,7 +6533,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" }, "funding": [ { @@ -6129,27 +6541,27 @@ "type": "tidelift" } ], - "time": "2024-06-12T14:39:25+00:00" + "time": "2024-11-08T17:47:46+00:00" }, { "name": "nunomaduro/collision", - "version": "v8.4.0", + "version": "v8.5.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "e7d1aa8ed753f63fa816932bbc89678238843b4a" + "reference": "f5c101b929c958e849a633283adff296ed5f38f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/e7d1aa8ed753f63fa816932bbc89678238843b4a", - "reference": "e7d1aa8ed753f63fa816932bbc89678238843b4a", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/f5c101b929c958e849a633283adff296ed5f38f5", + "reference": "f5c101b929c958e849a633283adff296ed5f38f5", "shasum": "" }, "require": { - "filp/whoops": "^2.15.4", - "nunomaduro/termwind": "^2.0.1", + "filp/whoops": "^2.16.0", + "nunomaduro/termwind": "^2.1.0", "php": "^8.2.0", - "symfony/console": "^7.1.3" + "symfony/console": "^7.1.5" }, "conflict": { "laravel/framework": "<11.0.0 || >=12.0.0", @@ -6157,14 +6569,14 @@ }, "require-dev": { "larastan/larastan": "^2.9.8", - "laravel/framework": "^11.19.0", - "laravel/pint": "^1.17.1", - "laravel/sail": "^1.31.0", - "laravel/sanctum": "^4.0.2", - "laravel/tinker": "^2.9.0", - "orchestra/testbench-core": "^9.2.3", - "pestphp/pest": "^2.35.0 || ^3.0.0", - "sebastian/environment": "^6.1.0 || ^7.0.0" + "laravel/framework": "^11.28.0", + "laravel/pint": "^1.18.1", + "laravel/sail": "^1.36.0", + "laravel/sanctum": "^4.0.3", + "laravel/tinker": "^2.10.0", + "orchestra/testbench-core": "^9.5.3", + "pestphp/pest": "^2.36.0 || ^3.4.0", + "sebastian/environment": "^6.1.0 || ^7.2.0" }, "type": "library", "extra": { @@ -6226,7 +6638,7 @@ "type": "patreon" } ], - "time": "2024-08-03T15:32:23+00:00" + "time": "2024-10-15T16:06:32+00:00" }, { "name": "phar-io/manifest", @@ -6348,35 +6760,35 @@ }, { "name": "phpunit/php-code-coverage", - "version": "11.0.6", + "version": "11.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "ebdffc9e09585dafa71b9bffcdb0a229d4704c45" + "reference": "418c59fd080954f8c4aa5631d9502ecda2387118" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ebdffc9e09585dafa71b9bffcdb0a229d4704c45", - "reference": "ebdffc9e09585dafa71b9bffcdb0a229d4704c45", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/418c59fd080954f8c4aa5631d9502ecda2387118", + "reference": "418c59fd080954f8c4aa5631d9502ecda2387118", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^5.1.0", + "nikic/php-parser": "^5.3.1", "php": ">=8.2", - "phpunit/php-file-iterator": "^5.0.1", + "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-text-template": "^4.0.1", "sebastian/code-unit-reverse-lookup": "^4.0.1", "sebastian/complexity": "^4.0.1", "sebastian/environment": "^7.2.0", "sebastian/lines-of-code": "^3.0.1", - "sebastian/version": "^5.0.1", + "sebastian/version": "^5.0.2", "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.5.0" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -6414,7 +6826,7 @@ "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/11.0.6" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.8" }, "funding": [ { @@ -6422,7 +6834,7 @@ "type": "github" } ], - "time": "2024-08-22T04:37:56+00:00" + "time": "2024-12-11T12:34:27+00:00" }, { "name": "phpunit/php-file-iterator", @@ -6671,16 +7083,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.3.6", + "version": "11.5.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "d62c45a19c665bb872c2a47023a0baf41a98bb2b" + "reference": "153d0531b9f7e883c5053160cad6dd5ac28140b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d62c45a19c665bb872c2a47023a0baf41a98bb2b", - "reference": "d62c45a19c665bb872c2a47023a0baf41a98bb2b", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/153d0531b9f7e883c5053160cad6dd5ac28140b3", + "reference": "153d0531b9f7e883c5053160cad6dd5ac28140b3", "shasum": "" }, "require": { @@ -6690,25 +7102,26 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.0", + "myclabs/deep-copy": "^1.12.1", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.6", + "phpunit/php-code-coverage": "^11.0.8", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", "sebastian/cli-parser": "^3.0.2", - "sebastian/code-unit": "^3.0.1", - "sebastian/comparator": "^6.1.0", + "sebastian/code-unit": "^3.0.2", + "sebastian/comparator": "^6.2.1", "sebastian/diff": "^6.0.2", "sebastian/environment": "^7.2.0", - "sebastian/exporter": "^6.1.3", + "sebastian/exporter": "^6.3.0", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", "sebastian/type": "^5.1.0", - "sebastian/version": "^5.0.1" + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" }, "suggest": { "ext-soap": "To be able to generate mocks based on WSDL files" @@ -6719,7 +7132,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "11.3-dev" + "dev-main": "11.5-dev" } }, "autoload": { @@ -6751,7 +7164,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.3.6" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.2" }, "funding": [ { @@ -6767,7 +7180,7 @@ "type": "tidelift" } ], - "time": "2024-09-19T10:54:28+00:00" + "time": "2024-12-21T05:51:08+00:00" }, { "name": "sebastian/cli-parser", @@ -6828,23 +7241,23 @@ }, { "name": "sebastian/code-unit", - "version": "3.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "6bb7d09d6623567178cf54126afa9c2310114268" + "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/6bb7d09d6623567178cf54126afa9c2310114268", - "reference": "6bb7d09d6623567178cf54126afa9c2310114268", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca", + "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.5" }, "type": "library", "extra": { @@ -6873,7 +7286,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", "security": "https://github.com/sebastianbergmann/code-unit/security/policy", - "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.2" }, "funding": [ { @@ -6881,7 +7294,7 @@ "type": "github" } ], - "time": "2024-07-03T04:44:28+00:00" + "time": "2024-12-12T09:59:06+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -6941,16 +7354,16 @@ }, { "name": "sebastian/comparator", - "version": "6.1.0", + "version": "6.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa37b9e2ca618cb051d71b60120952ee8ca8b03d" + "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa37b9e2ca618cb051d71b60120952ee8ca8b03d", - "reference": "fa37b9e2ca618cb051d71b60120952ee8ca8b03d", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/43d129d6a0f81c78bee378b46688293eb7ea3739", + "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739", "shasum": "" }, "require": { @@ -6961,12 +7374,12 @@ "sebastian/exporter": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^11.3" + "phpunit/phpunit": "^11.4" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.1-dev" + "dev-main": "6.2-dev" } }, "autoload": { @@ -7006,7 +7419,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.1.0" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.2.1" }, "funding": [ { @@ -7014,7 +7427,7 @@ "type": "github" } ], - "time": "2024-09-11T15:42:56+00:00" + "time": "2024-10-31T05:30:08+00:00" }, { "name": "sebastian/complexity", @@ -7207,16 +7620,16 @@ }, { "name": "sebastian/exporter", - "version": "6.1.3", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e" + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e", - "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/3473f61172093b2da7de1fb5782e1f24cc036dc3", + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3", "shasum": "" }, "require": { @@ -7225,7 +7638,7 @@ "sebastian/recursion-context": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^11.2" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { @@ -7273,7 +7686,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/6.1.3" + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.0" }, "funding": [ { @@ -7281,7 +7694,7 @@ "type": "github" } ], - "time": "2024-07-03T04:56:19+00:00" + "time": "2024-12-05T09:17:50+00:00" }, { "name": "sebastian/global-state", @@ -7640,16 +8053,16 @@ }, { "name": "sebastian/version", - "version": "5.0.1", + "version": "5.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "45c9debb7d039ce9b97de2f749c2cf5832a06ac4" + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/45c9debb7d039ce9b97de2f749c2cf5832a06ac4", - "reference": "45c9debb7d039ce9b97de2f749c2cf5832a06ac4", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", "shasum": "" }, "require": { @@ -7682,7 +8095,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/version/issues", "security": "https://github.com/sebastianbergmann/version/security/policy", - "source": "https://github.com/sebastianbergmann/version/tree/5.0.1" + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" }, "funding": [ { @@ -7690,24 +8103,77 @@ "type": "github" } ], - "time": "2024-07-03T05:13:08+00:00" + "time": "2024-10-09T05:16:32+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" }, { "name": "symfony/yaml", - "version": "v7.1.5", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "4e561c316e135e053bd758bf3b3eb291d9919de4" + "reference": "099581e99f557e9f16b43c5916c26380b54abb22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/4e561c316e135e053bd758bf3b3eb291d9919de4", - "reference": "4e561c316e135e053bd758bf3b3eb291d9919de4", + "url": "https://api.github.com/repos/symfony/yaml/zipball/099581e99f557e9f16b43c5916c26380b54abb22", + "reference": "099581e99f557e9f16b43c5916c26380b54abb22", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { @@ -7745,7 +8211,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.1.5" + "source": "https://github.com/symfony/yaml/tree/v7.2.0" }, "funding": [ { @@ -7761,7 +8227,7 @@ "type": "tidelift" } ], - "time": "2024-09-17T12:49:58+00:00" + "time": "2024-10-23T06:56:12+00:00" }, { "name": "theseer/tokenizer", diff --git a/config/cors.php b/config/cors.php new file mode 100644 index 0000000..5182f04 --- /dev/null +++ b/config/cors.php @@ -0,0 +1,34 @@ + ['api/*', 'sanctum/csrf-cookie', '*'], + + 'allowed_methods' => ['*'], + + 'allowed_origins' => ['*'], + + 'allowed_origins_patterns' => [], + + 'allowed_headers' => ['*'], + + 'exposed_headers' => [], + + 'max_age' => 0, + + 'supports_credentials' => true, + +]; diff --git a/config/permission.php b/config/permission.php new file mode 100644 index 0000000..f39f6b5 --- /dev/null +++ b/config/permission.php @@ -0,0 +1,202 @@ + [ + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * Eloquent model should be used to retrieve your permissions. Of course, it + * is often just the "Permission" model but you may use whatever you like. + * + * The model you want to use as a Permission model needs to implement the + * `Spatie\Permission\Contracts\Permission` contract. + */ + + 'permission' => Spatie\Permission\Models\Permission::class, + + /* + * When using the "HasRoles" trait from this package, we need to know which + * Eloquent model should be used to retrieve your roles. Of course, it + * is often just the "Role" model but you may use whatever you like. + * + * The model you want to use as a Role model needs to implement the + * `Spatie\Permission\Contracts\Role` contract. + */ + + 'role' => Spatie\Permission\Models\Role::class, + + ], + + 'table_names' => [ + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'roles' => 'roles', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your permissions. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'permissions' => 'permissions', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your models permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_permissions' => 'model_has_permissions', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your models roles. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_roles' => 'model_has_roles', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'role_has_permissions' => 'role_has_permissions', + ], + + 'column_names' => [ + /* + * Change this if you want to name the related pivots other than defaults + */ + 'role_pivot_key' => null, // default 'role_id', + 'permission_pivot_key' => null, // default 'permission_id', + + /* + * Change this if you want to name the related model primary key other than + * `model_id`. + * + * For example, this would be nice if your primary keys are all UUIDs. In + * that case, name this `model_uuid`. + */ + + 'model_morph_key' => 'model_id', + + /* + * Change this if you want to use the teams feature and your related model's + * foreign key is other than `team_id`. + */ + + 'team_foreign_key' => 'team_id', + ], + + /* + * When set to true, the method for checking permissions will be registered on the gate. + * Set this to false if you want to implement custom logic for checking permissions. + */ + + 'register_permission_check_method' => true, + + /* + * When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered + * this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated + * NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it. + */ + 'register_octane_reset_listener' => false, + + /* + * Events will fire when a role or permission is assigned/unassigned: + * \Spatie\Permission\Events\RoleAttached + * \Spatie\Permission\Events\RoleDetached + * \Spatie\Permission\Events\PermissionAttached + * \Spatie\Permission\Events\PermissionDetached + * + * To enable, set to true, and then create listeners to watch these events. + */ + 'events_enabled' => false, + + /* + * Teams Feature. + * When set to true the package implements teams using the 'team_foreign_key'. + * If you want the migrations to register the 'team_foreign_key', you must + * set this to true before doing the migration. + * If you already did the migration then you must make a new migration to also + * add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions' + * (view the latest version of this package's migration file) + */ + + 'teams' => false, + + /* + * The class to use to resolve the permissions team id + */ + 'team_resolver' => \Spatie\Permission\DefaultTeamResolver::class, + + /* + * Passport Client Credentials Grant + * When set to true the package will use Passports Client to check permissions + */ + + 'use_passport_client_credentials' => false, + + /* + * When set to true, the required permission names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. + */ + + 'display_permission_in_exception' => false, + + /* + * When set to true, the required role names are added to exception messages. + * This could be considered an information leak in some contexts, so the default + * setting is false here for optimum safety. + */ + + 'display_role_in_exception' => false, + + /* + * By default wildcard permission lookups are disabled. + * See documentation to understand supported syntax. + */ + + 'enable_wildcard_permission' => false, + + /* + * The class to use for interpreting wildcard permissions. + * If you need to modify delimiters, override the class and specify its name here. + */ + // 'wildcard_permission' => Spatie\Permission\WildcardPermission::class, + + /* Cache-specific settings */ + + 'cache' => [ + + /* + * By default all permissions are cached for 24 hours to speed up performance. + * When permissions or roles are updated the cache is flushed automatically. + */ + + 'expiration_time' => \DateInterval::createFromDateString('24 hours'), + + /* + * The cache key used to store all permissions. + */ + + 'key' => 'spatie.permission.cache', + + /* + * You may optionally indicate a specific cache driver to use for permission and + * role caching using any of the `store` drivers listed in the cache.php config + * file. Using 'default' here means to use the `default` set in cache.php. + */ + + 'store' => 'default', + ], +]; diff --git a/config/sanctum.php b/config/sanctum.php index 764a82f..b03f249 100644 --- a/config/sanctum.php +++ b/config/sanctum.php @@ -16,9 +16,10 @@ */ 'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( - '%s%s', - 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', - Sanctum::currentApplicationUrlWithPort() + '%s%s%s', + 'localhost,localhost:3000,127.0.0.1,127.0.0.1:3000,127.0.0.1:8000,::1', + Sanctum::currentApplicationUrlWithPort(), + env('FRONTEND_URL') ? ','.parse_url(env('FRONTEND_URL'), PHP_URL_HOST) : '' ))), /* diff --git a/database/factories/PostFactory.php b/database/factories/CustomerFactory.php similarity index 65% rename from database/factories/PostFactory.php rename to database/factories/CustomerFactory.php index 55f1633..0b09f6e 100644 --- a/database/factories/PostFactory.php +++ b/database/factories/CustomerFactory.php @@ -5,9 +5,9 @@ use Illuminate\Database\Eloquent\Factories\Factory; /** - * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Post> + * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Customer> */ -class PostFactory extends Factory +class CustomerFactory extends Factory { /** * Define the model's default state. @@ -17,7 +17,9 @@ class PostFactory extends Factory public function definition(): array { return [ - // + 'name' => fake()->name(), + 'phone' => fake()->phoneNumber(), + 'address' => fake()->address(), ]; } } diff --git a/database/factories/JobdeskFactory.php b/database/factories/JobdeskFactory.php new file mode 100644 index 0000000..9e9fc79 --- /dev/null +++ b/database/factories/JobdeskFactory.php @@ -0,0 +1,32 @@ + + */ +class JobdeskFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + $status = random_int(1, 100) <= 80 ? 'Selesai' : fake()->randomElement(['Masuk', 'Progress']); + $staffs = User::role('staff')->get(); + return [ + 'order_id' => fake()->numberBetween(1, 10), + 'description' => fake()->sentence(), + 'user_id' => $staffs->random()->id, + 'tanggal_pengerjaan' => $status !== 'Masuk' ? now()->subDays(fake()->numberBetween(5, 10)) : null, + 'tanggal_selesai' => $status === 'Selesai' ? now() : null, + 'status' => $status + ]; + } +} diff --git a/database/factories/MetaFactory.php b/database/factories/MetaFactory.php new file mode 100644 index 0000000..5c7f916 --- /dev/null +++ b/database/factories/MetaFactory.php @@ -0,0 +1,26 @@ + + */ +class MetaFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'name' => $this->faker->name(), + 'type' => $this->faker->word(), + 'created_at' => now(), + 'updated_at' => now(), + ]; + } +} diff --git a/database/factories/MetaProductFactory.php b/database/factories/MetaProductFactory.php new file mode 100644 index 0000000..3288536 --- /dev/null +++ b/database/factories/MetaProductFactory.php @@ -0,0 +1,33 @@ + Meta::factory(), + 'product_id' => Product::factory(), + 'created_at' => now(), + 'updated_at' => now(), + ]; + } +} diff --git a/database/factories/OrderFactory.php b/database/factories/OrderFactory.php new file mode 100644 index 0000000..4af7daf --- /dev/null +++ b/database/factories/OrderFactory.php @@ -0,0 +1,37 @@ + + */ +class OrderFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + $price = fake()->numberBetween(1000000, 10000000); + $paid = fake()->numberBetween(1000000, $price); + $order_date = fake()->dateTimeBetween('-6 months', 'now'); + $customers = Customer::inRandomOrder()->first(); + $products = Product::inRandomOrder()->first(); + return [ + 'customer_id' => $customers->id, + 'order_date' => $order_date, + 'product_id' => $products->id, + 'price' => $price, + 'payment_method' => fake()->randomElement(['Tunai', 'Transfer']), + 'paid' => $paid, + 'meta' => null, + ]; + } +} diff --git a/database/factories/ProductFactory.php b/database/factories/ProductFactory.php new file mode 100644 index 0000000..22d35ad --- /dev/null +++ b/database/factories/ProductFactory.php @@ -0,0 +1,22 @@ + $this->faker->word, + 'category' => $this->faker->randomElement(['bank', 'perorangan']), + 'description' => $this->faker->paragraph, + 'created_at' => now(), + 'updated_at' => now(), + ]; + } +} diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 584104c..b3741b6 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -28,6 +28,10 @@ public function definition(): array 'email' => fake()->unique()->safeEmail(), 'email_verified_at' => now(), 'password' => static::$password ??= Hash::make('password'), + 'is_admin' => false, + 'avatar' => null, + 'phone' => fake()->phoneNumber(), + 'address' => fake()->address(), 'remember_token' => Str::random(10), ]; } @@ -37,8 +41,8 @@ public function definition(): array */ public function unverified(): static { - return $this->state(fn (array $attributes) => [ - 'email_verified_at' => null, + return $this->state(fn(array $attributes) => [ + 'email_verified_at' => now(), ]); } } diff --git a/database/migrations/0001_01_01_000000_create_users_table.php b/database/migrations/0001_01_01_000000_create_users_table.php index 05fb5d9..cd186e1 100644 --- a/database/migrations/0001_01_01_000000_create_users_table.php +++ b/database/migrations/0001_01_01_000000_create_users_table.php @@ -16,24 +16,29 @@ public function up(): void $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); + $table->boolean('is_admin')->default(false); + $table->string('avatar')->nullable(); + $table->string('phone')->nullable(); + $table->text('address')->nullable(); + $table->string('position')->nullable(); $table->string('password'); $table->rememberToken(); $table->timestamps(); }); Schema::create('password_reset_tokens', function (Blueprint $table) { - $table->string('email')->primary(); - $table->string('token'); + $table->string('email', 191)->primary(); + $table->text('token'); $table->timestamp('created_at')->nullable(); }); Schema::create('sessions', function (Blueprint $table) { - $table->string('id')->primary(); - $table->foreignId('user_id')->nullable()->index(); + $table->string('id', 191)->primary(); + $table->unsignedBigInteger('user_id')->nullable(); $table->string('ip_address', 45)->nullable(); $table->text('user_agent')->nullable(); $table->longText('payload'); - $table->integer('last_activity')->index(); + $table->integer('last_activity'); }); } diff --git a/database/migrations/0001_01_01_000001_create_cache_table.php b/database/migrations/0001_01_01_000001_create_cache_table.php index b9c106b..d6ce316 100644 --- a/database/migrations/0001_01_01_000001_create_cache_table.php +++ b/database/migrations/0001_01_01_000001_create_cache_table.php @@ -12,13 +12,13 @@ public function up(): void { Schema::create('cache', function (Blueprint $table) { - $table->string('key')->primary(); - $table->mediumText('value'); - $table->integer('expiration'); + $table->string('key', 191)->primary(); // Use VARCHAR(191) + $table->mediumText('value'); // Keep as mediumText + $table->integer('expiration'); // Keep as is }); Schema::create('cache_locks', function (Blueprint $table) { - $table->string('key')->primary(); + $table->string('key',191)->primary(); $table->string('owner'); $table->integer('expiration'); }); diff --git a/database/migrations/0001_01_01_000002_create_jobs_table.php b/database/migrations/0001_01_01_000002_create_jobs_table.php index 425e705..94efe45 100644 --- a/database/migrations/0001_01_01_000002_create_jobs_table.php +++ b/database/migrations/0001_01_01_000002_create_jobs_table.php @@ -22,8 +22,8 @@ public function up(): void }); Schema::create('job_batches', function (Blueprint $table) { - $table->string('id')->primary(); - $table->string('name'); + $table->string('id', 191)->primary(); + $table->string('name', 255); $table->integer('total_jobs'); $table->integer('pending_jobs'); $table->integer('failed_jobs'); diff --git a/database/migrations/2024_10_03_022302_create_personal_access_tokens_table.php b/database/migrations/2024_10_31_042843_create_personal_access_tokens_table.php similarity index 90% rename from database/migrations/2024_10_03_022302_create_personal_access_tokens_table.php rename to database/migrations/2024_10_31_042843_create_personal_access_tokens_table.php index e828ad8..0ffea38 100644 --- a/database/migrations/2024_10_03_022302_create_personal_access_tokens_table.php +++ b/database/migrations/2024_10_31_042843_create_personal_access_tokens_table.php @@ -13,7 +13,7 @@ public function up(): void { Schema::create('personal_access_tokens', function (Blueprint $table) { $table->id(); - $table->morphs('tokenable'); + $table->string('tokenable_type', 191); // Set length for tokenable_type $table->string('name'); $table->string('token', 64)->unique(); $table->text('abilities')->nullable(); diff --git a/database/migrations/2024_11_01_021016_create_customers_table.php b/database/migrations/2024_11_01_021016_create_customers_table.php new file mode 100644 index 0000000..9f13fb3 --- /dev/null +++ b/database/migrations/2024_11_01_021016_create_customers_table.php @@ -0,0 +1,30 @@ +id(); + $table->string('name', 100); + $table->string('phone', 20)->unique(); + $table->string('address', 255)->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('customers'); + } +}; diff --git a/database/migrations/2024_11_06_033508_create_orders_table.php b/database/migrations/2024_11_06_033508_create_orders_table.php new file mode 100644 index 0000000..76d08ef --- /dev/null +++ b/database/migrations/2024_11_06_033508_create_orders_table.php @@ -0,0 +1,37 @@ +id(); + $table->string('no_order')->unique(); + $table->bigInteger('customer_id')->unsigned(); + $table->foreign('customer_id')->references('id')->on('customers')->onDelete('cascade'); + $table->date('order_date'); + $table->bigInteger('product_id')->unsigned(); + $table->integer('price')->nullable(); + $table->string('payment_method')->nullable(); + $table->integer('paid')->nullable(); + $table->text('meta')->nullable(); + $table->text('lampiran')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('orders'); + } +}; diff --git a/database/migrations/2024_10_03_031706_create_posts_table.php b/database/migrations/2024_12_14_102650_create_settings_table.php similarity index 63% rename from database/migrations/2024_10_03_031706_create_posts_table.php rename to database/migrations/2024_12_14_102650_create_settings_table.php index e6af613..cf8fb6c 100644 --- a/database/migrations/2024_10_03_031706_create_posts_table.php +++ b/database/migrations/2024_12_14_102650_create_settings_table.php @@ -11,11 +11,10 @@ */ public function up(): void { - Schema::create('posts', function (Blueprint $table) { + Schema::create('settings', function (Blueprint $table) { $table->id(); - $table->foreignId('user_id')->constrained()->cascadeOnDelete(); - $table->string('title'); - $table->text('body'); + $table->string('setting_key')->unique(); + $table->longText('setting_value')->nullable(); $table->timestamps(); }); } @@ -25,6 +24,6 @@ public function up(): void */ public function down(): void { - Schema::dropIfExists('posts'); + Schema::dropIfExists('settings'); } }; diff --git a/database/migrations/2024_12_17_145546_create_notifications_table.php b/database/migrations/2024_12_17_145546_create_notifications_table.php new file mode 100644 index 0000000..9bbe08a --- /dev/null +++ b/database/migrations/2024_12_17_145546_create_notifications_table.php @@ -0,0 +1,35 @@ +uuid('id')->primary(); + $table->string('type'); + $table->string('notifiable_type', 191); // Ubah panjang kolom + $table->uuid('notifiable_id'); // Pastikan ini sesuai dengan tipe ID yang Anda gunakan + $table->text('data'); + $table->timestamp('read_at')->nullable(); + $table->timestamps(); + + // Tambahkan indeks jika diperlukan + $table->index(['notifiable_type', 'notifiable_id'], 'notifications_notifiable_type_notifiable_id_index'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('notifications'); + } +}; diff --git a/database/migrations/2025_01_08_224038_create_products_table.php b/database/migrations/2025_01_08_224038_create_products_table.php new file mode 100644 index 0000000..c0db34f --- /dev/null +++ b/database/migrations/2025_01_08_224038_create_products_table.php @@ -0,0 +1,30 @@ +id(); + $table->string('name'); + $table->string('category')->nullable(); + $table->text('description')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('products'); + } +}; diff --git a/database/migrations/2025_01_08_224039_create_jobdesks_table.php b/database/migrations/2025_01_08_224039_create_jobdesks_table.php new file mode 100644 index 0000000..bb14992 --- /dev/null +++ b/database/migrations/2025_01_08_224039_create_jobdesks_table.php @@ -0,0 +1,37 @@ +id(); + $table->unsignedBigInteger('order_id')->nullable(); + $table->foreign('order_id')->references('id')->on('orders')->onDelete('cascade'); + + $table->text('description')->nullable(); + $table->unsignedBigInteger('user_id')->nullable(); // Assuming it references the users table + $table->foreign('user_id')->references('id')->on('users')->onDelete('set null'); // Add foreign key constraint + + $table->date('tanggal_pengerjaan')->nullable(); + $table->date('tanggal_selesai')->nullable(); + $table->string('status')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('jobdesks'); + } +}; diff --git a/database/migrations/2025_01_08_231951_create_metas_table.php b/database/migrations/2025_01_08_231951_create_metas_table.php new file mode 100644 index 0000000..c08893a --- /dev/null +++ b/database/migrations/2025_01_08_231951_create_metas_table.php @@ -0,0 +1,29 @@ +id(); + $table->string('name'); + $table->string('type'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('metas'); + } +}; diff --git a/database/migrations/2025_01_09_074553_create_meta_product_table.php b/database/migrations/2025_01_09_074553_create_meta_product_table.php new file mode 100644 index 0000000..7839b95 --- /dev/null +++ b/database/migrations/2025_01_09_074553_create_meta_product_table.php @@ -0,0 +1,29 @@ +id(); + $table->foreignId('meta_id')->constrained()->onDelete('cascade'); + $table->foreignId('product_id')->constrained()->onDelete('cascade'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('meta_product'); + } +}; diff --git a/database/migrations/2025_02_17_193026_create_customer_meta_table.php b/database/migrations/2025_02_17_193026_create_customer_meta_table.php new file mode 100644 index 0000000..27179c0 --- /dev/null +++ b/database/migrations/2025_02_17_193026_create_customer_meta_table.php @@ -0,0 +1,33 @@ +id(); + $table->unsignedBigInteger('customer_id'); + $table->string('meta_key'); + $table->text('meta_value')->nullable(); + $table->timestamps(); + + // Menambahkan foreign key constraint + $table->foreign('customer_id')->references('id')->on('customers')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('customer_meta'); + } +}; diff --git a/database/migrations/2025_02_28_092405_create_categories_table.php b/database/migrations/2025_02_28_092405_create_categories_table.php new file mode 100644 index 0000000..f92547d --- /dev/null +++ b/database/migrations/2025_02_28_092405_create_categories_table.php @@ -0,0 +1,22 @@ +id(); + $table->string('name')->unique(); + $table->string('slug')->unique(); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('categories'); + } +}; diff --git a/database/migrations/2025_02_28_092410_create_posts_table.php b/database/migrations/2025_02_28_092410_create_posts_table.php new file mode 100644 index 0000000..4cc1642 --- /dev/null +++ b/database/migrations/2025_02_28_092410_create_posts_table.php @@ -0,0 +1,26 @@ +id(); + $table->string('title'); + $table->text('content'); + $table->string('slug')->unique(); + $table->foreignId('user_id')->constrained()->onDelete('cascade'); + $table->string('featured_image')->nullable(); + $table->foreignId('category_id')->constrained('categories')->onDelete('cascade'); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('posts'); + } +}; diff --git a/database/migrations/2025_05_23_112118_create_permission_tables.php b/database/migrations/2025_05_23_112118_create_permission_tables.php new file mode 100644 index 0000000..58d90a8 --- /dev/null +++ b/database/migrations/2025_05_23_112118_create_permission_tables.php @@ -0,0 +1,143 @@ +engine('InnoDB'); + $table->bigIncrements('id'); // permission id + $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); + $table->timestamps(); + + $table->unique(['name', 'guard_name']); + }); + + Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) { + // $table->engine('InnoDB'); + $table->bigIncrements('id'); // role id + if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing + $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); + $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); + } + $table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format) + $table->string('guard_name'); // For MyISAM use string('guard_name', 25); + $table->timestamps(); + if ($teams || config('permission.testing')) { + $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); + } else { + $table->unique(['name', 'guard_name']); + } + }); + + Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) { + $table->unsignedBigInteger($pivotPermission); + + $table->string('model_type', 191); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); + + $table->primary( + [$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary' + ); + } else { + $table->primary( + [$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary' + ); + } + }); + + Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) { + $table->unsignedBigInteger($pivotRole); + + $table->string('model_type', 191); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); + + $table->primary( + [$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary' + ); + } else { + $table->primary( + [$pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary' + ); + } + }); + + Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) { + $table->unsignedBigInteger($pivotPermission); + $table->unsignedBigInteger($pivotRole); + + $table->foreign($pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + + $table->foreign($pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + + $table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary'); + }); + + app('cache') + ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null) + ->forget(config('permission.cache.key')); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + $tableNames = config('permission.table_names'); + + if (empty($tableNames)) { + throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'); + } + + Schema::drop($tableNames['role_has_permissions']); + Schema::drop($tableNames['model_has_roles']); + Schema::drop($tableNames['model_has_permissions']); + Schema::drop($tableNames['roles']); + Schema::drop($tableNames['permissions']); + } +}; diff --git a/database/migrations/2025_05_25_110241_remove_position_to_role_in_users_table.php b/database/migrations/2025_05_25_110241_remove_position_to_role_in_users_table.php new file mode 100644 index 0000000..95527ee --- /dev/null +++ b/database/migrations/2025_05_25_110241_remove_position_to_role_in_users_table.php @@ -0,0 +1,22 @@ +removeColumn('position'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void {} +}; diff --git a/database/migrations/2025_08_07_204447_add_email_notifications_to_users_table.php b/database/migrations/2025_08_07_204447_add_email_notifications_to_users_table.php new file mode 100644 index 0000000..3f002be --- /dev/null +++ b/database/migrations/2025_08_07_204447_add_email_notifications_to_users_table.php @@ -0,0 +1,28 @@ +boolean('email_notifications')->default(true)->after('email_verified_at'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('email_notifications'); + }); + } +}; diff --git a/database/seeders/AdminAccessSeeder.php b/database/seeders/AdminAccessSeeder.php new file mode 100644 index 0000000..c95ba18 --- /dev/null +++ b/database/seeders/AdminAccessSeeder.php @@ -0,0 +1,41 @@ +first(); + + if (!$admin) { + $admin = User::create([ + 'name' => 'Super Admin', + 'email' => $email, + 'is_admin' => true, + 'avatar' => null, + 'phone' => '081111111111', + 'address' => 'Admin Default Access', + 'password' => Hash::make($password), + ]); + + $admin->assignRole('admin'); + echo "Admin user created: $email / $password\n"; + } else { + // Reset password jika user sudah ada + $admin->password = Hash::make($password); + $admin->save(); + echo "Admin user password reset: $email / $password\n"; + } + } +} diff --git a/database/seeders/CategorySeeder.php b/database/seeders/CategorySeeder.php new file mode 100644 index 0000000..dbb5692 --- /dev/null +++ b/database/seeders/CategorySeeder.php @@ -0,0 +1,36 @@ + $categoryName, + 'slug' => Str::slug($categoryName) + ]); + } + } +} diff --git a/database/seeders/CustomerSeeder.php b/database/seeders/CustomerSeeder.php new file mode 100644 index 0000000..41591d0 --- /dev/null +++ b/database/seeders/CustomerSeeder.php @@ -0,0 +1,1017 @@ + 'Nyonya TRI WHYDHARTI', + 'phone' => '082264079363', + 'address' => 'Baciro Sanggrahan Gondokusuman 4/105 Yogyakarta, Rukun Tetangga 042, Rukun Warga 011, Kelurahan Baciro, Kecamatan Gondokusuman, Kota Yogyakarta', + 'created_at' => '2025-11-24T08:04:08.000000Z', + 'updated_at' => '2025-11-24T08:04:08.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR NUSAMBA KK PIYUNGAN'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00555', + 'order_date' => '2025-11-24', + 'product_id' => 2, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '2' => '86/2025', + '3' => '2025-11-23T17:00:00.000Z', + '5' => '75000000', + '7' => '2028-11-23T17:00:00.000Z', + '8' => 'HM 12345/Baciro', + '9' => '3404010107890', + '10' => '34.71.100.004.018-0045.0', + '11' => 'Nyonya TRI WHYDHARTI', + '14' => 93750000, + '33' => 'HM 11678/Baciro' + ], + 'created_at' => '2025-11-24T08:30:00.000000Z', + 'updated_at' => '2025-11-24T08:30:00.000000Z' + ] + ] + ], + [ + 'name' => 'Nyonya MUJIYEM', + 'phone' => '081804275558', + 'address' => 'Burikan, RT 005, RW 005, Desa Sumberadi, Kecamatan Mlati, Kabupaten Sleman', + 'created_at' => '2025-11-24T07:06:24.000000Z', + 'updated_at' => '2025-11-24T07:06:24.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR BBA'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00554', + 'order_date' => '2025-11-24', + 'product_id' => 3, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '2' => '85', + '3' => '2025-11-23T17:00:00.000Z', + '5' => '50000000', + '9' => '13040605.09816', + '10' => '34.04.060.002.020-0152.0', + '11' => 'Nyonya MUJIYEM', + '14' => 62500000, + '33' => 'HM 10569/SUMBERADI' + ], + 'created_at' => '2025-11-24T07:07:39.000000Z', + 'updated_at' => '2025-11-24T07:07:39.000000Z' + ], + [ + 'no_order' => 'AN00556', + 'order_date' => '2025-11-24', + 'product_id' => 1, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '2' => '87', + '3' => '2025-11-23T17:00:00.000Z', + '5' => '30000000', + '7' => '2026-11-23T17:00:00.000Z', + '8' => 'SHM NIB 13.02.000012345.0', + '9' => '13040605.09817', + '10' => '34.04.060.002.020-0153.0', + '11' => 'Nyonya MUJIYEM', + '14' => 37500000, + '33' => 'HM 10570/SUMBERADI' + ], + 'created_at' => '2025-11-24T08:45:00.000000Z', + 'updated_at' => '2025-11-24T08:45:00.000000Z' + ] + ] + ], + [ + 'name' => 'Tuan BUDI WIDODO', + 'phone' => '085868542340', + 'address' => 'Butuh, RT 001, RW 001, Desa Muruh, Kecamatan Gantiwarno, Kabupaten Klaten', + 'created_at' => '2025-11-24T07:02:18.000000Z', + 'updated_at' => '2025-11-24T07:02:18.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR BBA'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00553', + 'order_date' => '2025-11-24', + 'product_id' => 3, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '2' => '84', + '3' => '2025-11-23T17:00:00.000Z', + '5' => '180000000', + '9' => '11.19.02.09.00655', + '10' => '33.10.020.011.007-0066.0', + '11' => 'Tuan BUDI WIDODO', + '14' => 225000000, + '33' => 'HM 01614/MURUH' + ], + 'created_at' => '2025-11-24T07:04:08.000000Z', + 'updated_at' => '2025-11-24T07:04:08.000000Z' + ] + ] + ], + [ + 'name' => 'Tuan ANDI TRISETIAWAN', + 'phone' => '088233435561', + 'address' => 'Bobung, Rukun Tetangga 009, Rukun Warga 002, Desa Putat, Kecamatan Patuk, Kabupaten Gunungkidul', + 'created_at' => '2025-11-21T09:23:54.000000Z', + 'updated_at' => '2025-11-21T09:23:54.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR PALA WNO'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00551', + 'order_date' => '2025-11-21', + 'product_id' => 2, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '2' => '170/2025', + '3' => '2025-11-20T17:00:00.000Z', + '5' => '35000000', + '7' => '2028-11-20T17:00:00.000Z', + '8' => 'SHM NIB 13.02.000009471.0', + '9' => '-', + '10' => '34.03.100.006.008-0323.0', + '11' => 'ANDI TRISETIAWAN', + '12' => '-', + '14' => 50000000, + '55' => '-' + ], + 'created_at' => '2025-11-21T09:26:43.000000Z', + 'updated_at' => '2025-11-21T09:26:43.000000Z' + ] + ] + ], + [ + 'name' => 'Tuan YOYOK SUPRIADI', + 'phone' => '085784857779', + 'address' => 'Jlagran Gedongtengen 2/351, Rukun Tetangga 015, Rukun Warga 003, Kelurahan Pringgokusuman, Kecamatan Gedongtengen, Kota Yogyakarta', + 'created_at' => '2025-11-21T09:22:34.000000Z', + 'updated_at' => '2025-11-21T09:22:34.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'KOPERASI ARTHA KENCANA'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00552', + 'order_date' => '2025-11-21', + 'product_id' => 2, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '2' => '82', + '3' => '2025-11-20T17:00:00.000Z', + '5' => '50000000', + '8' => null, + '9' => '13.05.000003240.0', + '10' => '34.71.120.001.001-0127.0', + '11' => 'YOYOK SUPRIADI', + '14' => 65000000 + ], + 'created_at' => '2025-11-21T09:27:29.000000Z', + 'updated_at' => '2025-11-21T09:27:29.000000Z' + ], + [ + 'no_order' => 'AN00557', + 'order_date' => '2025-11-21', + 'product_id' => 4, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '1' => 'PERJANJIAN PENGIKATAN JAMINAN FIDUSIA', + '2' => '88', + '3' => '2025-11-20T17:00:00.000Z', + '5' => '25000000', + '17' => 'BPKB Nomor: D-12345678', + '18' => '45000000', + '19' => 'YOYOK SUPRIADI', + '20' => '45000000', + '21' => '3401234567890123' + ], + 'created_at' => '2025-11-21T10:15:00.000000Z', + 'updated_at' => '2025-11-21T10:15:00.000000Z' + ] + ] + ], + [ + 'name' => 'Nyonya MUNAH', + 'phone' => '085790665374', + 'address' => 'Jalan Grinjing Nomor 17 Papringan, Rukun Tetangga 017, Rukun Warga 006, Desa Caturtunggal, Kecamatan Depok, Kabupaten Sleman', + 'created_at' => '2025-11-21T09:16:16.000000Z', + 'updated_at' => '2025-11-21T09:16:16.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR NUSAMBA PUSAT'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00550', + 'order_date' => '2025-11-21', + 'product_id' => 4, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '1' => 'PERJANJIAN PENGIKATAN JAMINAN FIDUSIA', + '2' => '83', + '3' => '2025-11-20T17:00:00.000Z', + '5' => '13500000', + '17' => 'BPKB Nomor: R-01906175 I', + '18' => '19500000', + '19' => 'MUNAH', + '20' => '19500000', + '21' => '3308207112940006', + '55' => '835/ZPW/LEG/XI/2025' + ], + 'created_at' => '2025-11-21T09:19:31.000000Z', + 'updated_at' => '2025-11-21T09:19:31.000000Z' + ] + ] + ], + [ + 'name' => 'Tuan SUGIYARTO', + 'phone' => '083173486696', + 'address' => 'Sambirejo, Rukun Tetangga 005, Rukun Warga 004, Desa Semanu, Kecamatan Semanu, Kabupaten Gunungkidul', + 'created_at' => '2025-11-21T09:12:29.000000Z', + 'updated_at' => '2025-11-21T09:12:29.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR ARUM MANDIRI'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00549', + 'order_date' => '2025-11-21', + 'product_id' => 2, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '2' => '167/2025', + '3' => '2025-11-18T17:00:00.000Z', + '5' => '10000000', + '7' => '2027-05-18T17:00:00.000Z', + '8' => '02664/Semanu', + '9' => '1302050502509', + '10' => '34.03.050.005.009-0047.0', + '11' => 'TUKILAH', + '12' => '-', + '13' => '-', + '14' => 87421000, + '55' => '-' + ], + 'created_at' => '2025-11-21T09:18:43.000000Z', + 'updated_at' => '2025-11-21T09:18:43.000000Z' + ] + ] + ], + [ + 'name' => 'Tuan DRS. ISKAK RUSMADI', + 'phone' => '081804329151', + 'address' => 'Sokokerep, Rukun Tetangga 007, Rukun Warga 049, Desa Semanu, Kecamatan Semanu, Kabupaten Gunungkidul', + 'created_at' => '2025-11-21T09:06:12.000000Z', + 'updated_at' => '2025-11-21T09:06:12.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR ARUM MANDIRI'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00548', + 'order_date' => '2025-11-21', + 'product_id' => 4, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '1' => 'PERJANJIAN PENGIKATAN JAMINAN FIDUSIA', + '2' => '81', + '3' => '2025-11-20T17:00:00.000Z', + '5' => '5000000', + '17' => 'BPKB O-07842719 I', + '18' => '9800000', + '19' => 'DRS ISKAK RUSMADI', + '20' => '9800000', + '21' => '696120393545', + '55' => '-' + ], + 'created_at' => '2025-11-21T09:09:18.000000Z', + 'updated_at' => '2025-11-21T09:09:18.000000Z' + ] + ] + ], + [ + 'name' => 'Tuan GATOT WUKIR SARTOPO', + 'phone' => '081802735959', + 'address' => 'Pacing Kidul, Rukun Tetangga 005, Rukun Warga 023, Desa Pacarejo, Kecamatan Semanu, Kabupaten Gunungkidul', + 'created_at' => '2025-11-21T09:01:33.000000Z', + 'updated_at' => '2025-11-21T09:01:33.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR ARUM MANDIRI'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00547', + 'order_date' => '2025-11-21', + 'product_id' => 4, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '1' => 'PERJANJIAN PENGIKATAN JAMINAN FIDUSIA', + '2' => '74', + '3' => '2025-11-18T17:00:00.000Z', + '5' => '20000000', + '17' => 'BPKB V-06238878', + '18' => '71400000', + '19' => 'ELISA FEBRIANA', + '20' => '71400000', + '21' => '959384611545000', + '55' => '-' + ], + 'created_at' => '2025-11-21T09:03:48.000000Z', + 'updated_at' => '2025-11-21T09:03:48.000000Z' + ] + ] + ], + [ + 'name' => 'Nyonya Ir. MARSIYATI', + 'phone' => '081328156455', + 'address' => 'Nitiprayan, RT 002, RW 000, Desa Ngestiharjo, Kecamatan Kasihan, Kabupaten Bantul', + 'created_at' => '2025-11-21T05:40:17.000000Z', + 'updated_at' => '2025-11-21T05:40:17.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR BBA'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00546', + 'order_date' => '2025-11-21', + 'product_id' => 3, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '2' => '80', + '3' => '2025-11-20T17:00:00.000Z', + '5' => '225000000', + '9' => '13010304.25614', + '10' => '34.02.150.001.017-0602.0', + '11' => 'Ir. MARSIYATI', + '14' => 281250000, + '33' => 'HM. 21314/Bangunjiwo' + ], + 'created_at' => '2025-11-21T05:42:00.000000Z', + 'updated_at' => '2025-11-21T05:42:00.000000Z' + ] + ] + ], + [ + 'name' => 'Tuan AFANDI NUGROHO', + 'phone' => '+62 87775757493', + 'address' => 'Kedaton, Rukun Tetangga 001, Rukun Warga 000, Desa Pleret, Kecamatan Pleret, Kabupaten Bantul', + 'created_at' => '2025-11-19T09:27:55.000000Z', + 'updated_at' => '2025-11-19T09:27:55.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR DANAMAS PRIMA'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00545', + 'order_date' => '2025-11-19', + 'product_id' => 4, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '1' => 'PERJANJIAN PENGIKATAN JAMINAN FIDUSIA', + '2' => '73', + '3' => '2025-11-18T17:00:00.000Z', + '5' => '20000000', + '17' => 'BPKB Nomor: Q-01258649 I', + '18' => '75000000', + '19' => 'MEILINA DUIKORINA', + '20' => '75000000', + '21' => '-', + '55' => '832/ZPW/LEG/XI/2025' + ], + 'created_at' => '2025-11-19T09:29:35.000000Z', + 'updated_at' => '2025-11-19T09:29:35.000000Z' + ] + ] + ], + [ + 'name' => 'Nyonya YUNIARTI', + 'phone' => '081393717662', + 'address' => 'Pajangan, Rukun Tetangga 003, Rukun Warga 010, Desa Sendangtirto, Kecamatan Berbah, Kabupaten Sleman', + 'created_at' => '2025-11-19T09:01:16.000000Z', + 'updated_at' => '2025-11-19T09:01:16.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR NUSAMBA PUSAT'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00544', + 'order_date' => '2025-11-19', + 'product_id' => 4, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '1' => 'PERJANJIAN PENGIKATAN JAMINAN FIDUSIA', + '2' => '70', + '3' => '2025-11-18T17:00:00.000Z', + '5' => '45000000', + '17' => 'BPKB Nomor: M-12286246', + '18' => '75000000', + '19' => 'SIGIT', + '20' => '75000000', + '21' => '3471125005690002', + '55' => '830/ZPW/LEG/XI/2025' + ], + 'created_at' => '2025-11-19T09:03:16.000000Z', + 'updated_at' => '2025-11-19T09:03:16.000000Z' + ] + ] + ], + [ + 'name' => 'Tuan TUKIMAN', + 'phone' => '085878069250', + 'address' => 'Temuwuh, RT 003, RW 000, Desa Temuruh, Kecamatan Dlingo, Kabupaten Bantul', + 'created_at' => '2025-11-19T08:26:12.000000Z', + 'updated_at' => '2025-11-19T08:26:12.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR PALA PUSAT'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00543', + 'order_date' => '2025-11-19', + 'product_id' => 2, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '2' => '72', + '3' => '2025-11-18T17:00:00.000Z', + '5' => '260000000', + '8' => 'HM. 00821/Temuwuh', + '9' => '13.01.17.04.00466', + '10' => '34.02.100.004.011-0183.0', + '11' => 'Haris Pujianto Alias Tukiman', + '14' => 50000000 + ], + 'created_at' => '2025-11-19T08:27:55.000000Z', + 'updated_at' => '2025-11-19T08:27:55.000000Z' + ] + ] + ], + [ + 'name' => 'Nyonya SUMARNI', + 'phone' => '08', + 'address' => 'Grogol 6, Rukun Tetangga 007, Rukun Warga 006, Desa Bejiharjo, Kecamatan Karangmojo, Kabupaten Gunungkidul', + 'created_at' => '2025-11-19T08:02:17.000000Z', + 'updated_at' => '2025-11-19T08:02:17.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR ARUM MANDIRI'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00542', + 'order_date' => '2025-11-19', + 'product_id' => 4, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '1' => 'PERJANJIAN PENGIKATAN JAMINAN FIDUSIA', + '2' => '64', + '3' => '2025-11-17T17:00:00.000Z', + '5' => '9000000', + '17' => 'FC BPKB P-01862335 I', + '18' => '13500000', + '19' => 'SLAMET', + '20' => '13500000', + '21' => '-', + '55' => '-' + ], + 'created_at' => '2025-11-19T08:04:11.000000Z', + 'updated_at' => '2025-11-19T08:04:11.000000Z' + ] + ] + ], + [ + 'name' => 'Nyonya NINING PUSPITASARI', + 'phone' => '081328405191', + 'address' => 'Nglebak, Rukun Tetangga 009, Rukun Warga -, Desa Palbapang, Kecamatan Bantul, Kabupaten Bantul', + 'created_at' => '2025-11-19T07:58:08.000000Z', + 'updated_at' => '2025-11-19T07:58:08.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR NUSAMBA BANTUL'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00541', + 'order_date' => '2025-11-19', + 'product_id' => 4, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '1' => 'PERJANJIAN PENGIKATAN JAMINAN FIDUSIA', + '2' => '71', + '3' => '2025-11-18T17:00:00.000Z', + '5' => '25000000', + '17' => 'BPKB Nomor: O-07875341', + '18' => '115000000', + '19' => 'NINING PUSPITASARI', + '20' => '115000000', + '21' => '3402086909790005', + '55' => '831/ZPW/LEG/XI/2025' + ], + 'created_at' => '2025-11-19T08:02:27.000000Z', + 'updated_at' => '2025-11-19T08:02:27.000000Z' + ] + ] + ], + [ + 'name' => 'Tuan KALIS SULIA ATMOJO', + 'phone' => '085234273005', + 'address' => 'Sumbermulyo, Rukun Tetangga 004, Rukun Warga 003, Desa Kepek, Kecamatan Wonosari, Kabupaten Gunungkidul', + 'created_at' => '2025-11-19T07:56:29.000000Z', + 'updated_at' => '2025-11-19T07:56:29.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR PALA WNO'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00540', + 'order_date' => '2025-11-19', + 'product_id' => 2, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '2' => '166/2025', + '3' => '2025-11-18T17:00:00.000Z', + '5' => '40000000', + '7' => '2028-11-18T17:00:00.000Z', + '8' => '00679/Karangrejek', + '9' => '1302080700701', + '10' => '34.03.080.007.016-0055.0', + '11' => 'SAMIRAN', + '12' => '-', + '13' => 'A0 102431', + '14' => 60000000, + '55' => '-' + ], + 'created_at' => '2025-11-19T07:59:45.000000Z', + 'updated_at' => '2025-11-19T07:59:45.000000Z' + ] + ] + ], + [ + 'name' => 'Nyonya HARTINI', + 'phone' => '083854230787', + 'address' => 'Gunting, Rukun Tetangga 0003, Rukun Warga -, Desa Gilangharjo, Kecamatan Pandak, Kabupaten Bantul', + 'created_at' => '2025-11-17T06:11:52.000000Z', + 'updated_at' => '2025-11-17T06:11:52.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR NUSAMBA BANTUL'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00538', + 'order_date' => '2025-11-17', + 'product_id' => 4, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '1' => 'PERJANJIAN PENGIKATAN JAMINAN FIDUSIA', + '2' => '59', + '3' => '2025-11-16T17:00:00.000Z', + '5' => '12000000', + '17' => 'BPKB Nomor: S-01417439 I', + '18' => '20000000', + '19' => 'HERI KURNIAWAN', + '20' => '20000000', + '21' => '-', + '55' => '824/ZPW/LEG/XI/2025' + ], + 'created_at' => '2025-11-17T06:13:04.000000Z', + 'updated_at' => '2025-11-17T06:13:04.000000Z' + ] + ] + ], + [ + 'name' => 'Tuan SUPRIYADI', + 'phone' => '081252240395', + 'address' => 'Deresan Dukuh Deresan, Rukun Tetangga 002, Rukun Warga -, Desa Ringin Harjo, Kecamatan Bantul, Kabupaten Bantul', + 'created_at' => '2025-11-17T06:08:18.000000Z', + 'updated_at' => '2025-11-17T06:08:18.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR NUSAMBA BANTUL'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00537', + 'order_date' => '2025-11-17', + 'product_id' => 4, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '1' => 'PERJANJIAN PENGIKATAN JAMINAN FIDUSIA', + '2' => '58', + '3' => '2025-11-16T17:00:00.000Z', + '5' => '35000000', + '17' => 'BPKB Nomor: Q-01227826', + '18' => '100000000', + '19' => 'INTAN PRIDANI PUTRI PRASTIWI', + '20' => '100000000', + '21' => '-', + '55' => '823/ZPW/LEG/XI/2025' + ], + 'created_at' => '2025-11-17T06:09:42.000000Z', + 'updated_at' => '2025-11-17T06:09:42.000000Z' + ] + ] + ], + [ + 'name' => 'MURTONO', + 'phone' => '081328610235', + 'address' => 'Glagah, Rukun Tetangga 006, Rukun Warga 006, Desa Kemiri, Kecamatan Tanjungsari, Kabupaten Gunungkidul', + 'created_at' => '2025-11-15T07:43:09.000000Z', + 'updated_at' => '2025-11-15T07:43:09.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR PALA WNO'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00536', + 'order_date' => '2025-11-14', + 'product_id' => 3, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '5' => '100000000', + '10' => '34.03.070.001.001-0017.0', + '11' => 'MURTONO', + '13' => 'A8643216', + '14' => 60000000, + '33' => 'HM NIB 13.02.000030081.0', + '55' => '-' + ], + 'created_at' => '2025-11-15T07:50:13.000000Z', + 'updated_at' => '2025-11-15T07:51:49.000000Z' + ], + [ + 'no_order' => 'AN00535', + 'order_date' => '2025-11-15', + 'product_id' => 3, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '5' => '100000000', + '9' => '-', + '10' => '34.03.070.001.004-0004.0', + '11' => 'HARTO WIYONO', + '12' => '-', + '13' => 'AF 485263', + '14' => 90000000, + '33' => '00467/Bendungan', + '55' => '-' + ], + 'created_at' => '2025-11-15T07:46:39.000000Z', + 'updated_at' => '2025-11-15T07:46:39.000000Z' + ], + [ + 'no_order' => 'AN00558', + 'order_date' => '2025-11-15', + 'product_id' => 2, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '2' => '168/2025', + '3' => '2025-11-14T17:00:00.000Z', + '5' => '150000000', + '7' => '2029-11-14T17:00:00.000Z', + '8' => 'HM 00900/Kemiri', + '9' => '13.05.17.06.00789', + '10' => '34.03.070.001.005-0012.0', + '11' => 'MURTONO', + '14' => 187500000, + '33' => 'HM 00901/Kemiri' + ], + 'created_at' => '2025-11-15T08:30:00.000000Z', + 'updated_at' => '2025-11-15T08:30:00.000000Z' + ] + ] + ], + [ + 'name' => 'Nyonya MIPAH YULIANTI', + 'phone' => '081329805861', + 'address' => 'Soboman, RT 005, RW 000, Desa Ngestiharjo, Kecamatan Kasihan, Kabupaten Bantul', + 'created_at' => '2025-11-14T08:43:43.000000Z', + 'updated_at' => '2025-11-14T08:43:43.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR NUSAMBA KK KADIPIRO'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00534', + 'order_date' => '2025-11-14', + 'product_id' => 26, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '1' => 'Kesepakatan Jual Beli, Surat Kuasa Menjual', + '2' => '52 ; 53', + '3' => '2025-11-13T17:00:00.000Z', + '5' => '350000000', + '19' => 'Nyonya MIPAH YULIANTI', + '20' => '525000000', + '30' => '2025-11-13T17:00:00.000Z', + '33' => 'HM 09346/Ngestiharjo', + '55' => '821/ZPW/LEG/XI/2025' + ], + 'created_at' => '2025-11-14T08:48:25.000000Z', + 'updated_at' => '2025-11-14T08:48:25.000000Z' + ] + ] + ], + [ + 'name' => 'Tuan FATAJI SUSIADI', + 'phone' => '089674435783', + 'address' => 'Jl. Maundri No. 6A, Rejokusuman, Sokowaten, RT 004, RW 000, Desa Tamanan, Kecamatan Banguntapan, Kabupaten Bantul', + 'created_at' => '2025-11-14T08:39:51.000000Z', + 'updated_at' => '2025-11-14T08:39:51.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR NUSAMBA PUSAT'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00533', + 'order_date' => '2025-11-13', + 'product_id' => 26, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '1' => 'Perjanjian Kredit ; Surat Kuasa Membebankan Hak Tanggungan', + '2' => '42 ; 43', + '3' => '2025-11-12T17:00:00.000Z', + '5' => '450000000', + '19' => 'FATAJI SUSIADI', + '20' => '675000000', + '30' => '2025-11-12T17:00:00.000Z', + '33' => 'HM 06396/Tamanan', + '55' => '815/ZPW/LEG/XI/2025' + ], + 'created_at' => '2025-11-14T08:42:46.000000Z', + 'updated_at' => '2025-11-14T08:42:46.000000Z' + ] + ] + ], + [ + 'name' => 'Tuan EKO SUHARTONO, Bachelor of Science', + 'phone' => '082220003546', + 'address' => 'Jomblangan, Rukun Tetangga 004, Rukun Warga -, Desa Banguntapan, Kecamatan Banguntapan, Kabupaten Bantul', + 'created_at' => '2025-11-14T08:38:43.000000Z', + 'updated_at' => '2025-11-14T08:38:43.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR NUSAMBA PUSAT'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00532', + 'order_date' => '2025-11-14', + 'product_id' => 4, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '1' => 'PERJANJIAN PENGIKATAN JAMINAN FIDUSIA', + '2' => '51', + '3' => '2025-11-13T17:00:00.000Z', + '5' => '15000000', + '17' => 'BPKB Nomor: S-01341505', + '18' => '110000000', + '19' => 'LUCIA ETTY KRISTIANI', + '20' => '110000000', + '21' => '-', + '55' => '820/ZPW/LEG/XI/2025' + ], + 'created_at' => '2025-11-14T08:40:32.000000Z', + 'updated_at' => '2025-11-14T08:40:32.000000Z' + ] + ] + ], + [ + 'name' => 'Tuan ANDI PASKAH KURNIAWAN', + 'phone' => '08994565689', + 'address' => 'Kitren Kotagede 2/664, Rukun Tetangga 036, Rukun Warga 008, Kelurahan Prenggan, Kecamatan Kotagede, Kota Yogyakarta', + 'created_at' => '2025-11-14T08:35:51.000000Z', + 'updated_at' => '2025-11-14T08:35:51.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR NUSAMBA PUSAT'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00531', + 'order_date' => '2025-11-14', + 'product_id' => 4, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '1' => 'PERJANJIAN PENGIKATAN JAMINAN FIDUSIA', + '2' => '50', + '3' => '2025-11-13T17:00:00.000Z', + '5' => '22000000', + '17' => '1. BPKB Nomor: R-01828236 I, 2. BPKB Nomor: N-10675133 I', + '18' => '38000000', + '19' => '1. WINARTI, S.Pd., 2. ANDREAS WIRATSONGKO', + '20' => '38000000', + '21' => '-', + '55' => '819/ZPW/LEG/XI/2025' + ], + 'created_at' => '2025-11-14T08:37:45.000000Z', + 'updated_at' => '2025-11-14T08:37:45.000000Z' + ] + ] + ], + [ + 'name' => 'Nyonya VERONIKA ELYSA KURNIADEWI', + 'phone' => '08158791209', + 'address' => 'Perumahan Sendok Indah Kotagede II/373, Rukun Tetangga 020, Rukun Warga 004, Kelurahan Prenggan, Kecamatan Kotagede, Kota Yogyakarta', + 'created_at' => '2025-11-14T08:26:09.000000Z', + 'updated_at' => '2025-11-14T08:26:09.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR NUSAMBA PUSAT'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00530', + 'order_date' => '2025-11-14', + 'product_id' => 25, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '1' => 'Perjanjian Pengikatan Jaminan Fidusia & Perjanjian Kredit Notariil', + '2' => '55', + '3' => '2025-11-13T17:00:00.000Z', + '5' => '85000000', + '17' => 'BPKB Nomor: P-02805918 I', + '18' => '220000000', + '19' => 'VERONIKA ELYSA KURNIADEWI', + '20' => '220000000', + '21' => '-', + '33' => 'BPKB Nomor: P-02805918 I', + '55' => '-' + ], + 'created_at' => '2025-11-14T08:28:41.000000Z', + 'updated_at' => '2025-11-14T08:28:41.000000Z' + ] + ] + ], + [ + 'name' => 'Tuan NGADIMIN', + 'phone' => '0819 1462 7639', + 'address' => 'Warak Kidul, Rukun Tetangga 003, Rukun Warga 009, Desa Sumberadi, Kecamatan Mlati, Kabupaten Sleman.', + 'created_at' => '2025-11-14T04:14:51.000000Z', + 'updated_at' => '2025-11-14T04:14:51.000000Z', + 'meta' => [ + ['meta_key' => 'bank', 'meta_value' => 'BPR NUSAMBA KK PIYUNGAN'] + ], + 'orders' => [ + [ + 'no_order' => 'AN00529', + 'order_date' => '2025-11-14', + 'product_id' => 4, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '1' => 'PERJANJIAN PENGIKATAN JAMINAN FIDUSIA', + '2' => '49', + '3' => '2025-11-13T17:00:00.000Z', + '5' => '30000000', + '17' => 'BPKB Nomor: K-03108372 I', + '18' => '100000000', + '19' => 'UTI HARTATI', + '20' => '100000000', + '21' => '-', + '55' => '818/ZPW/LEG/XI/2025' + ], + 'created_at' => '2025-11-14T04:16:19.000000Z', + 'updated_at' => '2025-11-14T04:16:19.000000Z' + ], + [ + 'no_order' => 'AN00559', + 'order_date' => '2025-11-14', + 'product_id' => 1, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '2' => '56', + '3' => '2025-11-13T17:00:00.000Z', + '5' => '120000000', + '7' => '2027-11-13T17:00:00.000Z', + '8' => 'SHM NIB 13.02.000045678.0', + '9' => '1302080601234', + '10' => '34.03.080.007.017-0056.0', + '11' => 'NGADIMIN', + '14' => 150000000, + '33' => 'HM 01234/Sumberadi' + ], + 'created_at' => '2025-11-14T09:30:00.000000Z', + 'updated_at' => '2025-11-14T09:30:00.000000Z' + ], + [ + 'no_order' => 'AN00560', + 'order_date' => '2025-11-14', + 'product_id' => 3, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'meta' => [ + '5' => '80000000', + '9' => '1302080601235', + '10' => '34.03.080.007.018-0057.0', + '11' => 'NGADIMIN', + '13' => 'A7654321', + '14' => 100000000, + '33' => 'HM 01235/Sumberadi' + ], + 'created_at' => '2025-11-14T10:45:00.000000Z', + 'updated_at' => '2025-11-14T10:45:00.000000Z' + ] + ] + ], + ]; + + // Insert specific customers with their meta and order data + foreach ($customers as $customerData) { + $meta = $customerData['meta'] ?? []; + $orders = $customerData['orders'] ?? []; + unset($customerData['meta']); + unset($customerData['orders']); + + $customer = Customer::create($customerData); + + // Insert meta data for the customer + foreach ($meta as $metaData) { + $customer->meta()->create($metaData); + } + + // Insert orders for the customer + foreach ($orders as $orderData) { + $orderMeta = $orderData['meta'] ?? []; + unset($orderData['meta']); + + $order = $customer->orders()->create($orderData); + + // Insert meta data for the order + if (!empty($orderMeta)) { + $order->meta = $orderMeta; + $order->save(); + } + } + } + + // Create additional random customers if needed + Customer::factory(50)->create(); + } +} diff --git a/database/seeders/DataSeeder.php b/database/seeders/DataSeeder.php new file mode 100644 index 0000000..28e9716 --- /dev/null +++ b/database/seeders/DataSeeder.php @@ -0,0 +1,17 @@ +create(); - User::factory()->create([ - 'name' => 'Test User', - 'email' => 'test@example.com', + $this->call([ + SettingSeeder::class, + RoleSeeder::class, + PermissionSeeder::class, + UserSeeder::class, + ProductSeeder::class, + CategorySeeder::class, + CustomerSeeder::class, + OrderSeeder::class, + PostSeeder::class, + NotificationSeeder::class, ]); } } diff --git a/database/seeders/NotificationSeeder.php b/database/seeders/NotificationSeeder.php new file mode 100644 index 0000000..a9f9a86 --- /dev/null +++ b/database/seeders/NotificationSeeder.php @@ -0,0 +1,175 @@ +truncate(); + + // Ambil sample users dan orders + $users = User::take(5)->get(); + $orders = Order::take(10)->get(); + + if ($users->isEmpty() || $orders->isEmpty()) { + $this->command->warn('Tidak ada user atau order untuk membuat notifikasi. Jalankan UserSeeder dan OrderSeeder terlebih dahulu.'); + return; + } + + $notifications = []; + $now = Carbon::now(); + + // Template notifikasi untuk berbagai jenis + $notificationTemplates = [ + // New Order Notifications + [ + 'type' => 'App\Notifications\NewOrderNotification', + 'data' => [ + 'title' => 'Order Baru Masuk', + 'message' => 'Ada order baru yang perlu ditangani', + 'order_id' => null, // akan diisi dinamis + 'order_no' => null, // akan diisi dinamis + 'customer_name' => null, // akan diisi dinamis + 'action_url' => '/orders/', + 'icon' => 'bell', + 'type' => 'new_order' + ] + ], + // Pending Jobdesk Notifications + [ + 'type' => 'App\Notifications\PendingJobdesk', + 'data' => [ + 'title' => 'Tugas Menunggu Dikerjakan', + 'message' => 'Ada tugas yang belum dikerjakan dan mendekati deadline', + 'jobdesk_id' => null, // akan diisi dinamis + 'order_id' => null, // akan diisi dinamis + 'deadline' => null, // akan diisi dinamis + 'action_url' => '/jobdesk/', + 'icon' => 'clock', + 'type' => 'pending_jobdesk' + ] + ], + // System Notifications + [ + 'type' => 'App\Notifications\SystemNotification', + 'data' => [ + 'title' => 'Selamat Datang di Sistem', + 'message' => 'Akun Anda telah berhasil dibuat dan diaktifkan', + 'action_url' => '/dashboard', + 'icon' => 'user-check', + 'type' => 'welcome' + ] + ], + [ + 'type' => 'App\Notifications\SystemNotification', + 'data' => [ + 'title' => 'Backup Database Berhasil', + 'message' => 'Backup database harian telah berhasil dilakukan', + 'action_url' => '/settings', + 'icon' => 'database', + 'type' => 'backup_success' + ] + ], + [ + 'type' => 'App\Notifications\SystemNotification', + 'data' => [ + 'title' => 'Update Sistem Tersedia', + 'message' => 'Ada update sistem terbaru yang tersedia untuk diinstall', + 'action_url' => '/system/updates', + 'icon' => 'download', + 'type' => 'system_update' + ] + ] + ]; + + // Buat notifikasi untuk setiap user + foreach ($users as $user) { + // Buat 3-5 notifikasi per user + $notificationCount = rand(3, 5); + + for ($i = 0; $i < $notificationCount; $i++) { + $template = $notificationTemplates[array_rand($notificationTemplates)]; + $order = $orders->random(); + + // Sesuaikan data berdasarkan jenis notifikasi + $data = $template['data']; + + if ($template['type'] === 'App\Notifications\NewOrderNotification') { + $data['order_id'] = $order->id; + $data['order_no'] = $order->no_order; + $data['customer_name'] = $order->customer->name ?? 'Customer'; + $data['action_url'] = '/orders/' . $order->id; + } elseif ($template['type'] === 'App\Notifications\PendingJobdesk') { + $data['order_id'] = $order->id; + $data['jobdesk_id'] = rand(1, 50); // Random jobdesk ID + $data['deadline'] = $now->copy()->addDays(rand(1, 7))->format('Y-m-d H:i:s'); + $data['action_url'] = '/jobdesk/' . $data['jobdesk_id']; + } + + // Tentukan status read/unread (80% unread, 20% read) + $isRead = rand(1, 100) <= 20; + + $notifications[] = [ + 'id' => \Illuminate\Support\Str::uuid(), + 'type' => $template['type'], + 'notifiable_type' => 'App\Models\User', + 'notifiable_id' => $user->id, + 'data' => json_encode($data), + 'read_at' => $isRead ? $now->copy()->subDays(rand(1, 30))->format('Y-m-d H:i:s') : null, + 'created_at' => $now->copy()->subDays(rand(0, 30))->subHours(rand(0, 23))->format('Y-m-d H:i:s'), + 'updated_at' => $now->format('Y-m-d H:i:s'), + ]; + } + } + + // Tambahkan beberapa notifikasi broadcast untuk semua user + $broadcastNotifications = [ + [ + 'title' => 'Maintenance Sistem Terjadwal', + 'message' => 'Sistem akan menjalani maintenance pada hari Minggu jam 02:00 - 04:00 WIB', + 'icon' => 'tools', + 'type' => 'maintenance', + 'action_url' => '/announcements' + ], + [ + 'title' => 'Pelatihan Sistem Baru', + 'message' => 'Akan ada pelatihan penggunaan fitur baru sistem pada hari Jumat', + 'icon' => 'graduation-cap', + 'type' => 'training', + 'action_url' => '/training' + ] + ]; + + foreach ($broadcastNotifications as $broadcast) { + foreach ($users as $user) { + $notifications[] = [ + 'id' => \Illuminate\Support\Str::uuid(), + 'type' => 'App\Notifications\SystemNotification', + 'notifiable_type' => 'App\Models\User', + 'notifiable_id' => $user->id, + 'data' => json_encode($broadcast), + 'read_at' => rand(1, 100) <= 30 ? $now->copy()->subDays(rand(1, 5))->format('Y-m-d H:i:s') : null, + 'created_at' => $now->copy()->subDays(rand(1, 7))->format('Y-m-d H:i:s'), + 'updated_at' => $now->format('Y-m-d H:i:s'), + ]; + } + } + + // Insert semua notifikasi ke database + DB::table('notifications')->insert($notifications); + + $this->command->info('Berhasil membuat ' . count($notifications) . ' notifikasi untuk ' . $users->count() . ' user'); + } +} diff --git a/database/seeders/OrderSeeder.php b/database/seeders/OrderSeeder.php new file mode 100644 index 0000000..8863b9b --- /dev/null +++ b/database/seeders/OrderSeeder.php @@ -0,0 +1,260 @@ + 'AN00561', + 'order_date' => '2025-11-25', + 'product_id' => 1, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'customer' => [ + 'name' => 'Tuan AHMAD RIZKI', + 'phone' => '081223345678', + 'address' => 'Jl. Malioboro No. 45, Gedongtengen, Kecamatan Gondokusuman, Kota Yogyakarta' + ], + 'meta' => [ + '1' => 'Akta Jual Beli', + '2' => '89/2025', + '3' => '2025-11-24T17:00:00.000Z', + '5' => '250000000', + '7' => '2026-11-24T17:00:00.000Z', + '8' => 'SHM NIB 13.02.000056789.0', + '9' => '3404010123456', + '10' => '34.71.100.004.019-0056.0', + '11' => 'AHMAD RIZKI', + '12' => '-', + '14' => 312500000, + '33' => 'HM 23456/Gedongtengen' + ] + ], + [ + 'no_order' => 'AN00562', + 'order_date' => '2025-11-25', + 'product_id' => 2, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'customer' => [ + 'name' => 'Nyonya SITI NURHALIZAH', + 'phone' => '087654321098', + 'address' => 'Perumahan Griya Citra Asri Blok A No. 12, Condongcatur, Kecamatan Depok, Kabupaten Sleman' + ], + 'meta' => [ + '2' => '90/2025', + '3' => '2025-11-24T17:00:00.000Z', + '5' => '150000000', + '7' => '2027-11-24T17:00:00.000Z', + '8' => 'HM 34567/Condongcatur', + '9' => '1302050701234', + '10' => '34.03.080.007.019-0067.0', + '11' => 'SITI NURHALIZAH', + '14' => 187500000, + '33' => 'HM 34568/Condongcatur' + ] + ], + [ + 'no_order' => 'AN00563', + 'order_date' => '2025-11-25', + 'product_id' => 3, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'customer' => [ + 'name' => 'Tuan BAMBANG SUGIANTO', + 'phone' => '082345678901', + 'address' => 'Jl. Parangtritis Km 8, Sewon, Bantul, DIY' + ], + 'meta' => [ + '5' => '300000000', + '9' => '1301030456789', + '10' => '34.02.150.001.018-0089.0', + '11' => 'BAMBANG SUGIANTO', + '13' => 'A1234567', + '14' => 375000000, + '33' => 'HM 456789/Sewon' + ] + ], + [ + 'no_order' => 'AN00564', + 'order_date' => '2025-11-25', + 'product_id' => 4, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'customer' => [ + 'name' => 'Nyonya DEWI LESTARI', + 'phone' => '089876543210', + 'address' => 'Jl. Kaliurang Km 10, Ngaglik, Sleman, DIY' + ], + 'meta' => [ + '1' => 'PERJANJIAN PENGIKATAN JAMINAN FIDUSIA', + '2' => '91', + '3' => '2025-11-24T17:00:00.000Z', + '5' => '50000000', + '17' => 'BPKB Nomor: AB-12345678', + '18' => '85000000', + '19' => 'DEWI LESTARI', + '20' => '85000000', + '21' => '3401023456789012' + ] + ], + [ + 'no_order' => 'AN00565', + 'order_date' => '2025-11-25', + 'product_id' => 1, + 'price' => 0, + 'payment_method' => 'Transfer', + 'paid' => 0, + 'customer' => [ + 'name' => 'Tuan SLAMET RAHARJO', + 'phone' => '081234567890', + 'address' => 'Jl. Wates Km 12, Pengasih, Kulon Progo, DIY' + ], + 'meta' => [ + '2' => '92/2025', + '3' => '2025-11-24T17:00:00.000Z', + '5' => '175000000', + '7' => '2028-11-24T17:00:00.000Z', + '8' => 'SHM NIB 13.02.000067890.0', + '9' => '3403010234567', + '10' => '34.01.020.003.012-0098.0', + '11' => 'SLAMET RAHARJO', + '14' => 218750000, + '33' => 'HM 567890/Pengasih' + ] + ] + ]; + + // Insert 5 perorangan orders (individual customers) + foreach ($peroranganOrders as $orderData) { + $customerData = $orderData['customer']; + $orderMeta = $orderData['meta'] ?? []; + + // Extract order data (remove customer and meta) + $orderInfo = [ + 'no_order' => $orderData['no_order'], + 'order_date' => $orderData['order_date'], + 'product_id' => $orderData['product_id'], + 'price' => $orderData['price'], + 'payment_method' => $orderData['payment_method'], + 'paid' => $orderData['paid'], + 'created_at' => '2025-11-25T10:00:00.000000Z', + 'updated_at' => '2025-11-25T10:00:00.000000Z' + ]; + + unset($orderData['customer']); + unset($orderData['meta']); + + // Create or find customer + $customer = Customer::firstOrCreate( + ['phone' => $customerData['phone']], + [ + 'name' => $customerData['name'], + 'address' => $customerData['address'], + 'created_at' => '2025-11-25T09:00:00.000000Z', + 'updated_at' => '2025-11-25T09:00:00.000000Z' + ] + ); + + // Create order + $order = $customer->orders()->create($orderInfo); + + // Create order meta data + $order->meta = $orderMeta; + $order->save(); + + // Add Perorangan meta for these individual customers + $customer->meta()->create([ + 'meta_key' => 'bank', + 'meta_value' => 'Perorangan' + ]); + } + + // Insert bank orders (15 orders with bank customers) + $bankOrders = [ + // BPR NUSAMBA PUSAT orders + ['no_order' => 'BP001', 'product_id' => 2, 'bank' => 'BPR NUSAMBA PUSAT', 'customer_name' => 'PT. MITRA SEJAHTERA', 'customer_phone' => '02745551234', 'customer_address' => 'Jl. Sudirman No. 123, Yogyakarta'], + ['no_order' => 'BP002', 'product_id' => 1, 'bank' => 'BPR NUSAMBA PUSAT', 'customer_name' => 'CV. JAYA ABADI', 'customer_phone' => '02745551123', 'customer_address' => 'Jl. Malioboro No. 56, Yogyakarta'], + ['no_order' => 'BP003', 'product_id' => 3, 'bank' => 'BPR NUSAMBA PUSAT', 'customer_name' => 'PT. BERKAH MULYA', 'customer_phone' => '02745551345', 'customer_address' => 'Jl. Solo Km 12, Yogyakarta'], + + // BPR BBA orders + ['no_order' => 'BB001', 'product_id' => 2, 'bank' => 'BPR BBA', 'customer_name' => 'PT. MAJU JAYA', 'customer_phone' => '027433344567', 'customer_address' => 'Jl. Kaliurang Km 5, Sleman'], + ['no_order' => 'BB002', 'product_id' => 1, 'bank' => 'BPR BBA', 'customer_name' => 'CV. KARYA MANDIRI', 'customer_phone' => '027433344578', 'customer_address' => 'Jl. Gejayan No. 88, Yogyakarta'], + + // BPR PALA WNO orders + ['no_order' => 'PW001', 'product_id' => 3, 'bank' => 'BPR PALA WNO', 'customer_name' => 'PT. INDO TEKNIK', 'customer_phone' => '027422233890', 'customer_address' => 'Jl. Ring Road Utara, Sleman'], + ['no_order' => 'PW002', 'product_id' => 4, 'bank' => 'BPR PALA WNO', 'customer_name' => 'CV. SENTOSA ABADI', 'customer_phone' => '027422233901', 'customer_address' => 'Jl. Magelang Km 10, Yogyakarta'], + + // BPR ARUM MANDIRI orders + ['no_order' => 'AM001', 'product_id' => 2, 'bank' => 'BPR ARUM MANDIRI', 'customer_name' => 'PT. MAHKOTA INDAH', 'customer_phone' => '027411122345', 'customer_address' => 'Jl. Wates Km 8, Bantul'], + ['no_order' => 'AM002', 'product_id' => 1, 'bank' => 'BPR ARUM MANDIRI', 'customer_name' => 'CV. CAHAYA MULIA', 'customer_phone' => '027411122456', 'customer_address' => 'Jl. Parangtritis Km 5, Bantul'], + + // BPR NUSAMBA BANTUL orders + ['no_order' => 'NB001', 'product_id' => 4, 'bank' => 'BPR NUSAMBA BANTUL', 'customer_name' => 'PT. BANTUL SEJAHTERA', 'customer_phone' => '027500998765', 'customer_address' => 'Jl. Bantul Km 3, Bantul'], + ['no_order' => 'NB002', 'product_id' => 3, 'bank' => 'BPR NUSAMBA BANTUL', 'customer_name' => 'CV. NUSANTARA JAYA', 'customer_phone' => '027500998876', 'customer_address' => 'Jl. Imogiri Timur Km 10, Bantul'], + + // BPR DANAMAS PRIMA orders + ['no_order' => 'DP001', 'product_id' => 2, 'bank' => 'BPR DANAMAS PRIMA', 'customer_name' => 'PT. DANAMAS INVESTAMA', 'customer_phone' => '027466677890', 'customer_address' => 'Jl. Godean Km 9, Sleman'], + ['no_order' => 'DP002', 'product_id' => 1, 'bank' => 'BPR DANAMAS PRIMA', 'customer_name' => 'CV. PRIMA KARYA', 'customer_phone' => '027466677901', 'customer_address' => 'Jl. Palagan Km 7, Yogyakarta'], + + // KOPERASI ARTHA KENCANA orders + ['no_order' => 'AK001', 'product_id' => 3, 'bank' => 'KOPERASI ARTHA KENCANA', 'customer_name' => 'KOPERASI KARYA TANI', 'customer_phone' => '027477788012', 'customer_address' => 'Jl. Kaliurang Km 15, Sleman'], + ['no_order' => 'AK002', 'product_id' => 4, 'bank' => 'KOPERASI ARTHA KENCANA', 'customer_name' => 'KOPKAR BERSATU', 'customer_phone' => '027477788123', 'customer_address' => 'Jl. Seturan Raya, Sleman'], + ]; + + // Insert bank orders with bank customers + foreach ($bankOrders as $orderData) { + // Extract order data + $orderInfo = [ + 'no_order' => $orderData['no_order'], + 'order_date' => '2025-11-25', + 'product_id' => $orderData['product_id'], + 'price' => rand(50000000, 500000000), + 'payment_method' => 'Transfer', + 'paid' => 0, + 'created_at' => '2025-11-25T11:00:00.000000Z', + 'updated_at' => '2025-11-25T11:00:00.000000Z' + ]; + + // Create or find bank customer + $customer = Customer::firstOrCreate( + ['phone' => $orderData['customer_phone']], + [ + 'name' => $orderData['customer_name'], + 'address' => $orderData['customer_address'], + 'created_at' => '2025-11-25T10:30:00.000000Z', + 'updated_at' => '2025-11-25T10:30:00.000000Z' + ] + ); + + // Create order + $order = $customer->orders()->create($orderInfo); + + // Create basic order meta data for bank orders + $order->meta = [ + '2' => rand(100, 999) . '/2025', + '3' => '2025-11-24T17:00:00.000Z', + '5' => rand(100000000, 400000000) + ]; + $order->save(); + + // Add bank meta to customer + $customer->meta()->create([ + 'meta_key' => 'bank', + 'meta_value' => $orderData['bank'] + ]); + } + } +} diff --git a/database/seeders/PermissionSeeder.php b/database/seeders/PermissionSeeder.php new file mode 100644 index 0000000..779b26d --- /dev/null +++ b/database/seeders/PermissionSeeder.php @@ -0,0 +1,68 @@ + $perm, 'guard_name' => 'web']); + } + + // Buat role admin + $admin = Role::firstOrCreate(['name' => 'admin']); + + // Assign semua permission yang sudah ada ke role admin + $admin->givePermissionTo(Permission::all()); + } +} diff --git a/database/seeders/PostSeeder.php b/database/seeders/PostSeeder.php index 9132da7..f6c0c41 100644 --- a/database/seeders/PostSeeder.php +++ b/database/seeders/PostSeeder.php @@ -2,16 +2,94 @@ namespace Database\Seeders; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; +use App\Models\Post; +use App\Models\User; +use App\Models\Category; use Illuminate\Database\Seeder; +use Illuminate\Support\Str; +use Faker\Factory as Faker; class PostSeeder extends Seeder { - /** - * Run the database seeds. - */ - public function run(): void - { - // + /** + * Run the database seeds. + */ + public function run(): void + { + $faker = Faker::create('id_ID'); // Indonesian locale + + // Get all users and categories + $users = User::all(); + $categories = Category::all(); + + // If no categories exist, create some basic ones + if ($categories->isEmpty()) { + $this->call(CategorySeeder::class); + $categories = Category::all(); + } + + // If no users exist, we can't create posts + if ($users->isEmpty()) { + $this->command->info('No users found. Please seed users first.'); + return; + } + + // Sample article titles and content in Indonesian + $articleTitles = [ + 'Tips Memulai Bisnis Online di Era Digital', + 'Panduan Lengkap Belajar Programming untuk Pemula', + 'Resep Masakan Indonesia yang Mudah dan Lezat', + 'Destinasi Wisata Tersembunyi di Indonesia', + 'Manfaat Olahraga Rutin untuk Kesehatan Mental', + 'Tren Fashion Terkini di Indonesia', + 'Cara Mengoptimalkan SEO Website untuk Bisnis', + 'Review Gadget Terbaru 2025', + 'Tips Mengelola Keuangan Pribadi dengan Baik', + 'Perkembangan Teknologi AI di Indonesia', + 'Wisata Kuliner Nusantara yang Wajib Dicoba', + 'Strategi Marketing Digital yang Efektif', + 'Pentingnya Literasi Digital di Era Modern', + 'Cara Memulai Investasi untuk Generasi Milenial', + 'Tren Arsitektur Modern di Indonesia', + ]; + + $contentTemplates = [ + 'Di era digital seperti sekarang ini, banyak peluang yang bisa dimanfaatkan untuk mengembangkan bisnis atau karier. Artikel ini akan membahas berbagai aspek penting yang perlu diketahui, mulai dari dasar-dasar hingga tips praktis yang bisa langsung diterapkan.', + 'Perkembangan teknologi yang pesat telah mengubah cara kita bekerja dan berinteraksi. Dalam konteks ini, penting untuk memahami berbagai tools dan strategi yang dapat membantu kita beradaptasi dengan perubahan zaman.', + 'Indonesia memiliki kekayaan budaya dan tradisi yang luar biasa. Hal ini tercermin dalam berbagai aspek kehidupan, mulai dari kuliner, seni, hingga cara hidup masyarakatnya. Mari kita eksplorasi lebih dalam tentang keunikan Indonesia.', + 'Kesehatan dan well-being menjadi prioritas utama di masa modern ini. Dengan gaya hidup yang semakin sibuk, penting untuk menemukan keseimbangan antara produktivitas dan kesehatan mental serta fisik.', + 'Dunia bisnis terus berkembang dengan munculnya berbagai inovasi dan teknologi baru. Para entrepreneur perlu memahami tren terkini dan strategi yang efektif untuk dapat bersaing di pasar yang kompetitif.', + ]; + + // Create 50 sample posts + for ($i = 0; $i < 50; $i++) { + $title = $faker->randomElement($articleTitles) . ' - ' . $faker->words(2, true); + $slug = Str::slug($title); + + // Make sure slug is unique + $originalSlug = $slug; + $counter = 1; + while (Post::where('slug', $slug)->exists()) { + $slug = $originalSlug . '-' . $counter; + $counter++; + } + + $content = $faker->randomElement($contentTemplates); + $content .= "\n\n" . $faker->paragraphs(4, true); + $content .= "\n\n## Kesimpulan\n\n" . $faker->paragraph(3); + + Post::create([ + 'title' => $title, + 'content' => $content, + 'slug' => $slug, + 'user_id' => $users->random()->id, + 'category_id' => $categories->random()->id, + 'featured_image' => $faker->optional(0.7)->imageUrl(800, 400, 'business'), + 'created_at' => $faker->dateTimeBetween('-6 months', 'now'), + 'updated_at' => now(), + ]); } + + $this->command->info('50 posts have been created successfully!'); + } } diff --git a/database/seeders/ProductSeeder.php b/database/seeders/ProductSeeder.php new file mode 100644 index 0000000..23cbdd5 --- /dev/null +++ b/database/seeders/ProductSeeder.php @@ -0,0 +1,349 @@ + [ + 'title' => 'Perjanjian Kredit', + 'meta' => [1, 2, 3, 5], + 'category' => 'perorangan' + ], + 'skmht' => [ + 'title' => 'SKMHT', + 'meta' => [2, 3, 8, 9, 10, 11, 12, 13, 14, 7], + 'category' => 'perorangan' + ], + 'apht' => [ + 'title' => 'APHT', + 'meta' => [2, 3, 8, 9, 10, 11, 12, 13, 14], + 'category' => 'perorangan' + ], + 'fidusia' => [ + 'title' => 'Fidusia', + 'meta' => [2, 3, 15, 16, 17, 18, 19, 20, 21, 7], + 'category' => 'perorangan' + ], + 'jual_beli' => [ + 'title' => 'Jual Beli', + 'meta' => [2, 3, 4, 31, 32, 33, 26, 34, 35, 36, 37, 38], + 'category' => 'perorangan', + ], + 'hibah' => [ + 'title' => 'Hibah', + 'meta' => [2, 3, 31, 32, 33, 26, 34, 35, 36, 37, 38], + 'category' => 'perorangan', + ], + 'turun_waris' => [ + 'title' => 'Turun Waris', + 'meta' => [23, 24, 25, 26, 27, 28, 29, 30], + 'category' => 'perorangan', + ], + 'pecah' => [ + 'title' => 'Pecah', + 'meta' => [1, 44], + 'category' => 'perorangan', + ], + 'pendirian_pt' => [ + 'title' => 'Pendirian PT', + 'meta' => [1, 2, 3, 42, 43, 21, 44, 45, 52], + 'category' => 'perorangan', + ], + 'pendirian_cv' => [ + 'title' => 'Pendirian CV', + 'meta' => [1, 2, 3, 46, 47, 21, 45, 52], + 'category' => 'perorangan', + ], + 'pendirian_yayasan' => [ + 'title' => 'Pendirian Yayasan', + 'meta' => [1, 2, 3, 48, 49, 50, 51, 21, 52], + 'category' => 'perorangan', + ], + 'pendirian_pt_perseorangan' => [ + 'title' => 'Pendirian PT Perseorangan', + 'meta' => [1, 2, 3, 42, 21, 45, 52], + 'category' => 'perorangan', + ], + 'perubahan_pt' => [ + 'title' => 'Perubahan PT', + 'meta' => [1, 2, 3, 42, 43, 21, 44, 45, 52], + 'category' => 'perorangan', + ], + 'perubahan_cv' => [ + 'title' => 'Perubahan CV', + 'meta' => [1, 2, 3, 46, 47, 21, 45, 52], + 'category' => 'perorangan', + ], + 'perubahan_yayasan' => [ + 'title' => 'Perubahan Yayasan', + 'meta' => [1, 2, 3, 48, 49, 50, 51, 21, 52], + 'category' => 'perorangan', + ], + 'perubahan_pt_perseorangan' => [ + 'title' => 'Perubahan PT Perseorangan', + 'meta' => [1, 2, 3, 42, 21, 45, 52], + 'category' => 'perorangan', + ], + 'bank_a' => [ + 'title' => 'Bank A', + 'meta' => [], + 'category' => 'bank', + ], + 'bank_b' => [ + 'title' => 'Bank B', + 'meta' => [], + 'category' => 'bank', + ], + 'bank_c' => [ + 'title' => 'Bank C', + 'meta' => [], + 'category' => 'bank', + ], + 'bank_d' => [ + 'title' => 'Bank D', + 'meta' => [], + 'category' => 'bank', + ], + 'bank_e' => [ + 'title' => 'Bank E', + 'meta' => [], + 'category' => 'bank', + ], + ]; + $metas = [ + 1 => [ + 'title' => 'Judul Akta', + 'type' => 'text' + ], + 2 => [ + 'title' => 'Nomor Akta', + 'type' => 'text' + ], + 3 => [ + 'title' => 'Tanggal Akta', + 'type' => 'date' + ], + 4 => [ + 'title' => 'Harga Real', + 'type' => 'currency' + ], + 5 => [ + 'title' => 'Jumlah Pinjaman', + 'type' => 'currency' + ], + 7 => [ + 'title' => 'Tanggal Habis SKMHT', + 'type' => 'date' + ], + 8 => [ + 'title' => 'Nomor Agunan', + 'type' => 'text' + ], + 9 => [ + 'title' => 'NIB', + 'type' => 'text' + ], + 10 => [ + 'title' => 'NOP', + 'type' => 'text' + ], + 11 => [ + 'title' => 'Nama Pemilik Agunan', + 'type' => 'text' + ], + 12 => [ + 'title' => 'Kode Sertifikat', + 'type' => 'text' + ], + 13 => [ + 'title' => 'Nomor Seri', + 'type' => 'text' + ], + 14 => [ + 'title' => 'Jumlah Pengikatan', + 'type' => 'number' + ], + 15 => [ + 'title' => 'Jenis Agunan', + 'type' => 'text' + ], + 16 => [ + 'title' => 'Keterangan Objek', + 'type' => 'text' + ], + 17 => [ + 'title' => 'Bukti Kepemilikan Objek', + 'type' => 'text' + ], + 18 => [ + 'title' => 'Nilai Objek', + 'type' => 'number' + ], + 19 => [ + 'title' => 'Pemilik Agunan', + 'type' => 'text' + ], + 20 => [ + 'title' => 'Nilai Penjaminan', + 'type' => 'currency' + ], + 21 => [ + 'title' => 'NPWP', + 'type' => 'text' + ], + 23 => [ + 'title' => 'Pewaris', + 'type' => 'text' + ], + 24 => [ + 'title' => 'Ahli Waris', + 'type' => 'text' + ], + 25 => [ + 'title' => 'Penerima Waris', + 'type' => 'text' + ], + 26 => [ + 'title' => 'Lokasi Tanah', + 'type' => 'text' + ], + 27 => [ + 'title' => 'Nilai Pajak', + 'type' => 'currency' + ], + 28 => [ + 'title' => 'Berkas Kembali', + 'type' => 'text' + ], + 29 => [ + 'title' => 'Masuk BPN', + 'type' => 'text' + ], + 30 => [ + 'title' => 'Tanggal Akad', + 'type' => 'date' + ], + 31 => [ + 'title' => 'Nama Penjual', + 'type' => 'text' + ], + 32 => [ + 'title' => 'Nama Pembeli', + 'type' => 'text' + ], + 33 => [ + 'title' => 'Sertifikat', + 'type' => 'text' + ], + 34 => [ + 'title' => 'Nilai Jual Beli', + 'type' => 'currency' + ], + 35 => [ + 'title' => 'Nilai SSB', + 'type' => 'currency' + ], + 36 => [ + 'title' => 'Nilai SSP', + 'type' => 'currency' + ], + 37 => [ + 'title' => 'Keterangan PPJB', + 'type' => 'text' + ], + 38 => [ + 'title' => 'Keterangan Kuasa Menjual', + 'type' => 'text' + ], + 42 => [ + 'title' => 'Nama Direktur', + 'type' => 'text' + ], + 43 => [ + 'title' => 'Nama Komisaris', + 'type' => 'text' + ], + 44 => [ + 'title' => 'Nama Pemilik Manfaat', + 'type' => 'text' + ], + 45 => [ + 'title' => 'Kedudukan PT', + 'type' => 'text' + ], + 46 => [ + 'title' => 'Nama Persero Aktif', + 'type' => 'text' + ], + 47 => [ + 'title' => 'Nama Persero Pasif', + 'type' => 'text' + ], + 48 => [ + 'title' => 'Nama Pembina', + 'type' => 'text' + ], + 49 => [ + 'title' => 'Nama Ketua', + 'type' => 'text' + ], + 50 => [ + 'title' => 'Nama Wakil', + 'type' => 'text' + ], + 51 => [ + 'title' => 'Nama Bendahara', + 'type' => 'text' + ], + 52 => [ + 'title' => 'Tanggal Upload', + 'type' => 'date' + ] + ]; + + // Membuat 5 customer + + foreach ($metas as $id => $meta) { + Meta::factory()->create([ + 'id' => $id, + 'name' => $meta['title'], + 'type' => $meta['type'], + ]); + } + + // Insert meta ke tabel 'products' dan 'meta_product' + foreach ($products as $slug => $product) { + // Insert data ke tabel 'products' + $productId = Product::factory()->create([ + 'name' => $product['title'], + 'description' => '-', + 'category' => $product['category'] + ]); + + // Insert data ke tabel 'meta_product' + foreach ($product['meta'] as $metaId) { + // Pastikan meta_id ada di tabel 'metas' + if (Meta::where('id', $metaId)->exists()) { + MetaProduct::factory()->create([ + 'meta_id' => $metaId, + 'product_id' => $productId + ]); + } else { + // Jika meta_id tidak ditemukan, tampilkan pesan error atau log + echo "Meta ID $metaId tidak ditemukan di tabel 'metas'. Produk: {$product['title']}\n"; + } + } + } + } +} diff --git a/database/seeders/README_NotificationSeeder.md b/database/seeders/README_NotificationSeeder.md new file mode 100644 index 0000000..7616a77 --- /dev/null +++ b/database/seeders/README_NotificationSeeder.md @@ -0,0 +1,97 @@ +# Notification Seeder Documentation + +## File yang Dibuat + +### 1. NotificationSeeder.php + +- **Lokasi**: `database/seeders/NotificationSeeder.php` +- **Fungsi**: Membuat data dummy notifikasi untuk testing dan development + +## Fitur NotificationSeeder + +### Jenis Notifikasi yang Dibuat: + +1. **New Order Notification** (`App\Notifications\NewOrderNotification`) + + - Notifikasi order baru masuk + - Data: order_id, order_no, customer_name, action_url + +2. **Pending Jobdesk** (`App\Notifications\PendingJobdesk`) + + - Notifikasi tugas yang pending/mendekati deadline + - Data: jobdesk_id, order_id, deadline, action_url + +3. **System Notification** (`App\Notifications\SystemNotification`) + - Welcome message + - Backup database berhasil + - Update sistem tersedia + - Maintenance terjadwal + - Pelatihan sistem + +### Karakteristik Data: + +- **Jumlah**: 3-5 notifikasi per user + 2 broadcast notification untuk semua user +- **Status Read**: 20% sudah dibaca, 80% belum dibaca (realistic scenario) +- **Tanggal**: Random dalam 30 hari terakhir +- **UUID**: Menggunakan UUID untuk primary key +- **JSON Data**: Data notifikasi disimpan dalam format JSON + +### Struktur Data Notifikasi: + +```json +{ + "title": "Judul Notifikasi", + "message": "Pesan notifikasi", + "action_url": "/path/to/action", + "icon": "icon-name", + "type": "notification_type", + "order_id": 123, + "customer_name": "Nama Customer" +} +``` + +## Cara Penggunaan + +### Jalankan Seeder Individual: + +```bash +php artisan db:seed --class=NotificationSeeder +``` + +### Jalankan Semua Seeder (termasuk NotificationSeeder): + +```bash +php artisan db:seed +``` + +### Reset dan Seed Ulang: + +```bash +php artisan migrate:fresh --seed +``` + +## Statistik Hasil Seeder + +Berdasarkan hasil terakhir: + +- **Total notifikasi**: 29 +- **Belum dibaca**: 24 (83%) +- **Sudah dibaca**: 5 (17%) + +## Dependencies + +Seeder ini membutuhkan: + +- User model dengan data user +- Order model dengan data order dan customer +- Tabel notifications (migrasi sudah ada) + +## Integration dengan DatabaseSeeder + +NotificationSeeder sudah ditambahkan ke `DatabaseSeeder.php` dan akan dijalankan setelah: + +- Users dibuat +- Customers dan Orders dibuat +- Semua data master sudah tersedia + +Hal ini memastikan referensi data (user_id, order_id) valid saat membuat notifikasi. diff --git a/database/seeders/RoleSeeder.php b/database/seeders/RoleSeeder.php new file mode 100644 index 0000000..b70a88c --- /dev/null +++ b/database/seeders/RoleSeeder.php @@ -0,0 +1,29 @@ + $role, + 'guard_name' => 'web', + ]); + } + } +} diff --git a/database/seeders/SettingSeeder.php b/database/seeders/SettingSeeder.php new file mode 100644 index 0000000..c143ae0 --- /dev/null +++ b/database/seeders/SettingSeeder.php @@ -0,0 +1,91 @@ +Pemberitahuan Order Layanan Notaris Baru
+
+ Tanggal: [tanggal_order]
+
+ Detail Order:
+ ======================================
+ Nama Klien: [nama_klien]
+ Nomor Telepon: [no_telp]
+ Jenis Layanan Notaris: [product]
+ ======================================
+
+ Harap segera follow up order ini ke staff terkait.
+
+ Terima kasih.
+ [tim_manajemen] + '; + $template_project = ' + Pemberitahuan Penugasan Proyek
+
+ Tanggal: [tanggal_penugasan]
+
+ Detail Proyek:
+ ======================================
+ Nama Proyek: [nama_proyek]
+ Deskripsi: [description_proyek]
+ Batas Waktu: [batas_waktu]
+ Penanggung Jawab: [nama_superadmin]
+ ======================================
+
+ Harap segera mulai bekerja pada proyek ini dan laporkan perkembangan secara berkala.
+
+ Terima kasih.
+ [tim_manajemen] + '; + $template_followup = ' + Follow-Up Proyek
+
+ Tanggal: [tanggal_followup]
+
+ Detail Proyek:
+ ======================================
+ Nama Proyek: [nama_proyek]
+ Penanggung Jawab: [nama_karyawan]
+ Batas Waktu: [batas_waktu]
+ Status Terakhir: [status_terakhir]
+ ======================================
+
+ Harap berikan update mengenai perkembangan proyek ini dan jika ada kendala, silakan laporkan segera.
+
+ Terima kasih.
+ [tim_manajemen] + '; + $banks = [ + ['name' => 'Bank A'], + ['name' => 'Bank B'], + ['name' => 'Bank C'], + ['name' => 'Bank D'], + ]; + $templates = [ + ['setting_key' => 'new_order', 'setting_value' => $template_order], + ['setting_key' => 'project_assignment', 'setting_value' => $template_project], + ['setting_key' => 'followup_project', 'setting_value' => $template_followup], + + ['setting_key' => 'app_name', 'setting_value' => 'APP'], + ['setting_key' => 'app_code', 'setting_value' => 'AN'], + ['setting_key' => 'app_description', 'setting_value' => 'Asisten Notaris Online'], + ['setting_key' => 'address', 'setting_value' => 'Jl. Kebon Jeruk, Jakarta Timur'], + ['setting_key' => 'pdf_sample', 'setting_value' => 'path/to/sample.pdf'], + ['setting_key' => 'email', 'setting_value' => 'admin@asistennotaris.com'], + ['setting_key' => 'banks', 'setting_value' => json_encode($banks)] + ]; + + Setting::insert($templates); // Batch insert + } +} diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php new file mode 100644 index 0000000..0e79578 --- /dev/null +++ b/database/seeders/UserSeeder.php @@ -0,0 +1,71 @@ +create([ + 'name' => 'Test Admin', + 'email' => 'user1@example.com', + 'is_admin' => true, + 'avatar' => null, + 'phone' => '08123456789', + 'address' => 'Jl. Kebon Jeruk No. 1', + 'password' => Hash::make('password'), + ]); + + $manager = User::factory()->create([ + 'name' => 'Test Manager', + 'email' => 'user2@example.com', + 'is_admin' => false, + 'avatar' => null, + 'phone' => '08123456789', + 'address' => 'Jl. Kebon Jeruk No. 1', + 'password' => Hash::make('password'), + ]); + + $finance = User::factory()->create([ + 'name' => 'Test Keuangan', + 'email' => 'user3@example.com', + 'is_admin' => false, + 'avatar' => null, + 'phone' => '08123456789', + 'address' => 'Jl. Kebon Jeruk No. 1', + 'password' => Hash::make('password'), + ]); + + $staff1 = User::factory()->create([ + 'name' => 'Test Staff', + 'email' => 'user4@example.com', + 'is_admin' => false, + 'avatar' => null, + 'phone' => '08123456789', + 'address' => 'Jl. Kebon Jeruk No. 1', + 'password' => Hash::make('password'), + ]); + + $staff2 = User::factory()->create([ + 'name' => 'Test Staff 2', + 'email' => 'user5@example.com', + 'is_admin' => false, + 'avatar' => null, + 'phone' => '08123456789', + 'address' => 'Jl. Kebon Jeruk No. 1', + 'password' => Hash::make('password'), + ]); + + $admin->assignRole('admin'); + $manager->assignRole('manager'); + $finance->assignRole('keuangan'); + $staff1->assignRole('staff'); + $staff2->assignRole('staff'); + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a4b203f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,580 @@ +{ + "name": "larapi", + "version": "1.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "larapi", + "version": "1.0.1", + "dependencies": { + "archiver": "^5.3.1", + "fs": "0.0.1-security", + "tailwindcss": "^4.1.13" + } + }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "license": "MIT", + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/fs": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", + "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==", + "license": "ISC" + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "license": "MIT" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.13.tgz", + "integrity": "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==", + "license": "MIT" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "license": "MIT", + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + } + } +} diff --git a/package.json b/package.json index 5d67800..504d803 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,14 @@ { - "private": true, - "type": "module", + "name": "larapi", + "version": "1.0.1", + "description": "Backend Aplikasi Notaris", + "main": "index.js", "scripts": { - "dev": "vite", - "build": "vite build" + "build": "node src/script.js" }, - "devDependencies": { - "axios": "^1.7.4", - "laravel-vite-plugin": "^1.0", - "vite": "^5.0" + "dependencies": { + "archiver": "^5.3.1", + "fs": "0.0.1-security", + "tailwindcss": "^4.1.13" } } diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..49c0612 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/public/assets/bg.jpeg b/public/assets/bg.jpeg new file mode 100644 index 0000000..a16c8be Binary files /dev/null and b/public/assets/bg.jpeg differ diff --git a/resources/css/app.css b/resources/css/app.css deleted file mode 100644 index e69de29..0000000 diff --git a/resources/js/app.js b/resources/js/app.js deleted file mode 100644 index e59d6a0..0000000 --- a/resources/js/app.js +++ /dev/null @@ -1 +0,0 @@ -import './bootstrap'; diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js deleted file mode 100644 index 5f1390b..0000000 --- a/resources/js/bootstrap.js +++ /dev/null @@ -1,4 +0,0 @@ -import axios from 'axios'; -window.axios = axios; - -window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; diff --git a/resources/views/.gitkeep b/resources/views/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/resources/views/.gitkeep @@ -0,0 +1 @@ + diff --git a/resources/views/emails/new_order.blade.php b/resources/views/emails/new_order.blade.php new file mode 100644 index 0000000..d84814b --- /dev/null +++ b/resources/views/emails/new_order.blade.php @@ -0,0 +1,16 @@ + + + + + + + Notifikasi Pesanan Baru + + + + {!! $messageContent !!} +
+

Terima kasih!

+ + + \ No newline at end of file diff --git a/resources/views/emails/pending_jobdesk.blade.php b/resources/views/emails/pending_jobdesk.blade.php new file mode 100644 index 0000000..c16f13b --- /dev/null +++ b/resources/views/emails/pending_jobdesk.blade.php @@ -0,0 +1,16 @@ + + + + + + + Pemberitahuan: Jobdesk Belum Diambil + + + + {!! $messageContent !!} +
+

Terima kasih!

+ + + \ No newline at end of file diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php deleted file mode 100644 index a9898e3..0000000 --- a/resources/views/welcome.blade.php +++ /dev/null @@ -1,172 +0,0 @@ - - - - - - - Laravel - - - - - - - - - - - - diff --git a/routes/api.php b/routes/api.php index d6d7e4b..ed92279 100644 --- a/routes/api.php +++ b/routes/api.php @@ -2,16 +2,96 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; -use App\Http\Controllers\PostController; -use App\Http\Controllers\AuthController; -use Illuminate\Container\Attributes\Auth; +use App\Http\Controllers\{ + KaryawanController, + OrderController, + JobdeskController, + CustomerController, + DashboardController, + SettingController, + UserController, + ProductController, + MetaController, + SendNotificationController, + NotificationController, + PermissionController, + SettingBankController, + SettingLoginController, + SettingFaviconController, + PostController, + CategoryController, + RoleController, + Auth\ProfileController +}; +use Spatie\Permission\Models\Permission; -Route::get('/user', function (Request $request) { - return $request->user(); -})->middleware('auth:sanctum'); +// Routes tanpa middleware +Route::get('/settings/background', [SettingLoginController::class, 'index']); +Route::get('/settings/favicon', [SettingFaviconController::class, 'index']); -Route::apiResource('posts', PostController::class); +// Routes dengan middleware 'auth:sanctum' +Route::middleware(['auth:sanctum'])->group(function () { + Route::get('profile', fn(Request $request) => $request->user()); + Route::get('user', [UserController::class, 'index']); + Route::put('profile', [ProfileController::class, 'update']); + Route::get('home', [DashboardController::class, 'index']); + Route::post('jobdesk-reminder', [SendNotificationController::class, 'sendJobdeskReminder']); -Route::post('/register', [AuthController::class, 'register']); -Route::post('/login', [AuthController::class, 'login']); -Route::post('/logout', [AuthController::class, 'logout'])->middleware('auth:sanctum'); + // Notifications + Route::controller(NotificationController::class)->group(function () { + Route::get('notifications', 'index'); + Route::put('notifications/{id}', 'update'); + Route::put('notifications/read/{id}', 'read'); + Route::put('notifications/read-all', 'readAll'); + Route::get('notifications/unread', 'unread'); + }); + + // Permissions + Route::get('/permissions', [PermissionController::class, 'index']); + Route::post('/user/{user}/permissions', [PermissionController::class, 'update']); + + Route::get('/capabilities', function () { + return Permission::select('id', 'name')->get(); + }); + + // Settings + Route::prefix('settings')->group(function () { + Route::controller(SettingLoginController::class)->group(function () { + Route::post('/background', 'store'); + Route::delete('/background', 'destroy'); + }); + + Route::controller(SettingFaviconController::class)->group(function () { + Route::post('/favicon', 'store'); + Route::delete('/favicon', 'destroy'); + }); + + Route::controller(SettingBankController::class)->group(function () { + Route::get('/banks', 'index'); + Route::post('/banks', 'store'); + Route::delete('/banks', 'destroy'); + }); + }); + + Route::post('customers/{customer}/meta', [CustomerController::class, 'storeMeta']); + + // Order stats endpoint + Route::get('orders/stats', [OrderController::class, 'stats']); + + // Jobdesk stats endpoint + Route::get('jobdesks/stats', [JobdeskController::class, 'stats']); + + // API Resources + Route::apiResources([ + 'posts' => PostController::class, + 'categories' => CategoryController::class, + 'karyawans' => KaryawanController::class, + 'orders' => OrderController::class, + 'jobdesks' => JobdeskController::class, + 'customers' => CustomerController::class, + 'settings' => SettingController::class, + 'products' => ProductController::class, + 'metas' => MetaController::class, + 'roles' => RoleController::class + ]); +}); diff --git a/routes/auth.php b/routes/auth.php new file mode 100644 index 0000000..4170a7e --- /dev/null +++ b/routes/auth.php @@ -0,0 +1,37 @@ +middleware('guest') + ->name('register'); + +Route::post('/login', [AuthenticatedSessionController::class, 'store']) + ->middleware('guest') + ->name('login'); + +Route::post('/forgot-password', [PasswordResetLinkController::class, 'store']) + ->middleware('guest') + ->name('password.email'); + +Route::post('/reset-password', [NewPasswordController::class, 'store']) + ->middleware('guest') + ->name('password.store'); + +Route::get('/verify-email/{id}/{hash}', VerifyEmailController::class) + ->middleware(['auth', 'signed', 'throttle:6,1']) + ->name('verification.verify'); + +Route::post('/email/verification-notification', [EmailVerificationSendNotificationController::class, 'store']) + ->middleware(['auth', 'throttle:6,1']) + ->name('verification.send'); + +Route::post('/logout', [AuthenticatedSessionController::class, 'destroy']) + ->middleware('auth') + ->name('logout'); diff --git a/routes/web.php b/routes/web.php index 7c4d255..cdd4027 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,3 +1,9 @@ app()->version()]; +}); + +require __DIR__.'/auth.php'; diff --git a/src/script.js b/src/script.js new file mode 100644 index 0000000..9620de2 --- /dev/null +++ b/src/script.js @@ -0,0 +1,86 @@ +const fs = require("fs"); +const path = require("path"); +const archiver = require("archiver"); + +// Buat folder `dist` jika belum ada +const distDir = path.join(__dirname, "../dist"); +if (!fs.existsSync(distDir)) { + fs.mkdirSync(distDir); +} + +// Nama file ZIP yang akan dihasilkan +const output = fs.createWriteStream(path.join(distDir, "laravel-app.zip")); +const archive = archiver("zip", { + zlib: { level: 9 }, // Tingkat kompresi maksimal +}); + +// Event listener untuk ketika file ZIP selesai dibuat +output.on("close", function () { + console.log( + "File ZIP berhasil dibuat: " + archive.pointer() + " total bytes" + ); + console.log( + `File ZIP disimpan di: ${path.join(distDir, "laravel-app.zip")}` + ); +}); + +// Event listener untuk menangani error +archive.on("error", function (err) { + throw err; +}); + +// Mulai proses kompresi +archive.pipe(output); + +// Direktori root Laravel +const laravelDir = path.join(__dirname, ".."); // Sesuaikan dengan struktur direktori Anda + +// Daftar file dan folder yang akan diabaikan +const ignoreList = [ + ".vscode", + ".git", + ".env", + "package.json", + "package-lock.json", + "node_modules", + "storage", + "vendor", + "dist", + "laravel-app.zip", + ".github", + "composer.lock", + "composer.json", +]; + +// Fungsi untuk menambahkan file dan folder ke ZIP +function addDirectoryToArchive(dir, prefix = "") { + const files = fs.readdirSync(dir); + + files.forEach((file) => { + const filePath = path.join(dir, file); + const relativePath = prefix ? `${prefix}/${file}` : file; + + // Cek apakah file/folder harus diabaikan + if (ignoreList.includes(file)) { + console.log(`Mengabaikan: ${relativePath}`); + return; + } + + const stat = fs.statSync(filePath); + + if (stat.isDirectory()) { + // Tambahkan direktori ke ZIP + addDirectoryToArchive(filePath, relativePath); + } else { + // Tambahkan file ke ZIP + archive.file(filePath, { name: relativePath }); + console.log(`Menambahkan: ${relativePath}`); + } + }); +} + +// Mulai proses menambahkan file dan folder ke ZIP +addDirectoryToArchive(laravelDir); + +// Finalisasi proses kompresi +archive.finalize(); diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..ce0c57f --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,20 @@ +import defaultTheme from 'tailwindcss/defaultTheme'; + +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php', + './storage/framework/views/*.php', + './resources/**/*.blade.php', + './resources/**/*.js', + './resources/**/*.vue', + ], + theme: { + extend: { + fontFamily: { + sans: ['Figtree', ...defaultTheme.fontFamily.sans], + }, + }, + }, + plugins: [], +}; diff --git a/tests/Feature/Auth/AuthenticationTest.php b/tests/Feature/Auth/AuthenticationTest.php new file mode 100644 index 0000000..2236551 --- /dev/null +++ b/tests/Feature/Auth/AuthenticationTest.php @@ -0,0 +1,47 @@ +create(); + + $response = $this->post('/login', [ + 'email' => $user->email, + 'password' => 'password', + ]); + + $this->assertAuthenticated(); + $response->assertNoContent(); + } + + public function test_users_can_not_authenticate_with_invalid_password(): void + { + $user = User::factory()->create(); + + $this->post('/login', [ + 'email' => $user->email, + 'password' => 'wrong-password', + ]); + + $this->assertGuest(); + } + + public function test_users_can_logout(): void + { + $user = User::factory()->create(); + + $response = $this->actingAs($user)->post('/logout'); + + $this->assertGuest(); + $response->assertNoContent(); + } +} diff --git a/tests/Feature/Auth/EmailVerificationTest.php b/tests/Feature/Auth/EmailVerificationTest.php new file mode 100644 index 0000000..2db7299 --- /dev/null +++ b/tests/Feature/Auth/EmailVerificationTest.php @@ -0,0 +1,49 @@ +unverified()->create(); + + Event::fake(); + + $verificationUrl = URL::temporarySignedRoute( + 'verification.verify', + now()->addMinutes(60), + ['id' => $user->id, 'hash' => sha1($user->email)] + ); + + $response = $this->actingAs($user)->get($verificationUrl); + + Event::assertDispatched(Verified::class); + $this->assertTrue($user->fresh()->hasVerifiedEmail()); + $response->assertRedirect(config('app.frontend_url').'/dashboard?verified=1'); + } + + public function test_email_is_not_verified_with_invalid_hash(): void + { + $user = User::factory()->unverified()->create(); + + $verificationUrl = URL::temporarySignedRoute( + 'verification.verify', + now()->addMinutes(60), + ['id' => $user->id, 'hash' => sha1('wrong-email')] + ); + + $this->actingAs($user)->get($verificationUrl); + + $this->assertFalse($user->fresh()->hasVerifiedEmail()); + } +} diff --git a/tests/Feature/Auth/PasswordResetTest.php b/tests/Feature/Auth/PasswordResetTest.php new file mode 100644 index 0000000..9b652bc --- /dev/null +++ b/tests/Feature/Auth/PasswordResetTest.php @@ -0,0 +1,49 @@ +create(); + + $this->post('/forgot-password', ['email' => $user->email]); + + Notification::assertSentTo($user, ResetPassword::class); + } + + public function test_password_can_be_reset_with_valid_token(): void + { + Notification::fake(); + + $user = User::factory()->create(); + + $this->post('/forgot-password', ['email' => $user->email]); + + Notification::assertSentTo($user, ResetPassword::class, function (object $notification) use ($user) { + $response = $this->post('/reset-password', [ + 'token' => $notification->token, + 'email' => $user->email, + 'password' => 'password', + 'password_confirmation' => 'password', + ]); + + $response + ->assertSessionHasNoErrors() + ->assertStatus(200); + + return true; + }); + } +} diff --git a/tests/Feature/Auth/RegistrationTest.php b/tests/Feature/Auth/RegistrationTest.php new file mode 100644 index 0000000..b48e150 --- /dev/null +++ b/tests/Feature/Auth/RegistrationTest.php @@ -0,0 +1,24 @@ +post('/register', [ + 'name' => 'Test User', + 'email' => 'test@example.com', + 'password' => 'password', + 'password_confirmation' => 'password', + ]); + + $this->assertAuthenticated(); + $response->assertNoContent(); + } +} diff --git a/vite.config.js b/vite.config.js deleted file mode 100644 index 421b569..0000000 --- a/vite.config.js +++ /dev/null @@ -1,11 +0,0 @@ -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, - }), - ], -});