Compare commits

...

27 Commits

Author SHA1 Message Date
kusowl
b2132f8c02 refactor: chat time, grouping and toast
- show message time
- group messages by date
- show toast when message sent is failed
2026-02-18 11:38:03 +05:30
kusowl
44b13a5ebf fix: disply chats in sidebar, minor ui issues 2026-02-17 11:49:49 +05:30
kusowl
964d7a8936 feature: user can chat to each other 2026-02-16 12:30:57 +05:30
kusowl
12726c93b5 chore: formatting via pint 2026-02-16 12:30:07 +05:30
kusowl
82d61e757f fix: schema so that created_at uses current timestamp 2026-02-16 12:28:06 +05:30
kusowl
59b74a6905 wip: live chat
1.1 User can request for message from explore page
1.2User cannot message broker if not following
1.3 prepare action which handles creating inbox between user and broker if not exists
2026-02-13 12:03:23 +05:30
kusowl
a853b58f48 wip live chat
- add individual user chat view
- add routes
- wip: action to check and get inbox
2026-02-12 12:37:59 +05:30
kusowl
587893511c wip: live support chat
- add migration
- setup models
- generate phpdoc via helper
2026-02-11 12:27:58 +05:30
kusowl
976ea01c89 wip: add laravel reverb and echo
fix env
install the dependecies
2026-02-10 19:15:50 +05:30
kusowl
2f566cc4d8 feature(Live Support): add chat ui
- add chat ui for live support
- fix misc bugs
2026-02-09 19:07:16 +05:30
kusowl
0d0818baf3 feature(send push notification): send push notification to broker's followers 2026-02-09 12:15:11 +05:30
kusowl
82715973dc WIP: push notification when admin approves a deal 2026-02-06 19:01:53 +05:30
kusowl
5cae04884a feature(users can follow a broker)
- add schema and endpoints to make follows relationship with customer
and broker

- show and update states of follow button on ui
2026-02-05 18:19:52 +05:30
kusowl
a06fac4fef feature(users can comment on deals) 2026-02-04 16:50:20 +05:30
kusowl
aa7e2f245f misx bugfixes:
- fix users cannot reset password due session expiration
- add a new mail when users contacts via Contact form
- add list page for all deals in broker dashboard
- fixed links in home page
2026-02-03 16:06:26 +05:30
kusowl
2fecd52d7e feature(send notification for report status): send mail to broker and user when admin resolves/rejects the reports 2026-02-02 19:33:25 +05:30
kusowl
d7c06c38a6 feature(deals by category pie chart):
- add a pie chart that shows deals by category
- change UI to make it more clear
2026-02-02 17:22:01 +05:30
kusowl
1edfd7b9d4 feature(active users graph)
- users active is recorded when users logges in
- add active broker and active customer multi axis line chart
- add filter option of 30 days and 7 days
2026-02-02 16:25:12 +05:30
kusowl
aa8ad6b84b feature(resend otp): users can resend OTP in OTP verification page 2026-01-30 13:37:02 +05:30
kusowl
d03aa31f30 chore: minor UI changes in password reset pages 2026-01-29 18:57:09 +05:30
kusowl
0b631f6049 feature(password reset): send otp via SMS
- use twilio to send otp via test sms
2026-01-29 18:50:53 +05:30
kusowl
039f8f5568 feature(password reset): guests can reset theri passwords via email 2026-01-29 17:32:02 +05:30
kusowl
38d429e5d5 bugfix: improvemnets in UI, misc bugfixes
add tooltip to sidebar buttons
remove profile for admin
fix mobile menu not opening in home page
fix deal image input modal size in mobile view
make image scrollable in input modal
fix explore page filters are not clickable when recent search is maxed
out
change UI for the recent searches
add seeder for categories
improve deal card ui in broker dashboard
2026-01-29 14:02:39 +05:30
kusowl
450c8b4dce chore: add ide helper code
- add ide-helper doc block generator for ide intelisense
2026-01-29 10:37:40 +05:30
kusowl
a8be44553e Merge branch 'feature/admin-panel' 2026-01-29 09:48:25 +05:30
kusowl
2590ebc0ab Merge feature/admin-panel into main 2026-01-28 17:01:34 +05:30
kusowl
298d73791f feature(manage-reported-deals): admin can approve or reject reports and remove content 2026-01-28 16:42:59 +05:30
175 changed files with 37432 additions and 505 deletions

View File

@ -63,3 +63,28 @@ AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}" VITE_APP_NAME="${APP_NAME}"
TWILIO_SID=
TWILIO_AUTH_TOKEN=
TWILIO_NUMBER=
# OTP valid time in minutes
OTP_LIFESPAN=10
VAPID_PUBLIC_KEY=
VAPID_PRIVATE_KEY=
# Same as the VAPID_PUBLIC_KEY
VITE_VAPID_PUBLIC_KEY="${VAPID_PUBLIC_KEY}"
REVERB_APP_ID=
REVERB_APP_KEY=
REVERB_APP_SECRET=
REVERB_HOST=
REVERB_PORT=
REVERB_SCHEME=
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"

2371
.phpstorm.meta.php Normal file

File diff suppressed because it is too large Load Diff

View File

@ -79,11 +79,22 @@ ## ⚙️ Installation
```bash ```bash
git clone https://git.sentientgeeks.us/joydip.manna/dealhub.git git clone https://git.sentientgeeks.us/joydip.manna/dealhub.git
cd dealhub cd dealhub
# Install dependencies
composer install composer install
npm install npm install
# Set VAPID for webpush
php artisan webpush:vapid
# Install Reverb
php artisan install:broadcasting
npm run dev npm run dev
php artisan migrate php artisan migrate
php artisan serve php artisan serve
# Start Reverb Server ( Another terminal )
php artisan reverb:start
``` ```
--- ---

4
TODO.md Normal file
View File

@ -0,0 +1,4 @@
- [ ] Live Support.
- [ ] Add an Audit Log table to save all actions.
- [ ] Link foreign keys properly with related tables in the database.
- [ ] Add debouncing to the search feature and create proper database indexes to improve search performance.

