Harden
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Attachment;
|
||||
use App\Models\Dispute;
|
||||
use App\Models\JobPost;
|
||||
use App\Models\User;
|
||||
use App\Models\WorkOrder;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class AdminController extends Controller
|
||||
{
|
||||
public function index(Request $request): View
|
||||
{
|
||||
abort_unless($request->user()->hasRole('admin'), 403);
|
||||
|
||||
return view('admin.index', [
|
||||
'counts' => [
|
||||
'users' => User::count(),
|
||||
'jobs' => JobPost::count(),
|
||||
'work_orders' => WorkOrder::count(),
|
||||
'disputes' => Dispute::count(),
|
||||
'attachments' => Attachment::count(),
|
||||
],
|
||||
'users' => User::with('roles')->latest()->limit(10)->get(),
|
||||
'jobs' => JobPost::with('buyer')->latest()->limit(10)->get(),
|
||||
'workOrders' => WorkOrder::with('jobPost', 'buyer', 'provider')->latest()->limit(10)->get(),
|
||||
'disputes' => Dispute::with('workOrder.jobPost', 'openedBy')->latest()->limit(10)->get(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Dispute;
|
||||
use App\Models\ExternalProfileImport;
|
||||
use App\Models\JobPost;
|
||||
use App\Models\ProviderProfile;
|
||||
use App\Models\SocialPost;
|
||||
use App\Models\WorkOrder;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class AttachmentController extends Controller
|
||||
{
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'attachable_type' => ['required', 'string', 'in:provider_profile,buyer_profile,social_post,job_post,work_order,dispute,external_profile_import'],
|
||||
'attachable_id' => ['required', 'integer'],
|
||||
'kind' => ['nullable', 'string', 'max:255'],
|
||||
'caption' => ['nullable', 'string', 'max:1000'],
|
||||
'file' => ['required', 'file', 'max:10240'],
|
||||
]);
|
||||
|
||||
$attachable = $this->resolveAttachable($data['attachable_type'], (int) $data['attachable_id']);
|
||||
$this->authorizeAttachment($request, $attachable);
|
||||
|
||||
$file = $request->file('file');
|
||||
$path = $file->store('attachments/'.Str::slug($data['attachable_type']), 'public');
|
||||
|
||||
$attachable->attachments()->create([
|
||||
'user_id' => $request->user()->id,
|
||||
'kind' => $data['kind'] ?? 'general',
|
||||
'disk' => 'public',
|
||||
'path' => $path,
|
||||
'original_name' => $file->getClientOriginalName(),
|
||||
'mime_type' => $file->getClientMimeType(),
|
||||
'size' => $file->getSize() ?: 0,
|
||||
'caption' => $data['caption'] ?? null,
|
||||
]);
|
||||
|
||||
return back()->with('status', 'File uploaded.');
|
||||
}
|
||||
|
||||
private function resolveAttachable(string $type, int $id): mixed
|
||||
{
|
||||
return match ($type) {
|
||||
'provider_profile' => ProviderProfile::findOrFail($id),
|
||||
'buyer_profile' => \App\Models\BuyerProfile::findOrFail($id),
|
||||
'social_post' => SocialPost::findOrFail($id),
|
||||
'job_post' => JobPost::findOrFail($id),
|
||||
'work_order' => WorkOrder::findOrFail($id),
|
||||
'dispute' => Dispute::findOrFail($id),
|
||||
'external_profile_import' => ExternalProfileImport::findOrFail($id),
|
||||
};
|
||||
}
|
||||
|
||||
private function authorizeAttachment(Request $request, mixed $attachable): void
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
if ($attachable instanceof ProviderProfile) {
|
||||
abort_unless($attachable->user_id === $user->id || $user->hasRole('admin'), 403);
|
||||
} elseif ($attachable instanceof \App\Models\BuyerProfile) {
|
||||
abort_unless($attachable->user_id === $user->id || $user->hasRole('admin'), 403);
|
||||
} elseif ($attachable instanceof SocialPost) {
|
||||
abort_unless($attachable->user_id === $user->id || $user->hasRole('admin'), 403);
|
||||
} elseif ($attachable instanceof JobPost) {
|
||||
abort_unless($attachable->buyer_id === $user->id || $user->hasRole('admin'), 403);
|
||||
} elseif ($attachable instanceof WorkOrder) {
|
||||
abort_unless(in_array($user->id, [$attachable->buyer_id, $attachable->provider_id], true) || $user->hasRole('admin'), 403);
|
||||
} elseif ($attachable instanceof Dispute) {
|
||||
abort_unless(in_array($user->id, [$attachable->workOrder->buyer_id, $attachable->workOrder->provider_id], true) || $user->hasRole('admin'), 403);
|
||||
} elseif ($attachable instanceof ExternalProfileImport) {
|
||||
abort_unless($attachable->providerProfile->user_id === $user->id || $user->hasRole('admin'), 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ class BuyerProfileController extends Controller
|
||||
public function index(): View
|
||||
{
|
||||
return view('buyers.index', [
|
||||
'profiles' => BuyerProfile::with('user')->latest()->paginate(20),
|
||||
'profiles' => BuyerProfile::with('user', 'attachments')->latest()->paginate(20),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ class BuyerProfileController extends Controller
|
||||
abort_unless($request->user()->hasRole('buyer'), 403);
|
||||
|
||||
return view('buyers.edit', [
|
||||
'profile' => $request->user()->buyerProfile,
|
||||
'profile' => $request->user()->buyerProfile?->load('attachments'),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ class BuyerProfileController extends Controller
|
||||
public function show(BuyerProfile $buyer): View
|
||||
{
|
||||
return view('buyers.show', [
|
||||
'profile' => $buyer->load('user'),
|
||||
'profile' => $buyer->load('user', 'attachments'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Dispute;
|
||||
use App\Models\JobPost;
|
||||
use App\Models\SocialPost;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class CommentController extends Controller
|
||||
{
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'commentable_type' => ['required', 'string', 'in:social_post,job_post,dispute'],
|
||||
'commentable_id' => ['required', 'integer'],
|
||||
'body' => ['required', 'string', 'max:5000'],
|
||||
]);
|
||||
|
||||
$commentable = match ($data['commentable_type']) {
|
||||
'social_post' => SocialPost::findOrFail($data['commentable_id']),
|
||||
'job_post' => JobPost::findOrFail($data['commentable_id']),
|
||||
'dispute' => Dispute::findOrFail($data['commentable_id']),
|
||||
};
|
||||
|
||||
if ($commentable instanceof Dispute) {
|
||||
abort_unless(
|
||||
in_array($request->user()->id, [$commentable->workOrder->buyer_id, $commentable->workOrder->provider_id], true)
|
||||
|| $request->user()->hasRole('admin'),
|
||||
403
|
||||
);
|
||||
}
|
||||
|
||||
$commentable->comments()->create([
|
||||
'user_id' => $request->user()->id,
|
||||
'body' => $data['body'],
|
||||
]);
|
||||
|
||||
return back()->with('status', 'Comment saved.');
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Dispute;
|
||||
use App\Models\WorkOrder;
|
||||
use App\Notifications\ExchangeEventNotification;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
@@ -12,7 +13,7 @@ class DisputeController extends Controller
|
||||
{
|
||||
public function show(Request $request, Dispute $dispute): View
|
||||
{
|
||||
$dispute->load('workOrder.buyer', 'workOrder.provider', 'openedBy');
|
||||
$dispute->load('workOrder.buyer', 'workOrder.provider', 'openedBy', 'votes.user', 'comments.user', 'attachments');
|
||||
|
||||
abort_unless(
|
||||
in_array($request->user()->id, [$dispute->workOrder->buyer_id, $dispute->workOrder->provider_id], true)
|
||||
@@ -40,6 +41,17 @@ class DisputeController extends Controller
|
||||
|
||||
$workOrder->update(['status' => 'disputed']);
|
||||
|
||||
$recipient = $request->user()->id === $workOrder->buyer_id
|
||||
? $workOrder->provider
|
||||
: $workOrder->buyer;
|
||||
|
||||
$recipient->notify(new ExchangeEventNotification(
|
||||
'Dispute opened',
|
||||
$request->user()->name.' opened a dispute on '.$workOrder->jobPost->title.'.',
|
||||
route('disputes.show', $dispute),
|
||||
'dispute_opened'
|
||||
));
|
||||
|
||||
return redirect()->route('disputes.show', $dispute)->with('status', 'Dispute opened for peer review.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Dispute;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class DisputeVoteController extends Controller
|
||||
{
|
||||
public function store(Request $request, Dispute $dispute): RedirectResponse
|
||||
{
|
||||
abort_unless($request->user()->hasAnyRole(['provider', 'buyer', 'admin']), 403);
|
||||
|
||||
$data = $request->validate([
|
||||
'recommendation' => ['required', 'string', 'in:provider,buyer,split,insufficient_evidence'],
|
||||
'reason' => ['nullable', 'string', 'max:2000'],
|
||||
]);
|
||||
|
||||
$dispute->votes()->updateOrCreate(
|
||||
['user_id' => $request->user()->id],
|
||||
$data
|
||||
);
|
||||
|
||||
return redirect()->route('disputes.show', $dispute)->with('status', 'Peer vote saved.');
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ class JobPostController extends Controller
|
||||
public function index(): View
|
||||
{
|
||||
return view('jobs.index', [
|
||||
'jobs' => JobPost::with('buyer.buyerProfile')->latest()->paginate(20),
|
||||
'jobs' => JobPost::with('buyer.buyerProfile', 'attachments')->latest()->paginate(20),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,14 @@ class JobPostController extends Controller
|
||||
public function show(JobPost $job): View
|
||||
{
|
||||
return view('jobs.show', [
|
||||
'job' => $job->load('buyer.buyerProfile', 'quotes.provider.providerProfile', 'workOrder'),
|
||||
'job' => $job->load(
|
||||
'buyer.buyerProfile',
|
||||
'quotes.provider.providerProfile',
|
||||
'quotes.revisions.user',
|
||||
'workOrder',
|
||||
'attachments',
|
||||
'comments.user'
|
||||
),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class ProviderProfileController extends Controller
|
||||
public function index(): View
|
||||
{
|
||||
return view('providers.index', [
|
||||
'profiles' => ProviderProfile::with('user', 'externalImports')->latest()->paginate(20),
|
||||
'profiles' => ProviderProfile::with('user', 'externalImports', 'attachments')->latest()->paginate(20),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ class ProviderProfileController extends Controller
|
||||
abort_unless($request->user()->hasRole('provider'), 403);
|
||||
|
||||
return view('providers.edit', [
|
||||
'profile' => $request->user()->providerProfile,
|
||||
'profile' => $request->user()->providerProfile?->load('attachments', 'externalImports.attachments'),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ class ProviderProfileController extends Controller
|
||||
public function show(ProviderProfile $provider): View
|
||||
{
|
||||
return view('providers.show', [
|
||||
'profile' => $provider->load('user', 'externalImports'),
|
||||
'profile' => $provider->load('user', 'externalImports.attachments', 'attachments'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
||||
use App\Models\JobPost;
|
||||
use App\Models\Quote;
|
||||
use App\Models\WorkOrder;
|
||||
use App\Notifications\ExchangeEventNotification;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
@@ -23,14 +24,80 @@ class QuoteController extends Controller
|
||||
'terms' => ['nullable', 'string'],
|
||||
]);
|
||||
|
||||
$job->quotes()->create($data + [
|
||||
$quote = $job->quotes()->create($data + [
|
||||
'provider_id' => $request->user()->id,
|
||||
'status' => 'submitted',
|
||||
]);
|
||||
|
||||
$quote->revisions()->create($data + [
|
||||
'user_id' => $request->user()->id,
|
||||
'action' => 'submitted',
|
||||
]);
|
||||
|
||||
$job->buyer->notify(new ExchangeEventNotification(
|
||||
'New quote received',
|
||||
$request->user()->name.' submitted a quote for '.$job->title.'.',
|
||||
route('jobs.show', $job),
|
||||
'quote_submitted'
|
||||
));
|
||||
|
||||
return redirect()->route('jobs.show', $job)->with('status', 'Quote submitted.');
|
||||
}
|
||||
|
||||
public function update(Request $request, Quote $quote): RedirectResponse
|
||||
{
|
||||
$quote->load('jobPost');
|
||||
|
||||
abort_unless($quote->provider_id === $request->user()->id, 403);
|
||||
abort_unless(in_array($quote->status, ['submitted', 'countered', 'revised'], true), 422);
|
||||
abort_unless($quote->jobPost->status === 'open', 422);
|
||||
|
||||
$data = $request->validate([
|
||||
'requested_amount' => ['nullable', 'numeric', 'min:0'],
|
||||
'rate_summary' => ['nullable', 'string', 'max:255'],
|
||||
'message' => ['nullable', 'string'],
|
||||
'terms' => ['nullable', 'string'],
|
||||
]);
|
||||
|
||||
$quote->update($data + ['status' => 'revised']);
|
||||
$quote->revisions()->create($data + [
|
||||
'user_id' => $request->user()->id,
|
||||
'action' => 'revised',
|
||||
]);
|
||||
|
||||
$quote->jobPost->buyer->notify(new ExchangeEventNotification(
|
||||
'Quote revised',
|
||||
$request->user()->name.' revised a quote for '.$quote->jobPost->title.'.',
|
||||
route('jobs.show', $quote->jobPost),
|
||||
'quote_revised'
|
||||
));
|
||||
|
||||
return redirect()->route('jobs.show', $quote->jobPost)->with('status', 'Quote revised.');
|
||||
}
|
||||
|
||||
public function decline(Request $request, Quote $quote): RedirectResponse
|
||||
{
|
||||
$quote->load('jobPost');
|
||||
|
||||
abort_unless($quote->jobPost->buyer_id === $request->user()->id, 403);
|
||||
abort_unless($quote->jobPost->status === 'open', 422);
|
||||
|
||||
$quote->update(['status' => 'declined']);
|
||||
$quote->revisions()->create([
|
||||
'user_id' => $request->user()->id,
|
||||
'action' => 'declined',
|
||||
]);
|
||||
|
||||
$quote->provider->notify(new ExchangeEventNotification(
|
||||
'Quote declined',
|
||||
$quote->jobPost->buyer->name.' declined your quote for '.$quote->jobPost->title.'.',
|
||||
route('jobs.show', $quote->jobPost),
|
||||
'quote_declined'
|
||||
));
|
||||
|
||||
return redirect()->route('jobs.show', $quote->jobPost)->with('status', 'Quote declined.');
|
||||
}
|
||||
|
||||
public function accept(Request $request, Quote $quote): RedirectResponse
|
||||
{
|
||||
$quote->load('jobPost');
|
||||
@@ -39,6 +106,10 @@ class QuoteController extends Controller
|
||||
abort_unless($quote->jobPost->status === 'open', 422);
|
||||
|
||||
$quote->update(['status' => 'accepted']);
|
||||
$quote->revisions()->create([
|
||||
'user_id' => $request->user()->id,
|
||||
'action' => 'accepted',
|
||||
]);
|
||||
$quote->jobPost->quotes()->whereKeyNot($quote->id)->update(['status' => 'declined']);
|
||||
$quote->jobPost->update(['status' => 'assigned']);
|
||||
|
||||
@@ -57,6 +128,13 @@ class QuoteController extends Controller
|
||||
]],
|
||||
]);
|
||||
|
||||
$quote->provider->notify(new ExchangeEventNotification(
|
||||
'Quote accepted',
|
||||
$quote->jobPost->buyer->name.' accepted your quote for '.$quote->jobPost->title.'.',
|
||||
route('work-orders.show', $workOrder),
|
||||
'quote_accepted'
|
||||
));
|
||||
|
||||
return redirect()->route('work-orders.show', $workOrder)->with('status', 'Quote accepted and work order created.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\WorkOrder;
|
||||
use App\Notifications\ExchangeEventNotification;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
@@ -18,6 +19,11 @@ class ReviewController extends Controller
|
||||
|
||||
$data = $request->validate([
|
||||
'rating' => ['required', 'integer', 'min:1', 'max:5'],
|
||||
'communication_rating' => ['nullable', 'integer', 'min:1', 'max:5'],
|
||||
'scope_accuracy_rating' => ['nullable', 'integer', 'min:1', 'max:5'],
|
||||
'payment_reliability_rating' => ['nullable', 'integer', 'min:1', 'max:5'],
|
||||
'workmanship_rating' => ['nullable', 'integer', 'min:1', 'max:5'],
|
||||
'timeliness_rating' => ['nullable', 'integer', 'min:1', 'max:5'],
|
||||
'body' => ['nullable', 'string'],
|
||||
]);
|
||||
|
||||
@@ -31,6 +37,17 @@ class ReviewController extends Controller
|
||||
]
|
||||
);
|
||||
|
||||
$reviewee = $request->user()->id === $workOrder->buyer_id
|
||||
? $workOrder->provider
|
||||
: $workOrder->buyer;
|
||||
|
||||
$reviewee->notify(new ExchangeEventNotification(
|
||||
'New review received',
|
||||
$request->user()->name.' reviewed a completed work order.',
|
||||
route('work-orders.show', $workOrder),
|
||||
'review_received'
|
||||
));
|
||||
|
||||
return redirect()->route('work-orders.show', $workOrder)->with('status', 'Review saved.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class SocialPostController extends Controller
|
||||
public function index(): View
|
||||
{
|
||||
return view('feed.index', [
|
||||
'posts' => SocialPost::with('user')->latest()->paginate(20),
|
||||
'posts' => SocialPost::with('user', 'attachments', 'comments.user')->latest()->paginate(20),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\WorkOrder;
|
||||
use App\Notifications\ExchangeEventNotification;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
@@ -34,7 +35,9 @@ class WorkOrderController extends Controller
|
||||
'acceptedQuote',
|
||||
'reviews.reviewer',
|
||||
'reviews.reviewee',
|
||||
'disputes.openedBy'
|
||||
'disputes.openedBy',
|
||||
'messages.user',
|
||||
'attachments',
|
||||
),
|
||||
]);
|
||||
}
|
||||
@@ -49,6 +52,9 @@ class WorkOrderController extends Controller
|
||||
]);
|
||||
|
||||
$status = $data['status'];
|
||||
abort_unless($workOrder->canTransitionTo($status), 422, 'This status transition is not allowed.');
|
||||
$this->authorizeTransitionRole($request, $workOrder, $status);
|
||||
|
||||
$timestamps = [
|
||||
'en_route' => 'en_route_at',
|
||||
'on_site' => 'on_site_at',
|
||||
@@ -77,6 +83,17 @@ class WorkOrderController extends Controller
|
||||
|
||||
$workOrder->update($updates);
|
||||
|
||||
$recipient = $request->user()->id === $workOrder->buyer_id
|
||||
? $workOrder->provider
|
||||
: $workOrder->buyer;
|
||||
|
||||
$recipient->notify(new ExchangeEventNotification(
|
||||
'Work order updated',
|
||||
$request->user()->name.' changed '.$workOrder->jobPost->title.' to '.str_replace('_', ' ', $status).'.',
|
||||
route('work-orders.show', $workOrder),
|
||||
'work_order_status'
|
||||
));
|
||||
|
||||
return redirect()->route('work-orders.show', $workOrder)->with('status', 'Work order updated.');
|
||||
}
|
||||
|
||||
@@ -88,4 +105,21 @@ class WorkOrderController extends Controller
|
||||
403
|
||||
);
|
||||
}
|
||||
|
||||
private function authorizeTransitionRole(Request $request, WorkOrder $workOrder, string $status): void
|
||||
{
|
||||
$userId = $request->user()->id;
|
||||
|
||||
$providerStatuses = ['en_route', 'on_site', 'in_progress', 'completed'];
|
||||
$buyerStatuses = ['buyer_approved', 'closed', 'cancelled'];
|
||||
$sharedStatuses = ['disputed'];
|
||||
|
||||
if (in_array($status, $providerStatuses, true)) {
|
||||
abort_unless($workOrder->provider_id === $userId || $request->user()->hasRole('admin'), 403);
|
||||
} elseif (in_array($status, $buyerStatuses, true)) {
|
||||
abort_unless($workOrder->buyer_id === $userId || $request->user()->hasRole('admin'), 403);
|
||||
} elseif (in_array($status, $sharedStatuses, true)) {
|
||||
abort_unless(in_array($userId, [$workOrder->buyer_id, $workOrder->provider_id], true) || $request->user()->hasRole('admin'), 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\WorkOrder;
|
||||
use App\Notifications\ExchangeEventNotification;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class WorkOrderMessageController extends Controller
|
||||
{
|
||||
public function store(Request $request, WorkOrder $workOrder): RedirectResponse
|
||||
{
|
||||
abort_unless(
|
||||
in_array($request->user()->id, [$workOrder->buyer_id, $workOrder->provider_id], true)
|
||||
|| $request->user()->hasRole('admin'),
|
||||
403
|
||||
);
|
||||
|
||||
$data = $request->validate([
|
||||
'body' => ['required', 'string', 'max:5000'],
|
||||
]);
|
||||
|
||||
$workOrder->messages()->create([
|
||||
'user_id' => $request->user()->id,
|
||||
'body' => $data['body'],
|
||||
]);
|
||||
|
||||
$recipient = $request->user()->id === $workOrder->buyer_id
|
||||
? $workOrder->provider
|
||||
: $workOrder->buyer;
|
||||
|
||||
$recipient->notify(new ExchangeEventNotification(
|
||||
'New work-order message',
|
||||
$request->user()->name.' sent a message on '.$workOrder->jobPost->title.'.',
|
||||
route('work-orders.show', $workOrder),
|
||||
'work_order_message'
|
||||
));
|
||||
|
||||
return redirect()->route('work-orders.show', $workOrder)->with('status', 'Message sent.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user