feature(external link redirection count of deal):

add a controller which increments the count for the click of external link of a deal.
This commit is contained in:
kusowl 2026-01-19 15:52:55 +05:30
parent 688fd02e26
commit af6d629b68
9 changed files with 100 additions and 28 deletions

View File

@ -18,7 +18,7 @@ class InteractionController extends Controller
*/
public function togglesState(Deal $deal, InteractionType $type)
{
if (! in_array($type, [InteractionType::Like, InteractionType::Favorite])) {
if (!in_array($type, [InteractionType::Like, InteractionType::Favorite])) {
return response()->json(['error' => 'This interaction is not supported'], 400);
}
@ -53,7 +53,7 @@ public function togglesState(Deal $deal, InteractionType $type)
Log::error('Error when liked a deal',
[
'deal_id' => $deal->id,
'use_id' => Auth::id(),
'user_id' => Auth::id(),
'type' => $type,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
@ -63,4 +63,37 @@ public function togglesState(Deal $deal, InteractionType $type)
return response()->json(['error' => 'Something went wrong.'], 500);
}
}
public function redirect(Deal $deal)
{
if (blank($deal->link)) {
abort(404);
}
\Illuminate\Support\defer(function () use ($deal) {
try {
$interaction = $deal->interactions()->firstOrCreate([
'type' => InteractionType::Redirection,
'user_id' => Auth::id(),
]);
if (!$interaction->wasRecentlyCreated) {
$interaction->increment('count');
}
} catch (\Throwable $e) {
Log::error('Error when redirecting a deal external link',
[
'deal_id' => $deal->id,
'user_id' => Auth::id(),
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]
);
abort(500);
}
});
return redirect()->away($deal->link);
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class DealOutboundRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@ -8,6 +8,11 @@
class Interaction extends Model
{
protected $fillable = [
'type',
'user_id',
];
public function user(): BelongsTo
{
return $this->belongsTo(Deal::class);

View File

@ -1,14 +1,14 @@
@props(['id' => '', 'image' => '', 'title' => '', 'category' => '', 'impressions' => 0, 'likes' => 0, 'clicks' => 0, 'status' => false, 'external_link' => ''])
@props(['deal'])
<x-ui.image-card :image="$image">
<x-ui.image-card :image="asset('storage/'.$deal->image)">
<div class="bg-white pt-8 p-4 h-full space-y-4 flex flex-col justify-between">
<div class="flex justify-between items-start">
<div class="flex items-start space-x-1 mr-2">
<p class="font-bold text-lg ">{{$title}}</p>
<p class="font-bold text-lg ">{{$deal->title}}</p>
@if($external_link !== '')
<a href="{{$external_link}}" target="_blank" title="{{$external_link}}"
@if(filled($deal->link))
<a href="{{\Illuminate\Support\Facades\URL::signedRoute('redirect', $deal->id)}}" target="_blank" title="{{$deal->link}}"
class="text-xs underline text-accent-601 mt-1">
<x-heroicon-o-arrow-top-right-on-square class="w-4 stroke-2 "/>
</a>
@ -16,7 +16,7 @@ class="text-xs underline text-accent-601 mt-1">
</div>
@if($status == 1)
@if($deal->active == 1)
<x-ui.badge title="Active"/>
@else
<x-ui.badge title="Pending" variant="ghost"/>
@ -25,15 +25,15 @@ class="text-xs underline text-accent-601 mt-1">
</div>
<div class="flex flex-col space-y-4">
<p class="text-accent-600">{{$category}}</p>
<p class="text-accent-600">{{$deal->category->name}}</p>
<x-dashboard.broker.listing-stats :impression="$impressions" :likes="$likes" :clicks="$clicks"/>
<x-dashboard.broker.listing-stats impression="0" likes="0" clicks="0"/>
<div class="flex justify-between space-x-4">
<x-ui.button :link="route('broker.deals.edit', $id)" class="w-full border border-accent-600/30"
<x-ui.button :link="route('broker.deals.edit', $deal->id)" class="w-full border border-accent-600/30"
icon="pencil-square">Edit
</x-ui.button>
<form class="w-full" onsubmit="return confirm('Are you sure to delete this ?')" method="post" action="{{route('broker.deals.destroy', $id)}}">
<form class="w-full" onsubmit="return confirm('Are you sure to delete this ?')" method="post" action="{{route('broker.deals.destroy', $deal->id)}}">
@csrf
@method("DELETE")
<x-ui.button variant="red" class="w-full" icon="trash" >Delete</x-ui.button>

View File

@ -4,17 +4,7 @@
<p class="font-bold mb-6">My Listings</p>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
@forelse($deals as $deal)
<x-dashboard.broker.listing-card
:id="$deal->id"
:image="asset('storage/'.$deal->image)"
:title="$deal->title"
:category="$deal->category->name"
:status="$deal->active"
impressions="1245"
likes="89"
class="156"
:external_link="$deal->link"
/>
<x-dashboard.broker.listing-card :deal="$deal" />
@empty
<p class="text-center text-xs text-accent-600 lg:col-span-3">No Deals created</p>
@endforelse

View File

@ -1,5 +1,15 @@
@props(['deal_id', 'deal_title', 'like' => false, 'favourite' => false])
@props(['deal_id', 'deal_title', 'like' => false, 'favourite' => false, 'deal_link' => ''])
<div class="">
@if(filled($deal_link))
<x-ui.button-sm @class(["text-accent-600"]) external="true" :link="\Illuminate\Support\Facades\URL::signedRoute('redirect', $deal_id)">
<x-heroicon-o-arrow-top-right-on-square
@class([
"w-4",
])
/>
</x-ui.button-sm>
@endif
<x-ui.button-sm @class(["text-accent-600", 'liked' => $like]) onclick="like(this, {{$deal_id}})">
<x-heroicon-o-heart
@class([

View File

@ -6,7 +6,7 @@
{{$deal->category->name}}
</x-ui.button-sm>
@ds($deal)
<x-dashboard.user.action-toolbar :deal_title="$deal->title" :deal_id="$deal->id" :like="$deal->is_liked" :favourite="$deal->is_favorite" />
<x-dashboard.user.action-toolbar :deal_link="$deal->link" :deal_title="$deal->title" :deal_id="$deal->id" :like="$deal->is_liked" :favourite="$deal->is_favorite" />
</div>
<p class="font-bold text-lg ">{{$deal->title}}</p>

View File

@ -1,4 +1,4 @@
@props(['variant' => '', 'link' => ''])
@props(['variant' => '', 'link' => '', 'external' => false])
@php
$variants = [
'neutral' => 'bg-primary-600 text-white',
@ -8,7 +8,7 @@
$variantClass = $variants[$variant] ?? '';
@endphp
@if($link !== '')
<a href="{{$link}}" {{$attributes->merge(['class' => "px-2 py-1 text-xs rounded-md font-medium hover:opacity-80 hover: active:scale-80 transition-all duration-300 ease-in-out $variantClass"])}}>
<a href="{{$link}}" @if($external) target="_blank" @endif {{$attributes->merge(['class' => "inline-flex px-2 py-1 text-xs rounded-md font-medium hover:opacity-80 hover: active:scale-80 transition-all duration-300 ease-in-out $variantClass"])}}>
{{$slot}}
</a>
@else

View File

@ -17,5 +17,11 @@
->middleware('throttle:30,1')
->name('favorite');
Route::post('/report/{deal}', [ReportController::class, 'store'])->name('report.store');
Route::post('/report/{deal}', [ReportController::class, 'store'])
->middleware('throttle:5,1')
->name('report.store');
Route::get('/redirect/{deal}', [InteractionController::class, 'redirect'])
->middleware(['throttle:10,1', 'signed'])
->name('redirect');
});