28340
_ide_helper.php Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
<?php
namespace App\Actions;
use App\Models\Inbox;
use App\Models\User;
use DB;
final readonly class CreateOrGetInboxAction
{
/**
* @throws \Throwable
*/
public function execute(User $recipient, User $sender): Inbox
{
$existingInbox = Inbox::whereHas('users', fn ($q) => $q->where('users.id', $sender->id))
->whereHas('users', fn ($q) => $q->where('users.id', $recipient->id))
->first();
if ($existingInbox) {
return $existingInbox;
}
return DB::transaction(function () use ($sender, $recipient) {
$inbox = Inbox::create();
$inbox->users()->attach([$sender->id, $recipient->id]);
return $inbox;
}, 2);
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Actions\PasswordReset;
use App\Exceptions\UserNotFoundException;
use App\Models\User;
use App\Services\OTPService;
final readonly class ResendOTPAction
{
public function __construct(
private OTPService $otpService,
private SendOTPToUserAction $otpToUserAction
) {}
/**
* @throws \Throwable
*/
public function execute(): void
{
$user = \Session::get('otp_user_id') ? User::find(\Session::get('otp_user_id')) : null;
throw_if(! $user, new UserNotFoundException('User not found'));
$otp = $this->otpService->generate($user);
$this->otpToUserAction->execute(['user' => $user, 'otp' => $otp]);
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Actions\PasswordReset;
use App\Exceptions\UserNotFoundException;
use App\Models\User;
use App\Services\OTPService;
final readonly class SendOTPAction
{
public function __construct(
private OTPService $otpService,
private SendOTPToUserAction $otpToUserAction
) {}
/**
* @throws \Throwable
*/
public function execute(array $data): void
{
$user = User::where('email', $data['email'])->first();
throw_if(! $user, new UserNotFoundException('User not found'));
\Session::put('otp_user_id', $user->id);
$otp = $this->otpService->generate($user);
$this->otpToUserAction->execute(['user' => $user, 'otp' => $otp]);
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Actions\PasswordReset;
use App\Actions\SendPasswordResetMailAction;
use App\Services\TwilioService;
use Twilio\Exceptions\TwilioException;
final readonly class SendOTPToUserAction
{
public function __construct(
private SendPasswordResetMailAction $mailAction,
private TwilioService $twilioService
) {}
/**
* @param array<string, mixed> $data
*
* @throws \Throwable
*/
public function execute(array $data): void
{
['user' => $user,'otp' => $otp] = $data;
$this->mailAction->execute($user->email, $otp);
if ($user?->type->phone !== null) {
try {
$this->twilioService->sendSms($user->type->phone, "Your OTP is $otp");
} catch (TwilioException $e) {
\Log::error('SMS send failed', [$e->getMessage()]);
}
}
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Actions\PasswordReset;
use App\Exceptions\UserNotFoundException;
use App\Models\User;
use App\Services\OTPService;
final readonly class VerifyOTPAction
{
public function __construct(
private OTPService $otpService,
) {}
/**
* @throws \Throwable
*/
public function execute(array $data): bool
{
$user = \Session::get('otp_user_id') ? User::find(\Session::get('otp_user_id')) : null;
throw_if(! $user, new UserNotFoundException('User not found'));
return $this->otpService->verify($user, $data['otp']);
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Actions;
use App\Models\PageVisit;
use App\Models\User;
final readonly class RecordUserPageVisitAction
{
public function execute(?User $user, string $page): void
{
PageVisit::create(['user_id' => $user?->id, 'page' => $page, 'user_type' => $user?->role ?? null, 'created_at' => now()]);
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace App\Actions;
use App\Models\Broker;
use App\Models\Customer;
use App\Models\Deal;
use App\Notifications\NewDealNotification;
final readonly class SendDealCreatedNotificationCustomerAction
{
public function execute(Deal $deal): void
{
/**
* @var Broker $broker
*/
$broker = $deal->broker->type;
$followers = $broker->followers()->with('user')->get();
$followers->map(function (Customer $follower) use ($deal) {
$user = $follower->user;
\Log::info("Sending notification to {$follower->user->name}", [$deal, $follower->user]);
$user->notifyNow(new NewDealNotification($deal));
});
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace App\Actions;
use App\Events\MessageSent;
use App\Exceptions\MessageNotSendException;
use App\Models\User;
use DB;
final readonly class SendMessageAction
{
public function __construct(private CreateOrGetInboxAction $inboxAction) {}
/**
* @throws \Throwable
*/
public function execute(User $sender, User $recipient, array $data): void
{
try {
// find the inbox between the two users
DB::beginTransaction();
$inbox = $this->inboxAction->execute($recipient, $sender);
// update the inbox with the last message and last user as current user
$inbox->last_message = $data['message'];
$inbox->last_user_id = $sender->id;
$inbox->save();
// create a new message in the inbox
$message = $inbox->messages()->create([
'message' => $data['message'],
'user_id' => $sender->id,
]);
// Send the message to all other users in the inbox
broadcast(new MessageSent($message))->toOthers();
DB::commit();
} catch (\Throwable $e) {
DB::rollBack();
throw new MessageNotSendException('Message not sent.', previous: $e);
}
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace App\Actions;
use App\Mail\PasswordResetMail;
final readonly class SendPasswordResetMailAction
{
/**
* @throws \Throwable
*/
public function execute(string $email, string $otp): void
{
try {
$message = \Mail::to($email)->send(new PasswordResetMail($otp));
\Log::info('Mail sent successfully', ['message' => $message]);
} catch (\Throwable $e) {
\Log::info('Mail send failed', ['message' => $e->getMessage()]);
}
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Events;
use App\Models\Message;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class MessageSent implements ShouldBroadcastNow
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(public Message $message)
{
//
}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
$users = $this->message->inbox->users->pluck('id')->toArray();
sort($users);
return [
new PrivateChannel("chat.{$users[0]}.{$users[1]}"),
];
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace App\Exceptions;
use Exception;
class MessageNotSendException extends Exception {}

View File

@ -0,0 +1,7 @@
<?php
namespace App\Exceptions;
use Exception;
class UserNotFoundException extends Exception {}

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Actions\SendDealCreatedNotificationCustomerAction;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Deal; use App\Models\Deal;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
@ -11,16 +12,18 @@ class DealController extends Controller
public function index() public function index()
{ {
return view('dashboards.admin.deals.index') return view('dashboards.admin.deals.index')
->with('deals', $this->deals()); ->with('pendingDeals', $this->pendingDeals())
->with('activeDeals', $this->activeDeals());
} }
public function approve(Deal $deal) public function approve(Deal $deal, SendDealCreatedNotificationCustomerAction $notificationCustomerAction)
{ {
try { try {
\DB::transaction(function () use ($deal) { \DB::transaction(function () use ($deal) {
$deal->active = true; $deal->active = true;
$deal->save(); $deal->save();
}); });
$notificationCustomerAction->execute($deal);
return back()->with('success', 'Deal activated successfully.'); return back()->with('success', 'Deal activated successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
@ -30,7 +33,8 @@ public function approve(Deal $deal)
} }
} }
public function reject(Deal $deal){ public function reject(Deal $deal)
{
try { try {
$deal->delete(); $deal->delete();
@ -42,8 +46,13 @@ public function reject(Deal $deal){
} }
} }
private function deals() private function pendingDeals()
{ {
return Deal::where('active', false)->get(); return Deal::where('active', false)->get();
} }
private function activeDeals()
{
return Deal::where('active', true)->get();
}
} }

View File

@ -5,6 +5,8 @@
use App\Enums\ReportStatus; use App\Enums\ReportStatus;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Report; use App\Models\Report;
use App\Notifications\ReportRejectedNotificationToUser;
use App\Notifications\ReportResolvedNotificationToBroker;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
class ReportController extends Controller class ReportController extends Controller
@ -27,9 +29,13 @@ public function resolve(Report $report)
$report->status = ReportStatus::Resolved; $report->status = ReportStatus::Resolved;
$report->save(); $report->save();
$report->user->notify(new ReportRejectedNotificationToUser($report->deals()->first()->title, false));
$report->deals()->first()->broker->notify(new ReportResolvedNotificationToBroker($report->deals()->first()->title,
false));
return back()->with('success', 'Report resolved successfully.'); return back()->with('success', 'Report resolved successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
\Log::error('Error resolving report', [$report->id, $e->getMessage(), $e->getTraceAsString()]); \Log::error('Error resolving report', [$report->id, $e->getMessage()]);
return back()->with('error', 'Something went wrong.'); return back()->with('error', 'Something went wrong.');
} }
@ -41,9 +47,11 @@ public function reject(Report $report)
$report->status = ReportStatus::Rejected; $report->status = ReportStatus::Rejected;
$report->save(); $report->save();
$report->user->notify(new ReportRejectedNotificationToUser($report->deals()->first()->title));
return back()->with('success', 'Report Rejected successfully.'); return back()->with('success', 'Report Rejected successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
\Log::error('Error rejecting report', [$report->id, $e->getMessage(), $e->getTraceAsString()]); \Log::error('Error rejecting report', [$report->id, $e->getMessage()]);
return back()->with('error', 'Something went wrong.'); return back()->with('error', 'Something went wrong.');
} }
@ -56,6 +64,11 @@ public function removeContent(Report $report)
{ {
try { try {
DB::transaction(function () use ($report) { DB::transaction(function () use ($report) {
$report->user->notify(new ReportRejectedNotificationToUser($report->deals()->first()->title, true));
$report->deals()->first()->broker->notify(new ReportResolvedNotificationToBroker($report->deals()->first()->title,
true));
$deal = $report->deals()->first(); $deal = $report->deals()->first();
$deal->active = false; $deal->active = false;
$report->status = ReportStatus::Resolved; $report->status = ReportStatus::Resolved;

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Auth; namespace App\Http\Controllers\Auth;
use App\Actions\RecordUserPageVisitAction;
use App\Enums\UserStatus; use App\Enums\UserStatus;
use App\Enums\UserTypes; use App\Enums\UserTypes;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
@ -16,7 +17,7 @@ public function create()
return view('auth.login'); return view('auth.login');
} }
public function store(AuthenticateUserRequest $request) public function store(AuthenticateUserRequest $request, RecordUserPageVisitAction $action)
{ {
$data = $request->validated(); $data = $request->validated();
if (Auth::attempt($data, $data['remember_me'] ?? false)) { if (Auth::attempt($data, $data['remember_me'] ?? false)) {
@ -35,6 +36,12 @@ public function store(AuthenticateUserRequest $request)
UserTypes::Broker->value, UserTypes::User->value => 'explore', UserTypes::Broker->value, UserTypes::User->value => 'explore',
}; };
try {
$action->execute($user, $route);
} catch (\Throwable $e) {
\Log::error('Error recording user page visit', [$e->getMessage()]);
}
return to_route($route); return to_route($route);
} else { } else {
return back() return back()

View File

@ -0,0 +1,91 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Actions\PasswordReset\ResendOTPAction;
use App\Actions\PasswordReset\SendOTPAction;
use App\Actions\PasswordReset\VerifyOTPAction;
use App\Exceptions\UserNotFoundException;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
use Illuminate\Validation\Rules\Password;
class PasswordResetController extends Controller
{
public function show()
{
return view('auth.passwords.reset');
}
public function sendCode(Request $request, SendOTPAction $action)
{
$data = $request->validate([
'email' => 'required|email',
]);
try {
$action->execute($data);
return to_route('password.reset.show.verify')
->with('success', 'Password reset code is sent');
} catch (UserNotFoundException $e) {
return to_route('password.reset.show.verify')->with('success', 'Password reset code is sent');
}
}
public function showVerify()
{
return view('auth.passwords.verify')
->with('expiryMinutes', 3);
}
public function verify(Request $request, VerifyOTPAction $otpAction)
{
$data = $request->validate(['otp' => 'required|string:min:5:max:6']);
try {
$isVerified = $otpAction->execute($data);
if (! $isVerified) {
return back()->with('error', 'Invalid OTP');
}
return to_route('password.reset.show.update')->with('success', 'OTP Verified');
} catch (UserNotFoundException $e) {
return back()->with('error', 'Session Expired');
}
}
public function showUpdate()
{
return view('auth.passwords.update');
}
public function update(Request $request)
{
$data = $request->validate([
'password' => 'required', 'confirmed', Password::min(8)->letters()->mixedCase()->numbers()->symbols(),
]);
$user = User::find(Session::get('otp_user_id'));
if (! $user) {
return back()->with('error', 'Session Expired');
}
$user->update(['password' => $data['password']]);
\Session::forget('otp_user_id');
return to_route('login.create')->with('success', 'Password updated successfully');
}
public function resend(ResendOTPAction $otpAction)
{
try {
$otpAction->execute();
return to_route('password.reset.show.verify')
->with('success', 'Password reset code is sent');
} catch (UserNotFoundException $e) {
return to_route('password.reset.show.verify')->with('success', 'Password reset code is sent');
}
}
}

View File

@ -45,7 +45,7 @@ public function store(StoreRegisterdUser $request)
}); });
return to_route('login.create') return to_route('login.create')
->with('userRegistered', 'User registered successfully.'); ->with('success', 'User registered successfully.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
Log::error('Registration Failed: '.$e->getMessage()); Log::error('Registration Failed: '.$e->getMessage());

View File

@ -33,7 +33,6 @@ protected function deals()
->WithLikePerDeal() ->WithLikePerDeal()
->WithRedirectionPerDeal() ->WithRedirectionPerDeal()
->withViewPerDeal() ->withViewPerDeal()
->latest() ->latest()->take(3)->get();
->paginate();
} }
} }

View File

@ -7,6 +7,7 @@
use App\Models\Deal; use App\Models\Deal;
use App\Models\DealCategory; use App\Models\DealCategory;
use App\Services\FileService; use App\Services\FileService;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@ -18,7 +19,8 @@ class BrokerDealController extends Controller
*/ */
public function index() public function index()
{ {
// return view('dashboards.broker.deals.index')
->with('deals', $this->deals());
} }
/** /**
@ -49,7 +51,7 @@ public function store(StoreBrokerDeal $request, FileService $fileService)
Deal::create($data); Deal::create($data);
Deal::reguard(); Deal::reguard();
return to_route('broker.dashboard')->with('success', 'Deal has been created.'); return to_route('broker.deals.index')->with('success', 'Deal has been created.');
} }
/** /**
@ -89,10 +91,10 @@ public function update(StoreBrokerDeal $request, Deal $deal, FileService $fileSe
} catch (\Throwable $exception) { } catch (\Throwable $exception) {
Log::error($exception->getMessage(), $exception->getTrace()); Log::error($exception->getMessage(), $exception->getTrace());
return to_route('broker.dashboard')->with('error', 'Something gone wrong.'); return back()->with('error', 'Something gone wrong.');
} }
return to_route('broker.dashboard')->with('success', 'Deal has been updated.'); return back()->with('success', 'Deal has been updated.');
} }
/** /**
@ -112,9 +114,31 @@ public function destroy(Deal $deal, FileService $fileService)
} catch (\Throwable $exception) { } catch (\Throwable $exception) {
Log::error($exception->getMessage(), $exception->getTrace()); Log::error($exception->getMessage(), $exception->getTrace());
return to_route('broker.dashboard')->with('error', 'Something gone wrong.'); return back()->with('error', 'Something gone wrong.');
} }
return to_route('broker.dashboard')->with('success', 'Deal has been deleted.'); return back()->with('success', 'Deal has been deleted.');
}
protected function deals()
{
return Auth::user()
->deals()
->select([
'id',
'title',
'description',
'image',
'active',
'slug',
'link',
'deal_category_id',
])
->with('category:id,name')
->WithLikePerDeal()
->WithRedirectionPerDeal()
->withViewPerDeal()
->latest()
->paginate(15);
} }
} }

View File

@ -0,0 +1,59 @@
<?php
namespace App\Http\Controllers;
use App\Actions\CreateOrGetInboxAction;
use App\Actions\SendMessageAction;
use App\Exceptions\MessageNotSendException;
use App\Http\Requests\SendMessageRequest;
use App\Models\User;
use Illuminate\Container\Attributes\CurrentUser;
use Log;
use Throwable;
class ChatController extends Controller
{
//
public function index(#[CurrentUser] User $user)
{
return view('dashboards.user.chat')
->with('inboxes', $user->inboxes);
}
public function show(#[CurrentUser] User $sender, User $recipient, CreateOrGetInboxAction $action)
{
try {
$inbox = $action->execute($recipient, $sender);
} catch (Throwable $e) {
Log::error('Inbox instantiation Failed: ', [$e->getMessage()]);
abort(500);
}
return view('dashboards.user.chat')
->with('recipient', $recipient)
->with('inboxes', $sender->inboxes)
->with('messages', $inbox->messages()->latest()->get());
}
/**
* @throws Throwable
*/
public function store(
#[CurrentUser] User $sender,
User $recipient,
SendMessageRequest $request,
SendMessageAction $action
) {
try {
$action->execute($sender, $recipient, $request->validated());
return response()->json(['message' => 'Message sent successfully.']);
} catch (MessageNotSendException $e) {
Log::error('Message send failed', [$e->getMessage()]);
return response()->json(['message' => 'Message sent failed.'], 500);
}
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\CommentRequest;
use App\Models\Comment;
use App\Models\Deal;
class CommentController extends Controller
{
public function index(Deal $deal)
{
$comments = $deal->comments()->with('user')->latest()->get();
$html = view('components.dashboard.user.deal-comment.index', compact('comments'))->render();
return response()->json(['html' => $html]);
}
public function store(Deal $deal, CommentRequest $request)
{
$data = $request->validated();
$data['user_id'] = $request->user()->id;
$data['deal_id'] = $deal->id;
Comment::create($data);
return response()->json(['message' => 'Comment created successfully.'], 201);
}
public function show(Comment $comment) {}
public function update(CommentRequest $request, Comment $comment)
{
$comment->update($request->validated());
}
public function destroy(Comment $comment)
{
$comment->delete();
return response()->json();
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\ContactRequest;
use App\Models\Admin;
use App\Notifications\NewContactNotification;
class ContactController extends Controller
{
public function __invoke(ContactRequest $request)
{
try {
$data = $request->validated();
$admin = Admin::first();
$admin->user->notify(new NewContactNotification($data['name'], $data['email'], $data['message']));
return back()->with('success', 'Your message has been sent successfully.');
} catch (\Throwable $e) {
\Log::error('Error sending contact message', [$e->getMessage()]);
return back()->with('error', 'Something went wrong.');
}
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Http\Controllers;
use App\Models\Broker;
use App\Models\Follow;
class FollowController extends Controller
{
public function __invoke(Broker $broker)
{
$follow = $this->checkFollow($broker);
if ($follow === null) {
return $this->store($broker);
} else {
return $this->destroy($follow);
}
}
public function store(Broker $broker)
{
Follow::create([
'broker_id' => $broker->id,
'customer_id' => auth()->id(),
]);
return response()->json(['message' => 'Followed successfully.']);
}
public function destroy(Follow $follow)
{
$follow->delete();
return response()->json(['message' => 'Unfollowed successfully.']);
}
protected function checkFollow(Broker $broker)
{
return Follow::where('broker_id', $broker->id)
->where('customer_id', auth()->id())
->first();
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\PushSubscriptionRequest;
use App\Models\User;
use Illuminate\Container\Attributes\CurrentUser;
use Illuminate\Http\JsonResponse;
class PushSubscriptionController extends Controller
{
public function __invoke(#[CurrentUser] User $user, PushSubscriptionRequest $request): JsonResponse
{
$data = $request->validated();
$user->updatePushSubscription($data['endpoint'], $data['keys']['p256dh'], $data['keys']['auth']);
return response()->json(['message' => 'Push subscription updated successfully.']);
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace App\Http\Controllers;
use App\Enums\UserTypes;
use App\Http\Resources\ActiveUsersStatsCollection;
use App\Http\Resources\DealsCountByCategoryCollection;
use App\Models\DealCategory;
use App\Queries\PageVisitStatsQuery;
use Illuminate\Http\Request;
class StatsController extends Controller
{
public function getActiveUsers(Request $request, PageVisitStatsQuery $baseQuery)
{
$startDay = $request->from ?? now()->subDays(30);
$endDay = $request->to ?? now();
$activeCustomers = $baseQuery->builder(UserTypes::User, $startDay, $endDay)->get();
$activeBrokers = $baseQuery->builder(UserTypes::Broker, $startDay, $endDay)->get();
return response()->json([
'activeCustomers' => new ActiveUsersStatsCollection($activeCustomers),
'activeBrokers' => new ActiveUsersStatsCollection($activeBrokers),
]);
}
public function getDealsByCategory()
{
return new DealsCountByCategoryCollection(
DealCategory::select(['id', 'name'])
->withCount('deals')
->get()
);
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureUserFollowedBroker
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
$sender = \Auth::user();
if ($sender->isBroker()) {
return $next($request);
}
$recipientUser = $request->route('recipient');
if ($recipientUser->isBroker()) {
$recipient = $recipientUser->type;
$isFollowing = $recipient->followers->contains($sender->id);
if ($isFollowing) {
return $next($request);
}
abort(403, 'You are not following this broker.');
}
abort('404', 'Broker not found.');
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class CommentRequest extends FormRequest
{
public function rules(): array
{
return [
'text' => ['required', 'string', 'max:255'],
];
}
public function authorize(): bool
{
return true;
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class ContactRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'name' => 'required|string|min:3|max:255',
'email' => 'required|email|max:255',
'message' => 'required|string|min:10|max:255',
];
}
protected function getRedirectUrl(): string
{
return parent::getRedirectUrl().'#contact';
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class PushSubscriptionRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'endpoint' => 'required|string|max:500',
'keys.auth' => 'required|string|max:255',
'keys.p256dh' => 'required|string|max:255',
];
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class SendMessageRequest extends FormRequest
{
public function rules(): array
{
return [
'message' => 'required|string',
];
}
public function authorize(): bool
{
return true;
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
class ActiveUsersStatsCollection extends ResourceCollection
{
public function toArray(Request $request): array
{
return [
'data' => $this->collection,
];
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class ActiveUsersStatsResource extends JsonResource
{
/**
* @return array{data: string, userCount: int}
*/
public function toArray(Request $request): array
{
return [
'date' => $this->date,
'userCount' => (int) $this->user_count,
];
}
}

View File

@ -27,6 +27,7 @@ public function toArray(Request $request): array
'totalRedirection' => $this->total_redirection, 'totalRedirection' => $this->total_redirection,
'isLiked' => $this->is_liked, 'isLiked' => $this->is_liked,
'isFavorite' => $this->is_favorite, 'isFavorite' => $this->is_favorite,
'isFollowed' => $this->is_followed,
]; ];
} }
} }

View File

@ -0,0 +1,16 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
class DealsCountByCategoryCollection extends ResourceCollection
{
public function toArray(Request $request): array
{
return [
'data' => $this->collection,
];
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
class DealsCountByCategoryResource extends JsonResource
{
public function toArray(Request $request): array
{
return [
'name' => $this->name,
'dealsCount' => $this->deals_count,
];
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class PasswordResetMail extends Mailable
{
use Queueable, SerializesModels;
public function __construct(private readonly string $otp) {}
public function envelope(): Envelope
{
return new Envelope(
from: new Address(config('mail.from.address'), config('mail.from.name')),
subject: 'Password Reset',
);
}
public function content(): Content
{
return new Content(
markdown: 'emails.password-reset',
with: ['otp' => $this->otp]
);
}
public function attachments(): array
{
return [];
}
}

View File

@ -5,6 +5,21 @@
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphOne; use Illuminate\Database\Eloquent\Relations\MorphOne;
/**
* @property int $id
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\User|null $user
*
* @method static \Illuminate\Database\Eloquent\Builder<static>|Admin newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Admin newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Admin query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Admin whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Admin whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Admin whereUpdatedAt($value)
*
* @mixin \Eloquent
*/
class Admin extends Model class Admin extends Model
{ {
public function user(): MorphOne public function user(): MorphOne

View File

@ -3,8 +3,35 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\MorphOne; use Illuminate\Database\Eloquent\Relations\MorphOne;
/**
* @property int $id
* @property string|null $bio
* @property string|null $location
* @property string|null $phone
* @property bool $verified
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Follow|null $pivot
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Customer> $followers
* @property-read int|null $followers_count
* @property-read \App\Models\User|null $user
*
* @method static \Illuminate\Database\Eloquent\Builder<static>|Broker newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Broker newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Broker query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Broker whereBio($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Broker whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Broker whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Broker whereLocation($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Broker wherePhone($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Broker whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Broker whereVerified($value)
*
* @mixin \Eloquent
*/
class Broker extends Model class Broker extends Model
{ {
protected $fillable = ['bio', 'location', 'phone']; protected $fillable = ['bio', 'location', 'phone'];
@ -20,4 +47,14 @@ public function user(): MorphOne
{ {
return $this->morphOne(User::class, 'role'); return $this->morphOne(User::class, 'role');
} }
public function followers(): BelongsToMany
{
return $this->belongsToMany(
Customer::class,
'follows',
'broker_id',
'customer_id'
)->using(Follow::class);
}
} }

47
app/Models/Comment.php Normal file
View File

@ -0,0 +1,47 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $id
* @property string $text
* @property int $deal_id
* @property int $user_id
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Deal|null $deal
* @property-read \App\Models\User|null $user
*
* @method static \Illuminate\Database\Eloquent\Builder<static>|Comment newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Comment newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Comment query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Comment whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Comment whereDealId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Comment whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Comment whereText($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Comment whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Comment whereUserId($value)
*
* @mixin \Eloquent
*/
class Comment extends Model
{
protected $fillable = [
'text',
'deal_id',
'user_id',
];
public function deal(): BelongsTo
{
return $this->belongsTo(Deal::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}

View File

@ -3,8 +3,32 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphOne; use Illuminate\Database\Eloquent\Relations\MorphOne;
/**
* @property int $id
* @property string|null $bio
* @property string|null $location
* @property string|null $phone
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Follow> $followings
* @property-read int|null $followings_count
* @property-read \App\Models\User|null $user
*
* @method static \Illuminate\Database\Eloquent\Builder<static>|Customer newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Customer newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Customer query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Customer whereBio($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Customer whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Customer whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Customer whereLocation($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Customer wherePhone($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Customer whereUpdatedAt($value)
*
* @mixin \Eloquent
*/
class Customer extends Model class Customer extends Model
{ {
protected $fillable = ['bio', 'location', 'phone']; protected $fillable = ['bio', 'location', 'phone'];
@ -13,4 +37,9 @@ public function user(): MorphOne
{ {
return $this->morphOne(User::class, 'role'); return $this->morphOne(User::class, 'role');
} }
public function followings(): HasMany
{
return $this->hasMany(Follow::class, 'customer_id');
}
} }

View File

@ -11,6 +11,52 @@
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
/**
* @property int $id
* @property string $title
* @property string $slug
* @property string $description
* @property string|null $image
* @property string|null $link
* @property int $active
* @property int $deal_category_id
* @property int $user_id
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\User|null $broker
* @property-read \App\Models\DealCategory|null $category
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Comment> $comments
* @property-read int|null $comments_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Interaction> $interactions
* @property-read int|null $interactions_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Report> $reports
* @property-read int|null $reports_count
*
* @method static Builder<static>|Deal WithActiveDeals()
* @method static Builder<static>|Deal WithCurrentUserInteractions()
* @method static Builder<static>|Deal WithLikePerDeal()
* @method static Builder<static>|Deal WithRedirectionPerDeal()
* @method static Builder<static>|Deal filterByCategory(string $category)
* @method static Builder<static>|Deal newModelQuery()
* @method static Builder<static>|Deal newQuery()
* @method static Builder<static>|Deal query()
* @method static Builder<static>|Deal search(string $search)
* @method static Builder<static>|Deal whereActive($value)
* @method static Builder<static>|Deal whereCreatedAt($value)
* @method static Builder<static>|Deal whereDealCategoryId($value)
* @method static Builder<static>|Deal whereDescription($value)
* @method static Builder<static>|Deal whereId($value)
* @method static Builder<static>|Deal whereImage($value)
* @method static Builder<static>|Deal whereLink($value)
* @method static Builder<static>|Deal whereSlug($value)
* @method static Builder<static>|Deal whereTitle($value)
* @method static Builder<static>|Deal whereUpdatedAt($value)
* @method static Builder<static>|Deal whereUserId($value)
* @method static Builder<static>|Deal withIsFollowedByCurrentUser()
* @method static Builder<static>|Deal withViewPerDeal()
*
* @mixin \Eloquent
*/
class Deal extends Model class Deal extends Model
{ {
public function broker(): BelongsTo public function broker(): BelongsTo
@ -33,6 +79,11 @@ public function reports(): BelongsToMany
return $this->belongsToMany(Report::class); return $this->belongsToMany(Report::class);
} }
public function comments(): HasMany
{
return $this->hasMany(Comment::class);
}
/** /**
* Scope a query to only include active deals * Scope a query to only include active deals
*/ */
@ -119,4 +170,30 @@ public function filterByCategory(Builder $query, string $category): Builder
{ {
return $query->where('deal_category_id', $category); return $query->where('deal_category_id', $category);
} }
// Add this to App\Models\Deal.php
/**
* Scope a query to check if the current user follows the deal's broker
*/
#[Scope]
public function withIsFollowedByCurrentUser(Builder $query): Builder
{
$user = Auth::user();
if (! $user || $user->role_type !== \App\Models\Customer::class) {
return $query->withExists(['broker as is_followed' => fn ($q) => $q->whereRaw('1 = 0')]);
}
return $query->withExists([
'broker as is_followed' => function ($query) use ($user) {
$query->where('role_type', \App\Models\Broker::class)
->whereHasMorph('type', [\App\Models\Broker::class], function ($query) use ($user) {
$query->whereHas('followers', function ($query) use ($user) {
$query->where('customer_id', $user->id);
});
});
},
]);
}
} }

View File

@ -5,8 +5,36 @@
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* @property int $id
* @property string $name
* @property string|null $description
* @property string $slug
* @property int $active
* @property int $order
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Deal> $deals
* @property-read int|null $deals_count
*
* @method static \Illuminate\Database\Eloquent\Builder<static>|DealCategory newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|DealCategory newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|DealCategory query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|DealCategory whereActive($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DealCategory whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DealCategory whereDescription($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DealCategory whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DealCategory whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DealCategory whereOrder($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DealCategory whereSlug($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|DealCategory whereUpdatedAt($value)
*
* @mixin \Eloquent
*/
class DealCategory extends Model class DealCategory extends Model
{ {
protected $fillable = ['name', 'description', 'slug', 'active', 'order'];
public function deals(): HasMany public function deals(): HasMany
{ {
return $this->hasMany(Deal::class); return $this->hasMany(Deal::class);

46
app/Models/Follow.php Normal file
View File

@ -0,0 +1,46 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\Pivot;
/**
* @property int $id
* @property int $customer_id
* @property int $broker_id
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Broker|null $broker
* @property-read \App\Models\Customer|null $customer
*
* @method static \Illuminate\Database\Eloquent\Builder<static>|Follow newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Follow newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Follow query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Follow whereBrokerId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Follow whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Follow whereCustomerId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Follow whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Follow whereUpdatedAt($value)
*
* @mixin \Eloquent
*/
class Follow extends Pivot
{
protected $table = 'follows';
protected $fillable = [
'customer_id',
'broker_id',
];
public function customer(): BelongsTo
{
return $this->belongsTo(Customer::class);
}
public function broker(): BelongsTo
{
return $this->belongsTo(Broker::class);
}
}

55
app/Models/Inbox.php Normal file
View File

@ -0,0 +1,55 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* @property int $id
* @property string|null $last_message
* @property int|null $last_user_id
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\User|null $lastUser
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\User> $users
* @property-read int|null $users_count
*
* @method static \Illuminate\Database\Eloquent\Builder<static>|Inbox newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Inbox newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Inbox query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Inbox whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Inbox whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Inbox whereLastMessage($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Inbox whereLastUserId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Inbox whereUpdatedAt($value)
*
* @mixin \Eloquent
*/
class Inbox extends Model
{
protected $fillable = ['last_user_id', 'last_message'];
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class);
}
public function lastUser(): BelongsTo
{
return $this->belongsTo(User::class, 'id', 'last_user_id');
}
public function messages(): HasMany
{
return $this->hasMany(Message::class, 'inbox_id');
}
public function getRecipientAttribute(): User
{
// first user in the relationship that is NOT the authenticated user
return $this->users->where('id', '!=', auth()->id())->first();
}
}

View File

@ -6,6 +6,30 @@
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $id
* @property int $user_id
* @property int $deal_id
* @property InteractionType $type
* @property int $count
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\Deal|null $deal
* @property-read \App\Models\Deal|null $user
*
* @method static \Illuminate\Database\Eloquent\Builder<static>|Interaction newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Interaction newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Interaction query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|Interaction whereCount($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Interaction whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Interaction whereDealId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Interaction whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Interaction whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Interaction whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|Interaction whereUserId($value)
*
* @mixin \Eloquent
*/
class Interaction extends Model class Interaction extends Model
{ {
protected $fillable = [ protected $fillable = [

62
app/Models/Message.php Normal file
View File

@ -0,0 +1,62 @@
<?php
namespace App\Models;
use Eloquent;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Carbon;
/**
* @property int $id
* @property int $user_id
* @property int $inbox_id
* @property string $message
* @property string|null $read_at
* @property string|null $delivered_at
* @property string|null $deleted_at
* @property string|null $failed_at
* @property Carbon $created_at
* @property-read Inbox|null $inbox
* @property-read User|null $user
*
* @method static Builder<static>|Message newModelQuery()
* @method static Builder<static>|Message newQuery()
* @method static Builder<static>|Message query()
* @method static Builder<static>|Message whereCreatedAt($value)
* @method static Builder<static>|Message whereDeletedAt($value)
* @method static Builder<static>|Message whereDeliveredAt($value)
* @method static Builder<static>|Message whereFailedAt($value)
* @method static Builder<static>|Message whereId($value)
* @method static Builder<static>|Message whereInboxId($value)
* @method static Builder<static>|Message whereMessage($value)
* @method static Builder<static>|Message whereReadAt($value)
* @method static Builder<static>|Message whereUserId($value)
*
* @mixin Eloquent
*/
class Message extends Model
{
public $timestamps = false;
protected $fillable = [
'inbox_id', 'user_id', 'message',
'read_at', 'deleted_at', 'failed_at',
'created_at', 'delivered_at',
];
protected $casts = [
'created_at' => 'datetime',
];
public function inbox(): BelongsTo
{
return $this->belongsTo(Inbox::class);
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}

41
app/Models/PageVisit.php Normal file
View File

@ -0,0 +1,41 @@
<?php
namespace App\Models;
use App\Enums\UserTypes;
use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
* @property int|null $user_id
* @property string $page
* @property UserTypes|null $user_type
* @property string $created_at
*
* @method static \Illuminate\Database\Eloquent\Builder<static>|PageVisit newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|PageVisit newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|PageVisit query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|PageVisit whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PageVisit whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PageVisit wherePage($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PageVisit whereUserId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|PageVisit whereUserType($value)
*
* @mixin \Eloquent
*/
class PageVisit extends Model
{
protected $fillable = [
'user_id', 'page', 'user_type',
'created_at',
];
public $timestamps = false;
protected function casts(): array
{
return [
'user_type' => UserTypes::class,
];
}
}

View File

@ -5,6 +5,25 @@
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $id
* @property int $user_id
* @property string $query
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \App\Models\User|null $user
*
* @method static \Illuminate\Database\Eloquent\Builder<static>|RecentSearch newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|RecentSearch newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|RecentSearch query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|RecentSearch whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|RecentSearch whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|RecentSearch whereQuery($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|RecentSearch whereUpdatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|RecentSearch whereUserId($value)
*
* @mixin \Eloquent
*/
class RecentSearch extends Model class RecentSearch extends Model
{ {
protected $fillable = ['query']; protected $fillable = ['query'];

View File

@ -10,6 +10,32 @@
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
/**
* @property int $id
* @property ReportType $type
* @property ReportStatus $status
* @property string $description
* @property int $user_id
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Deal> $deals
* @property-read int|null $deals_count
* @property-read \App\Models\User|null $user
*
* @method static Builder<static>|Report newModelQuery()
* @method static Builder<static>|Report newQuery()
* @method static Builder<static>|Report orderByStatus(array $statusOrder)
* @method static Builder<static>|Report query()
* @method static Builder<static>|Report whereCreatedAt($value)
* @method static Builder<static>|Report whereDescription($value)
* @method static Builder<static>|Report whereId($value)
* @method static Builder<static>|Report whereStatus($value)
* @method static Builder<static>|Report whereType($value)
* @method static Builder<static>|Report whereUpdatedAt($value)
* @method static Builder<static>|Report whereUserId($value)
*
* @mixin \Eloquent
*/
class Report extends Model class Report extends Model
{ {
public function user(): BelongsTo public function user(): BelongsTo

View File

@ -5,16 +5,74 @@
// use Illuminate\Contracts\Auth\MustVerifyEmail; // use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough; use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\Relations\MorphTo; use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notifiable;
use NotificationChannels\WebPush\HasPushSubscriptions;
/**
* @property int $id
* @property string $name
* @property string $email
* @property \Illuminate\Support\Carbon|null $email_verified_at
* @property string $password
* @property string|null $remember_token
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property string $status
* @property string $role
* @property string|null $role_type
* @property int|null $role_id
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Comment> $comments
* @property-read int|null $comments_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Deal> $deals
* @property-read int|null $deals_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Interaction> $dealsInteractions
* @property-read int|null $deals_interactions_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Inbox> $inboxes
* @property-read int|null $inboxes_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Deal> $interactedDeals
* @property-read int|null $interacted_deals_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, User> $interactions
* @property-read int|null $interactions_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Message> $messages
* @property-read int|null $messages_count
* @property-read \Illuminate\Notifications\DatabaseNotificationCollection<int, \Illuminate\Notifications\DatabaseNotification> $notifications
* @property-read int|null $notifications_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \NotificationChannels\WebPush\PushSubscription> $pushSubscriptions
* @property-read int|null $push_subscriptions_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\RecentSearch> $recentSearches
* @property-read int|null $recent_searches_count
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Report> $reports
* @property-read int|null $reports_count
* @property-read Model|\Eloquent|null $type
*
* @method static \Database\Factories\UserFactory factory($count = null, $state = [])
* @method static \Illuminate\Database\Eloquent\Builder<static>|User newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|User newQuery()
* @method static \Illuminate\Database\Eloquent\Builder<static>|User query()
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereCreatedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereEmail($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereEmailVerifiedAt($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereName($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User wherePassword($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereRememberToken($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereRole($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereRoleId($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereRoleType($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereStatus($value)
* @method static \Illuminate\Database\Eloquent\Builder<static>|User whereUpdatedAt($value)
*
* @mixin \Eloquent
*/
class User extends Authenticatable class User extends Authenticatable
{ {
/** @use HasFactory<\Database\Factories\UserFactory> */ /** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable; use HasFactory, HasPushSubscriptions, Notifiable;
/** /**
* The attributes that are mass assignable. * The attributes that are mass assignable.
@ -104,4 +162,19 @@ public function reports(): HasMany
{ {
return $this->hasMany(Report::class); return $this->hasMany(Report::class);
} }
public function comments(): HasMany
{
return $this->hasMany(Comment::class);
}
public function inboxes(): BelongsToMany
{
return $this->belongsToMany(Inbox::class);
}
public function messages(): HasMany
{
return $this->hasMany(Message::class);
}
} }

View File

@ -0,0 +1,37 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class NewContactNotification extends Notification implements ShouldQueue
{
use Queueable;
public function __construct(
private readonly string $customerName,
private readonly string $customerEmail,
private readonly string $customerMessage
) {}
public function via($notifiable): array
{
return ['mail'];
}
public function toMail($notifiable): MailMessage
{
return (new MailMessage)
->subject('New Contact Submission: '.$this->customerName)
->greeting('Hello Admin,') // Or keep it empty if you prefer
->line('You have received a new message from your contact form:')
->line("**Name:** {$this->customerName}")
->line("**Email:** {$this->customerEmail}")
->line('**Message:**')
->line($this->customerMessage)
->action('Reply via Email', 'mailto:'.$this->customerEmail);
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace App\Notifications;
use App\Models\Deal;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Str;
use NotificationChannels\WebPush\WebPushChannel;
use NotificationChannels\WebPush\WebPushMessage;
class NewDealNotification extends Notification
{
/**
* Create a new notification instance.
*/
public function __construct(private readonly Deal $deal)
{
//
}
/**
* Get the notification's delivery channels.
*
* @return array<int, string>
*/
public function via(object $notifiable): array
{
return [WebPushChannel::class];
}
public function toWebPush($notifiable, $notification): WebPushMessage
{
\Log::info('Building WebPush for user: '.$notifiable->id);
return (new WebPushMessage)
->title("New deal from {$this->deal->broker->name}")
->body('Check out this deal:'.Str::limit($this->deal->title, 30, '...'))
->action('View deal', route('explore', ['show' => $this->deal->id]));
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class ReportRejectedNotificationToUser extends Notification implements ShouldQueue
{
use Queueable;
public function __construct(
private readonly string $dealTitle,
) {}
public function via($notifiable): array
{
return ['mail'];
}
public function toMail($notifiable): MailMessage
{
return (new MailMessage)
->subject('Update on Your Recent Report: '.$this->dealTitle)
->greeting('Hello!')
->line('Thank you for helping us maintain the integrity of our marketplace.')
->line("We have completed our review of the deal you reported: **{$this->dealTitle}**.")
->line('Based on our moderation policy, we have rejected your report.')
->action('View Marketplace', route('explore'))
->line('Your feedback helps make our community a safer place for everyone.');
}
public function toArray($notifiable): array
{
return [
'report_outcome' => $this->isContentRemoved ? 'violation_confirmed' : 'no_violation_found',
'deal_title' => $this->dealTitle,
];
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class ReportResolvedNotificationToBroker extends Notification implements ShouldQueue
{
use Queueable;
public function __construct(
private readonly string $dealTitle,
private readonly bool $isContentRemoved
) {}
public function via($notifiable): array
{
return ['mail'];
}
public function toMail($notifiable): MailMessage
{
$status = $this->isContentRemoved
? 'has been removed following a policy review.'
: 'has been reviewed and remains active.';
return (new MailMessage)
->subject('Update Regarding Your Reported Deal: '.$this->dealTitle)
->greeting('Hello!')
->line("We are writing to inform you that the report regarding your deal, **{$this->dealTitle}**, has been resolved.")
->line("Our moderation team has completed their review, and the content {$status}")
->action('View My Deals', route('broker.dashboard')) // Adjusted for your UMS/Project structure
->line('Thank you for being a part of our marketplace.');
}
public function toArray($notifiable): array
{
return [
'deal_title' => $this->dealTitle,
'action_taken' => $this->isContentRemoved ? 'removed' : 'kept',
];
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
class ReportResolvedNotificationToUser extends Notification implements ShouldQueue
{
use Queueable;
public function __construct(
private readonly string $dealTitle,
private readonly bool $isContentRemoved
) {}
public function via($notifiable): array
{
return ['mail'];
}
public function toMail($notifiable): MailMessage
{
$outcome = $this->isContentRemoved
? 'has been removed following our investigation.'
: 'will remain active as it was found to be in compliance with our guidelines.';
return (new MailMessage)
->subject('Update on Your Recent Report: '.$this->dealTitle)
->greeting('Hello!')
->line('Thank you for helping us maintain the integrity of our marketplace.')
->line("We have completed our review of the deal you reported: **{$this->dealTitle}**.")
->line("Based on our moderation policy, the content {$outcome}")
->action('View Marketplace', route('explore'))
->line('Your feedback helps make our community a safer place for everyone.');
}
public function toArray($notifiable): array
{
return [
'report_outcome' => $this->isContentRemoved ? 'violation_confirmed' : 'no_violation_found',
'deal_title' => $this->dealTitle,
];
}
}

View File

@ -29,6 +29,7 @@ public function builder(): Builder
// Check if the current user interacted with the deal // Check if the current user interacted with the deal
->tap(fn ($q) => (new Deal)->withCurrentUserInteractions($q)) ->tap(fn ($q) => (new Deal)->withCurrentUserInteractions($q))
->tap(fn ($q) => (new Deal)->withLikePerDeal($q)) ->tap(fn ($q) => (new Deal)->withLikePerDeal($q))
->tap(fn ($q) => (new Deal)->withIsFollowedByCurrentUser($q))
->tap(fn ($q) => (new Deal)->withRedirectionPerDeal($q)); ->tap(fn ($q) => (new Deal)->withRedirectionPerDeal($q));
} }
} }

View File

@ -0,0 +1,22 @@
<?php
namespace App\Queries;
use App\Enums\UserTypes;
use App\Models\PageVisit;
use Illuminate\Database\Eloquent\Builder;
final readonly class PageVisitStatsQuery
{
/**
* @return Builder<PageVisit>
*/
public function builder(UserTypes $userType, string $startDay, string $endDay): Builder
{
return PageVisit::query()
->selectRaw('count(distinct user_id) as user_count, created_at as date')
->where('user_type', $userType)
->whereBetween('created_at', [$startDay.' 00:00:00', $endDay.' 23:59:59'])
->groupBy('date');
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Services;
use App\Models\User;
use Illuminate\Support\Facades\Cache;
class OTPService
{
public function generate(User $user, int $length = 6): string
{
$code = \Str::random($length);
Cache::put("otp_$user->id", $code, now()->addMinutes((int) config('auth.otp_lifespan', '10')));
return $code;
}
public function verify(User $user, string $otp): bool
{
$code = Cache::get("otp_$user->id");
if ($code === $otp) {
Cache::forget("otp_$user->id");
return true;
}
return false;
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Services;
use Twilio\Exceptions\TwilioException;
use Twilio\Rest\Client;
class TwilioService
{
private Client $client;
public function __construct()
{
$this->client = new Client(config('services.twilio.sid'), config('services.twilio.token'));
}
/**
* @throws TwilioException
*/
public function sendSms(string $to, string $message): void
{
$this->client->messages->create($to, ['from' => config('services.twilio.from'), 'body' => $message]);
}
}

View File

@ -8,6 +8,7 @@
->withRouting( ->withRouting(
web: __DIR__.'/../routes/web.php', web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php', commands: __DIR__.'/../routes/console.php',
channels: __DIR__.'/../routes/channels.php',
health: '/up', health: '/up',
) )
->withMiddleware(function (Middleware $middleware): void { ->withMiddleware(function (Middleware $middleware): void {

View File

@ -1,2 +0,0 @@
*
!.gitignore

105
bootstrap/cache/packages.php vendored Executable file
View File

@ -0,0 +1,105 @@
<?php return array (
'barryvdh/laravel-ide-helper' =>
array (
'providers' =>
array (
0 => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
),
),
'blade-ui-kit/blade-heroicons' =>
array (
'providers' =>
array (
0 => 'BladeUI\\Heroicons\\BladeHeroiconsServiceProvider',
),
),
'blade-ui-kit/blade-icons' =>
array (
'providers' =>
array (
0 => 'BladeUI\\Icons\\BladeIconsServiceProvider',
),
),
'laradumps/laradumps' =>
array (
'providers' =>
array (
0 => 'LaraDumps\\LaraDumps\\LaraDumpsServiceProvider',
),
),
'laravel-notification-channels/webpush' =>
array (
'providers' =>
array (
0 => 'NotificationChannels\\WebPush\\WebPushServiceProvider',
),
),
'laravel/pail' =>
array (
'providers' =>
array (
0 => 'Laravel\\Pail\\PailServiceProvider',
),
),
'laravel/reverb' =>
array (
'providers' =>
array (
0 => 'Laravel\\Reverb\\ApplicationManagerServiceProvider',
1 => 'Laravel\\Reverb\\ReverbServiceProvider',
),
),
'laravel/sail' =>
array (
'providers' =>
array (
0 => 'Laravel\\Sail\\SailServiceProvider',
),
),
'laravel/tinker' =>
array (
'providers' =>
array (
0 => 'Laravel\\Tinker\\TinkerServiceProvider',
),
),
'nesbot/carbon' =>
array (
'providers' =>
array (
0 => 'Carbon\\Laravel\\ServiceProvider',
),
),
'nunomaduro/collision' =>
array (
'providers' =>
array (
0 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider',
),
),
'nunomaduro/termwind' =>
array (
'providers' =>
array (
0 => 'Termwind\\Laravel\\TermwindServiceProvider',
),
),
'opcodesio/log-viewer' =>
array (
'aliases' =>
array (
'LogViewer' => 'Opcodes\\LogViewer\\Facades\\LogViewer',
),
'providers' =>
array (
0 => 'Opcodes\\LogViewer\\LogViewerServiceProvider',
),
),
'pestphp/pest-plugin-laravel' =>
array (
'providers' =>
array (
0 => 'Pest\\Laravel\\PestServiceProvider',
),
),
);

289
bootstrap/cache/services.php vendored Executable file
View File

@ -0,0 +1,289 @@
<?php return array (
'providers' =>
array (
0 => 'Illuminate\\Auth\\AuthServiceProvider',
1 => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
2 => 'Illuminate\\Bus\\BusServiceProvider',
3 => 'Illuminate\\Cache\\CacheServiceProvider',
4 => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
5 => 'Illuminate\\Concurrency\\ConcurrencyServiceProvider',
6 => 'Illuminate\\Cookie\\CookieServiceProvider',
7 => 'Illuminate\\Database\\DatabaseServiceProvider',
8 => 'Illuminate\\Encryption\\EncryptionServiceProvider',
9 => 'Illuminate\\Filesystem\\FilesystemServiceProvider',
10 => 'Illuminate\\Foundation\\Providers\\FoundationServiceProvider',
11 => 'Illuminate\\Hashing\\HashServiceProvider',
12 => 'Illuminate\\Mail\\MailServiceProvider',
13 => 'Illuminate\\Notifications\\NotificationServiceProvider',
14 => 'Illuminate\\Pagination\\PaginationServiceProvider',
15 => 'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider',
16 => 'Illuminate\\Pipeline\\PipelineServiceProvider',
17 => 'Illuminate\\Queue\\QueueServiceProvider',
18 => 'Illuminate\\Redis\\RedisServiceProvider',
19 => 'Illuminate\\Session\\SessionServiceProvider',
20 => 'Illuminate\\Translation\\TranslationServiceProvider',
21 => 'Illuminate\\Validation\\ValidationServiceProvider',
22 => 'Illuminate\\View\\ViewServiceProvider',
23 => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
24 => 'BladeUI\\Heroicons\\BladeHeroiconsServiceProvider',
25 => 'BladeUI\\Icons\\BladeIconsServiceProvider',
26 => 'LaraDumps\\LaraDumps\\LaraDumpsServiceProvider',
27 => 'NotificationChannels\\WebPush\\WebPushServiceProvider',
28 => 'Laravel\\Pail\\PailServiceProvider',
29 => 'Laravel\\Reverb\\ApplicationManagerServiceProvider',
30 => 'Laravel\\Reverb\\ReverbServiceProvider',
31 => 'Laravel\\Sail\\SailServiceProvider',
32 => 'Laravel\\Tinker\\TinkerServiceProvider',
33 => 'Carbon\\Laravel\\ServiceProvider',
34 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider',
35 => 'Termwind\\Laravel\\TermwindServiceProvider',
36 => 'Opcodes\\LogViewer\\LogViewerServiceProvider',
37 => 'Pest\\Laravel\\PestServiceProvider',
38 => 'App\\Providers\\AppServiceProvider',
),
'eager' =>
array (
0 => 'Illuminate\\Auth\\AuthServiceProvider',
1 => 'Illuminate\\Cookie\\CookieServiceProvider',
2 => 'Illuminate\\Database\\DatabaseServiceProvider',
3 => 'Illuminate\\Encryption\\EncryptionServiceProvider',
4 => 'Illuminate\\Filesystem\\FilesystemServiceProvider',
5 => 'Illuminate\\Foundation\\Providers\\FoundationServiceProvider',
6 => 'Illuminate\\Notifications\\NotificationServiceProvider',
7 => 'Illuminate\\Pagination\\PaginationServiceProvider',
8 => 'Illuminate\\Session\\SessionServiceProvider',
9 => 'Illuminate\\View\\ViewServiceProvider',
10 => 'BladeUI\\Heroicons\\BladeHeroiconsServiceProvider',
11 => 'BladeUI\\Icons\\BladeIconsServiceProvider',
12 => 'LaraDumps\\LaraDumps\\LaraDumpsServiceProvider',
13 => 'NotificationChannels\\WebPush\\WebPushServiceProvider',
14 => 'Laravel\\Pail\\PailServiceProvider',
15 => 'Laravel\\Reverb\\ReverbServiceProvider',
16 => 'Carbon\\Laravel\\ServiceProvider',
17 => 'NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider',
18 => 'Termwind\\Laravel\\TermwindServiceProvider',
19 => 'Opcodes\\LogViewer\\LogViewerServiceProvider',
20 => 'Pest\\Laravel\\PestServiceProvider',
21 => 'App\\Providers\\AppServiceProvider',
),
'deferred' =>
array (
'Illuminate\\Broadcasting\\BroadcastManager' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
'Illuminate\\Contracts\\Broadcasting\\Factory' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
'Illuminate\\Contracts\\Broadcasting\\Broadcaster' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
'Illuminate\\Bus\\Dispatcher' => 'Illuminate\\Bus\\BusServiceProvider',
'Illuminate\\Contracts\\Bus\\Dispatcher' => 'Illuminate\\Bus\\BusServiceProvider',
'Illuminate\\Contracts\\Bus\\QueueingDispatcher' => 'Illuminate\\Bus\\BusServiceProvider',
'Illuminate\\Bus\\BatchRepository' => 'Illuminate\\Bus\\BusServiceProvider',
'Illuminate\\Bus\\DatabaseBatchRepository' => 'Illuminate\\Bus\\BusServiceProvider',
'cache' => 'Illuminate\\Cache\\CacheServiceProvider',
'cache.store' => 'Illuminate\\Cache\\CacheServiceProvider',
'cache.psr6' => 'Illuminate\\Cache\\CacheServiceProvider',
'memcached.connector' => 'Illuminate\\Cache\\CacheServiceProvider',
'Illuminate\\Cache\\RateLimiter' => 'Illuminate\\Cache\\CacheServiceProvider',
'Illuminate\\Foundation\\Console\\AboutCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Cache\\Console\\ClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Cache\\Console\\ForgetCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ClearCompiledCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Auth\\Console\\ClearResetsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ConfigCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ConfigClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ConfigShowCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\DbCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\MonitorCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\PruneCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\ShowCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\TableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\WipeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\DownCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\EnvironmentCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\EnvironmentDecryptCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\EnvironmentEncryptCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\EventCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\EventClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\EventListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Concurrency\\Console\\InvokeSerializedClosureCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\KeyGenerateCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\OptimizeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\OptimizeClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\PackageDiscoverCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Cache\\Console\\PruneStaleTagsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\ClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\ListFailedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\FlushFailedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\ForgetFailedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\ListenCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\MonitorCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\PauseCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\PruneBatchesCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\PruneFailedJobsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\RestartCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\ResumeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\RetryCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\RetryBatchCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\WorkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ReloadCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\RouteCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\RouteClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\RouteListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\DumpCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Seeds\\SeedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Console\\Scheduling\\ScheduleFinishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Console\\Scheduling\\ScheduleListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Console\\Scheduling\\ScheduleRunCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Console\\Scheduling\\ScheduleClearCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Console\\Scheduling\\ScheduleTestCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Console\\Scheduling\\ScheduleWorkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Console\\Scheduling\\ScheduleInterruptCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\ShowModelCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\StorageLinkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\StorageUnlinkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\UpCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ViewCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ViewClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ApiInstallCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\BroadcastingInstallCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Cache\\Console\\CacheTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\CastMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ChannelListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ChannelMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ClassMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ComponentMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ConfigMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ConfigPublishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ConsoleMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Routing\\Console\\ControllerMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\DocsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\EnumMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\EventGenerateCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\EventMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ExceptionMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Factories\\FactoryMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\InterfaceMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\JobMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\JobMiddlewareMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\LangPublishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ListenerMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\MailMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Routing\\Console\\MiddlewareMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ModelMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\NotificationMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Notifications\\Console\\NotificationTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ObserverMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\PolicyMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ProviderMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\FailedTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\TableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Queue\\Console\\BatchesTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\RequestMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ResourceMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\RuleMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ScopeMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Seeds\\SeederMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Session\\Console\\SessionTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ServeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\StubPublishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\TestMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\TraitMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\VendorPublishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Foundation\\Console\\ViewMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'migrator' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'migration.repository' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'migration.creator' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Migrations\\Migrator' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Migrations\\MigrateCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Migrations\\FreshCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Migrations\\InstallCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Migrations\\RefreshCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Migrations\\ResetCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Migrations\\RollbackCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Migrations\\StatusCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Database\\Console\\Migrations\\MigrateMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'composer' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
'Illuminate\\Concurrency\\ConcurrencyManager' => 'Illuminate\\Concurrency\\ConcurrencyServiceProvider',
'hash' => 'Illuminate\\Hashing\\HashServiceProvider',
'hash.driver' => 'Illuminate\\Hashing\\HashServiceProvider',
'mail.manager' => 'Illuminate\\Mail\\MailServiceProvider',
'mailer' => 'Illuminate\\Mail\\MailServiceProvider',
'Illuminate\\Mail\\Markdown' => 'Illuminate\\Mail\\MailServiceProvider',
'auth.password' => 'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider',
'auth.password.broker' => 'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider',
'Illuminate\\Contracts\\Pipeline\\Hub' => 'Illuminate\\Pipeline\\PipelineServiceProvider',
'pipeline' => 'Illuminate\\Pipeline\\PipelineServiceProvider',
'queue' => 'Illuminate\\Queue\\QueueServiceProvider',
'queue.connection' => 'Illuminate\\Queue\\QueueServiceProvider',
'queue.failer' => 'Illuminate\\Queue\\QueueServiceProvider',
'queue.listener' => 'Illuminate\\Queue\\QueueServiceProvider',
'queue.worker' => 'Illuminate\\Queue\\QueueServiceProvider',
'redis' => 'Illuminate\\Redis\\RedisServiceProvider',
'redis.connection' => 'Illuminate\\Redis\\RedisServiceProvider',
'translator' => 'Illuminate\\Translation\\TranslationServiceProvider',
'translation.loader' => 'Illuminate\\Translation\\TranslationServiceProvider',
'validator' => 'Illuminate\\Validation\\ValidationServiceProvider',
'validation.presence' => 'Illuminate\\Validation\\ValidationServiceProvider',
'Illuminate\\Contracts\\Validation\\UncompromisedVerifier' => 'Illuminate\\Validation\\ValidationServiceProvider',
'Barryvdh\\LaravelIdeHelper\\Console\\GeneratorCommand' => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
'Barryvdh\\LaravelIdeHelper\\Console\\ModelsCommand' => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
'Barryvdh\\LaravelIdeHelper\\Console\\MetaCommand' => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
'Barryvdh\\LaravelIdeHelper\\Console\\EloquentCommand' => 'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider',
'Laravel\\Reverb\\ApplicationManager' => 'Laravel\\Reverb\\ApplicationManagerServiceProvider',
'Laravel\\Reverb\\Contracts\\ApplicationProvider' => 'Laravel\\Reverb\\ApplicationManagerServiceProvider',
'Laravel\\Sail\\Console\\InstallCommand' => 'Laravel\\Sail\\SailServiceProvider',
'Laravel\\Sail\\Console\\PublishCommand' => 'Laravel\\Sail\\SailServiceProvider',
'command.tinker' => 'Laravel\\Tinker\\TinkerServiceProvider',
),
'when' =>
array (
'Illuminate\\Broadcasting\\BroadcastServiceProvider' =>
array (
),
'Illuminate\\Bus\\BusServiceProvider' =>
array (
),
'Illuminate\\Cache\\CacheServiceProvider' =>
array (
),
'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider' =>
array (
),
'Illuminate\\Concurrency\\ConcurrencyServiceProvider' =>
array (
),
'Illuminate\\Hashing\\HashServiceProvider' =>
array (
),
'Illuminate\\Mail\\MailServiceProvider' =>
array (
),
'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider' =>
array (
),
'Illuminate\\Pipeline\\PipelineServiceProvider' =>
array (
),
'Illuminate\\Queue\\QueueServiceProvider' =>
array (
),
'Illuminate\\Redis\\RedisServiceProvider' =>
array (
),
'Illuminate\\Translation\\TranslationServiceProvider' =>
array (
),
'Illuminate\\Validation\\ValidationServiceProvider' =>
array (
),
'Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider' =>
array (
),
'Laravel\\Reverb\\ApplicationManagerServiceProvider' =>
array (
),
'Laravel\\Sail\\SailServiceProvider' =>
array (
),
'Laravel\\Tinker\\TinkerServiceProvider' =>
array (
),
),
);

View File

@ -1,91 +1,97 @@
{ {
"$schema": "https://getcomposer.org/schema.json", "$schema": "https://getcomposer.org/schema.json",
"name": "laravel/laravel", "name": "laravel/laravel",
"type": "project", "type": "project",
"description": "The skeleton application for the Laravel framework.", "description": "The skeleton application for the Laravel framework.",
"keywords": [ "keywords": [
"laravel", "laravel",
"framework" "framework"
],
"license": "MIT",
"require": {
"php": "^8.2",
"blade-ui-kit/blade-heroicons": "^2.6",
"laravel-notification-channels/webpush": "^10.4",
"laravel/framework": "^12.0",
"laravel/reverb": "^1.0",
"laravel/tinker": "^2.10.1",
"twilio/sdk": "^8.10"
},
"require-dev": {
"barryvdh/laravel-ide-helper": "^3.6",
"fakerphp/faker": "^1.23",
"laradumps/laradumps": "^5.0",
"laravel/pail": "^1.2.2",
"laravel/pint": "^1.24",
"laravel/sail": "^1.41",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"opcodesio/log-viewer": "^3.21",
"pestphp/pest": "^4.3",
"pestphp/pest-plugin-laravel": "^4.0"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"setup": [
"composer install",
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
"@php artisan key:generate",
"@php artisan migrate --force",
"npm install",
"npm run build"
], ],
"license": "MIT", "dev": [
"require": { "Composer\\Config::disableProcessTimeout",
"php": "^8.2", "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 --kill-others"
"blade-ui-kit/blade-heroicons": "^2.6", ],
"laravel/framework": "^12.0", "test": [
"laravel/tinker": "^2.10.1" "@php artisan config:clear --ansi",
}, "@php artisan test"
"require-dev": { ],
"fakerphp/faker": "^1.23", "post-autoload-dump": [
"laravel/pail": "^1.2.2", "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"laravel/pint": "^1.24", "@php artisan package:discover --ansi"
"laravel/sail": "^1.41", ],
"mockery/mockery": "^1.6", "post-update-cmd": [
"nunomaduro/collision": "^8.6", "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
"pestphp/pest": "^4.3", ],
"pestphp/pest-plugin-laravel": "^4.0" "post-root-package-install": [
}, "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
"autoload": { ],
"psr-4": { "post-create-project-cmd": [
"App\\": "app/", "@php artisan key:generate --ansi",
"Database\\Factories\\": "database/factories/", "@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"Database\\Seeders\\": "database/seeders/" "@php artisan migrate --graceful --ansi"
} ],
}, "pre-package-uninstall": [
"autoload-dev": { "Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"
"psr-4": { ]
"Tests\\": "tests/" },
} "extra": {
}, "laravel": {
"scripts": { "dont-discover": []
"setup": [ }
"composer install", },
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"", "config": {
"@php artisan key:generate", "optimize-autoloader": true,
"@php artisan migrate --force", "preferred-install": "dist",
"npm install", "sort-packages": true,
"npm run build" "allow-plugins": {
], "pestphp/pest-plugin": true,
"dev": [ "php-http/discovery": true
"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 --kill-others" },
], "minimum-stability": "stable",
"test": [ "prefer-stable": true
"@php artisan config:clear --ansi",
"@php artisan test"
],
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
],
"pre-package-uninstall": [
"Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"
]
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true
}
},
"minimum-stability": "stable",
"prefer-stable": true
} }

2400
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -111,5 +111,6 @@
*/ */
'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800), 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),
'otp_lifespan' => env('OTP_LIFESPAN', 10),
]; ];

82
config/broadcasting.php Normal file
View File

@ -0,0 +1,82 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Broadcaster
|--------------------------------------------------------------------------
|
| This option controls the default broadcaster that will be used by the
| framework when an event needs to be broadcast. You may set this to
| any of the connections defined in the "connections" array below.
|
| Supported: "reverb", "pusher", "ably", "redis", "log", "null"
|
*/
'default' => env('BROADCAST_CONNECTION', 'null'),
/*
|--------------------------------------------------------------------------
| Broadcast Connections
|--------------------------------------------------------------------------
|
| Here you may define all of the broadcast connections that will be used
| to broadcast events to other systems or over WebSockets. Samples of
| each available type of connection are provided inside this array.
|
*/
'connections' => [
'reverb' => [
'driver' => 'reverb',
'key' => env('REVERB_APP_KEY'),
'secret' => env('REVERB_APP_SECRET'),
'app_id' => env('REVERB_APP_ID'),
'options' => [
'host' => env('REVERB_HOST'),
'port' => env('REVERB_PORT', 443),
'scheme' => env('REVERB_SCHEME', 'https'),
'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
],
'client_options' => [
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
],
],
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
'port' => env('PUSHER_PORT', 443),
'scheme' => env('PUSHER_SCHEME', 'https'),
'encrypted' => true,
'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
],
'client_options' => [
// Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html
],
],
'ably' => [
'driver' => 'ably',
'key' => env('ABLY_KEY'),
],
'log' => [
'driver' => 'log',
],
'null' => [
'driver' => 'null',
],
],
];

95
config/reverb.php Normal file
View File

@ -0,0 +1,95 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Reverb Server
|--------------------------------------------------------------------------
|
| This option controls the default server used by Reverb to handle
| incoming messages as well as broadcasting message to all your
| connected clients. At this time only "reverb" is supported.
|
*/
'default' => env('REVERB_SERVER', 'reverb'),
/*
|--------------------------------------------------------------------------
| Reverb Servers
|--------------------------------------------------------------------------
|
| Here you may define details for each of the supported Reverb servers.
| Each server has its own configuration options that are defined in
| the array below. You should ensure all the options are present.
|
*/
'servers' => [
'reverb' => [
'host' => env('REVERB_SERVER_HOST', '0.0.0.0'),
'port' => env('REVERB_SERVER_PORT', 8080),
'path' => env('REVERB_SERVER_PATH', ''),
'hostname' => env('REVERB_HOST'),
'options' => [
'tls' => [],
],
'max_request_size' => env('REVERB_MAX_REQUEST_SIZE', 10_000),
'scaling' => [
'enabled' => env('REVERB_SCALING_ENABLED', false),
'channel' => env('REVERB_SCALING_CHANNEL', 'reverb'),
'server' => [
'url' => env('REDIS_URL'),
'host' => env('REDIS_HOST', '127.0.0.1'),
'port' => env('REDIS_PORT', '6379'),
'username' => env('REDIS_USERNAME'),
'password' => env('REDIS_PASSWORD'),
'database' => env('REDIS_DB', '0'),
'timeout' => env('REDIS_TIMEOUT', 60),
],
],
'pulse_ingest_interval' => env('REVERB_PULSE_INGEST_INTERVAL', 15),
'telescope_ingest_interval' => env('REVERB_TELESCOPE_INGEST_INTERVAL', 15),
],
],
/*
|--------------------------------------------------------------------------
| Reverb Applications
|--------------------------------------------------------------------------
|
| Here you may define how Reverb applications are managed. If you choose
| to use the "config" provider, you may define an array of apps which
| your server will support, including their connection credentials.
|
*/
'apps' => [
'provider' => 'config',
'apps' => [
[
'key' => env('REVERB_APP_KEY'),
'secret' => env('REVERB_APP_SECRET'),
'app_id' => env('REVERB_APP_ID'),
'options' => [
'host' => env('REVERB_HOST'),
'port' => env('REVERB_PORT', 443),
'scheme' => env('REVERB_SCHEME', 'https'),
'useTLS' => env('REVERB_SCHEME', 'https') === 'https',
],
'allowed_origins' => ['*'],
'ping_interval' => env('REVERB_APP_PING_INTERVAL', 60),
'activity_timeout' => env('REVERB_APP_ACTIVITY_TIMEOUT', 30),
'max_connections' => env('REVERB_APP_MAX_CONNECTIONS'),
'max_message_size' => env('REVERB_APP_MAX_MESSAGE_SIZE', 10_000),
],
],
],
];

View File

@ -35,4 +35,10 @@
], ],
], ],
'twilio' => [
'sid' => env('TWILIO_SID'),
'token' => env('TWILIO_AUTH_TOKEN'),
'from' => env('TWILIO_NUMBER'),
],
]; ];

44
config/webpush.php Normal file
View File

@ -0,0 +1,44 @@
<?php
return [
/**
* These are the keys for authentication (VAPID).
* These keys must be safely stored and should not change.
*/
'vapid' => [
'subject' => env('VAPID_SUBJECT'),
'public_key' => env('VAPID_PUBLIC_KEY'),
'private_key' => env('VAPID_PRIVATE_KEY'),
'pem_file' => env('VAPID_PEM_FILE'),
],
/**
* This is model that will be used to for push subscriptions.
*/
'model' => \NotificationChannels\WebPush\PushSubscription::class,
/**
* This is the name of the table that will be created by the migration and
* used by the PushSubscription model shipped with this package.
*/
'table_name' => env('WEBPUSH_DB_TABLE', 'push_subscriptions'),
/**
* This is the database connection that will be used by the migration and
* the PushSubscription model shipped with this package.
*/
'database_connection' => env('WEBPUSH_DB_CONNECTION', env('DB_CONNECTION', 'mysql')),
/**
* The Guzzle client options used by Minishlink\WebPush.
*/
'client_options' => [],
/**
* The automatic padding in bytes used by Minishlink\WebPush.
* Set to false to support Firefox Android with v1 endpoint.
*/
'automatic_padding' => env('WEBPUSH_AUTOMATIC_PADDING', true),
];

View File

@ -0,0 +1,32 @@
<?php
use App\Enums\UserTypes;
use App\Models\User;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('page_visits', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(User::class)->nullable();
$table->string('page');
$table->enum('user_type', UserTypes::values())->nullable();
$table->date('created_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('page_visits');
}
};

View File

@ -0,0 +1,26 @@
<?php
use App\Models\Deal;
use App\Models\User;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->string('text');
$table->foreignIdFor(Deal::class);
$table->foreignIdFor(User::class);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('comments');
}
};

View File

@ -0,0 +1,25 @@
<?php
use App\Models\Broker;
use App\Models\Customer;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('follows', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(Customer::class);
$table->foreignIdFor(Broker::class);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('follows');
}
};

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::connection(config('webpush.database_connection'))->create(config('webpush.table_name'), function (Blueprint $table) {
$table->bigIncrements('id');
$table->morphs('subscribable', 'push_subscriptions_subscribable_morph_idx');
$table->string('endpoint', 500)->unique();
$table->string('public_key')->nullable();
$table->string('auth_token')->nullable();
$table->string('content_encoding')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::connection(config('webpush.database_connection'))->dropIfExists(config('webpush.table_name'));
}
};

View File

@ -0,0 +1,32 @@
<?php
use App\Models\User;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('inboxes', function (Blueprint $table) {
$table->id()->index();
$table->text('last_message')->nullable();
$table->foreignIdFor(User::class, 'last_user_id')->nullable();
$table->timestamps();
$table->index(['last_user_id', 'created_at']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('inboxes');
}
};

View File

@ -0,0 +1,36 @@
<?php
use App\Models\Inbox;
use App\Models\User;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('messages', function (Blueprint $table) {
$table->id()->index();
$table->foreignIdFor(User::class)->index();
$table->foreignIdFor(Inbox::class)->index();
$table->text('message');
$table->time('read_at')->nullable();
$table->time('delivered_at')->nullable();
$table->time('deleted_at')->nullable();
$table->time('failed_at')->nullable();
$table->timestamp('created_at')->useCurrent()->index();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('messages');
}
};

View File

@ -0,0 +1,31 @@
<?php
use App\Models\Inbox;
use App\Models\User;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('inbox_user', function (Blueprint $table) {
$table->id()->index();
$table->foreignIdFor(Inbox::class)->index();
$table->foreignIdFor(User::class)->index();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('inbox_user');
}
};

View File

@ -0,0 +1,22 @@
<?php
namespace Database\Seeders;
use App\Models\DealCategory;
use Illuminate\Database\Seeder;
class CategorySeeder extends Seeder
{
public function run(): void
{
/**
* @var array<int, array{name: string, description: string, slug:string, active:bool, order:int}> $categories
*/
$categories = [
['name' => 'Food', 'description' => 'Share Food related deals', 'slug' => 'food', 'active' => true, 'order' => 1],
['name' => 'Real Estate', 'description' => 'Share Real Estate related deals', 'slug' => 'real-estate', 'active' => true, 'order' => 2],
['name' => 'Sell & Deal', 'description' => 'Sell your services', 'slug' => 'sell-and-deal', 'active' => true, 'order' => 3],
];
DealCategory::insert($categories);
}
}

182
package-lock.json generated
View File

@ -1,14 +1,19 @@
{ {
"name": "DealHub", "name": "dealhub",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"dependencies": {
"chart.js": "^4.5.1"
},
"devDependencies": { "devDependencies": {
"@tailwindcss/vite": "^4.0.0", "@tailwindcss/vite": "^4.0.0",
"axios": "^1.11.0", "axios": "^1.11.0",
"concurrently": "^9.0.1", "concurrently": "^9.0.1",
"laravel-echo": "^2.3.0",
"laravel-vite-plugin": "^2.0.0", "laravel-vite-plugin": "^2.0.0",
"pusher-js": "^8.4.0",
"tailwindcss": "^4.0.0", "tailwindcss": "^4.0.0",
"vite": "^7.0.7" "vite": "^7.0.7"
} }
@ -505,6 +510,12 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@kurkle/color": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.55.1", "version": "4.55.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz",
@ -855,6 +866,14 @@
"win32" "win32"
] ]
}, },
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/@tailwindcss/node": { "node_modules/@tailwindcss/node": {
"version": "4.1.18", "version": "4.1.18",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz",
@ -1223,6 +1242,18 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/chart.js": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
"integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
"license": "MIT",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
"engines": {
"pnpm": ">=8"
}
},
"node_modules/cliui": { "node_modules/cliui": {
"version": "8.0.1", "version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
@ -1296,6 +1327,25 @@
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1" "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
} }
}, },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/delayed-stream": { "node_modules/delayed-stream": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@ -1338,6 +1388,32 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/engine.io-client": {
"version": "6.6.4",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz",
"integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.4.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.18.3",
"xmlhttprequest-ssl": "~2.1.1"
}
},
"node_modules/engine.io-parser": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/enhanced-resolve": { "node_modules/enhanced-resolve": {
"version": "5.18.4", "version": "5.18.4",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz",
@ -1675,6 +1751,20 @@
"jiti": "lib/jiti-cli.mjs" "jiti": "lib/jiti-cli.mjs"
} }
}, },
"node_modules/laravel-echo": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-2.3.0.tgz",
"integrity": "sha512-wgHPnnBvfHmu2I58xJ4asZH37Nu6P0472ku6zuoGRLc3zEWwIbpovDLYTiOshDH1SM7rA6AjZTKuu+jYoM1tpQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=20"
},
"peerDependencies": {
"pusher-js": "*",
"socket.io-client": "*"
}
},
"node_modules/laravel-vite-plugin": { "node_modules/laravel-vite-plugin": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.0.1.tgz", "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.0.1.tgz",
@ -1999,6 +2089,14 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT",
"peer": true
},
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.11", "version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@ -2074,6 +2172,16 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/pusher-js": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.4.0.tgz",
"integrity": "sha512-wp3HqIIUc1GRyu1XrP6m2dgyE9MoCsXVsWNlohj0rjSkLf+a0jLvEyVubdg58oMk7bhjBWnFClgp8jfAa6Ak4Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"tweetnacl": "^1.0.3"
}
},
"node_modules/require-directory": { "node_modules/require-directory": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@ -2152,6 +2260,38 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/socket.io-client": {
"version": "4.8.3",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz",
"integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.4.1",
"engine.io-client": "~6.6.1",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.5",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz",
"integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.4.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@ -2261,6 +2401,13 @@
"dev": true, "dev": true,
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/tweetnacl": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
"dev": true,
"license": "Unlicense"
},
"node_modules/vite": { "node_modules/vite": {
"version": "7.3.1", "version": "7.3.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
@ -2378,6 +2525,39 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1" "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
} }
}, },
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz",
"integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==",
"dev": true,
"peer": true,
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/y18n": { "node_modules/y18n": {
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",

View File

@ -10,8 +10,13 @@
"@tailwindcss/vite": "^4.0.0", "@tailwindcss/vite": "^4.0.0",
"axios": "^1.11.0", "axios": "^1.11.0",
"concurrently": "^9.0.1", "concurrently": "^9.0.1",
"laravel-echo": "^2.3.0",
"laravel-vite-plugin": "^2.0.0", "laravel-vite-plugin": "^2.0.0",
"pusher-js": "^8.4.0",
"tailwindcss": "^4.0.0", "tailwindcss": "^4.0.0",
"vite": "^7.0.7" "vite": "^7.0.7"
},
"dependencies": {
"chart.js": "^4.5.1"
} }
} }

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
public/favicon-96x96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 0 B

After

Width:  |  Height:  |  Size: 15 KiB

1
public/favicon.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 45 KiB

20
public/service-worker.js Normal file
View File

@ -0,0 +1,20 @@
self.addEventListener('push', function (e) {
if (!(self.Notification && self.Notification.permission === 'granted')) {
//notifications aren't supported or permission not granted!
return;
}
if (e.data) {
let msg = '';
try {
msg = e.data.json();
}catch (e){
msg = e.data;
}
console.log(msg)
e.waitUntil(self.registration.showNotification(msg.title, {
body: msg.body,
actions: msg.actions
}));
}
});

21
public/site.webmanifest Normal file
View File

@ -0,0 +1,21 @@
{
"name": "MyWebSite",
"short_name": "MySite",
"icons": [
{
"src": "/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

1
public/vendor/log-viewer/app.css vendored Normal file

File diff suppressed because one or more lines are too long

2
public/vendor/log-viewer/app.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,33 @@
/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <http://feross.org>
* @license MIT
*/
/*!
* pinia v2.2.2
* (c) 2024 Eduardo San Martin Morote
* @license MIT
*/
/*! #__NO_SIDE_EFFECTS__ */
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
/**
* @license
* Lodash <https://lodash.com/>
* Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
* Released under MIT license <https://lodash.com/license>
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
*/
/**
* @vue/shared v3.4.38
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**/

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 970 B

View File

@ -0,0 +1,7 @@
{
"/app.js": "/app.js?id=b5eb6497b80ecd00237a857b35fcc1d6",
"/app.css": "/app.css?id=bf9e77abce3da8caacd004d57e4e8429",
"/img/log-viewer-128.png": "/img/log-viewer-128.png?id=d576c6d2e16074d3f064e60fe4f35166",
"/img/log-viewer-32.png": "/img/log-viewer-32.png?id=f8ec67d10f996aa8baf00df3b61eea6d",
"/img/log-viewer-64.png": "/img/log-viewer-64.png?id=8902d596fc883ca9eb8105bb683568c6"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Some files were not shown because too many files have changed in this diff Show More