authorize('viewAny',SerpCheck::class);$projects=$r->user()->projects()->orderBy('name')->get(['id','ulid','name']);$keywords=Keyword::query()->whereHas('project',fn($q)=>$q->where('user_id',$r->user()->id))->with('project:id,ulid,name')->orderBy('phrase')->get();if($r->filled('project')){$project=$projects->firstWhere('ulid',$r->input('project'));abort_unless($project,404);$keywords=$keywords->where('project_id',$project->id)->values();}if($r->filled('keyword')){abort_unless($keywords->contains('ulid',$r->input('keyword')),404);$keywords=$keywords->where('ulid',$r->input('keyword'))->values();}$q=SerpCheck::query()->whereIn('keyword_id',$keywords->pluck('id'))->with(['keyword.project','results'=>fn($x)=>$x->where('result_type','organic')->where('is_project_domain',true)->orderBy('position')]);if($r->filled('status')){abort_unless(in_array($r->input('status'),array_column(SerpCheckStatus::cases(),'value'),true),422);$q->where('status',$r->input('status'));}if($r->filled('device')){$r->validate(['device'=>[Rule::in(['desktop','mobile'])]]);$q->where('device',$r->input('device'));}if($r->filled('from'))$q->whereDate('created_at','>=',$r->date('from'));if($r->filled('to'))$q->whereDate('created_at','<=',$r->date('to'));if($search=trim($r->string('q')->toString()))$q->where('phrase','like','%'.addcslashes($search,'%_').'%');$all=(clone $q)->get();$summary=['total'=>$all->count(),'pending'=>$all->where('status',SerpCheckStatus::Pending)->count(),'running'=>$all->where('status',SerpCheckStatus::Running)->count(),'completed'=>$all->where('status',SerpCheckStatus::Completed)->count(),'failed'=>$all->where('status',SerpCheckStatus::Failed)->count(),'last24'=>$all->where('created_at','>=',now()->subDay())->count()];$checks=$q->latest('created_at')->paginate(25)->withQueryString();$checks->getCollection()->each(fn($check)=>$check->setAttribute('detected_rank',$check->status===SerpCheckStatus::Completed?$ranking->rank($check):null));return view('serp-checks.customer-index',compact('projects','keywords','checks','summary'));} public function customerShow(Request $r,SerpCheck $serpCheck,RankingHistoryService $ranking):View{abort_unless($serpCheck->keyword()->whereHas('project',fn($q)=>$q->where('user_id',$r->user()->id))->exists(),404);$serpCheck->load(['keyword.project','results'=>fn($q)=>$q->where('result_type','organic')->orderBy('position')]);$previous=SerpCheck::query()->where('keyword_id',$serpCheck->keyword_id)->where('status',SerpCheckStatus::Completed)->where('checked_at','<',$serpCheck->checked_at)->latest('checked_at')->with(['results'=>fn($q)=>$q->where('result_type','organic')->where('is_project_domain',true)])->first();$rank=$serpCheck->status===SerpCheckStatus::Completed?$serpCheck->results->where('is_project_domain',true)->min('position'):null;$prior=$previous?$ranking->rank($previous):null;return view('serp-checks.customer-show',['check'=>$serpCheck,'rank'=>$rank,'previous'=>$prior,'change'=>$rank&&$prior?$prior-$rank:null]);} public function customerStore(Request $r,Keyword $keyword,SerpCheckCreator $creator):RedirectResponse{abort_unless($keyword->project()->where('user_id',$r->user()->id)->exists(),404);$this->authorize('create',[SerpCheck::class,$keyword]);try{$check=$creator->create($r->user(),$keyword);return redirect()->route('serp-checks.show',$check)->with('status',__('serp_checks.requested'));}catch(\Illuminate\Validation\ValidationException){return back()->withErrors(['serp'=>__('serp_checks.unavailable')]);}} public function index(Project $customerProject,Keyword $customerKeyword):View{$this->authorize('viewAny',SerpCheck::class);$checks=$customerKeyword->serpChecks()->latest('id')->paginate(15);return view('serp-checks.index',['project'=>$customerProject,'keyword'=>$customerKeyword,'checks'=>$checks]);} public function store(Request $r,Project $customerProject,Keyword $customerKeyword,SerpCheckCreator $creator):RedirectResponse{$this->authorize('create',[SerpCheck::class,$customerKeyword]);$check=$creator->create($r->user(),$customerKeyword);return redirect()->route('projects.keywords.serp-checks.show',[$customerProject,$customerKeyword,$check])->with('status',__('serp_checks.requested'));} public function show(Project $customerProject,Keyword $customerKeyword,SerpCheck $customerSerpCheck):View{$this->authorize('view',$customerSerpCheck);$customerSerpCheck->load(['results'=>fn($q)=>$q->orderBy('position')]);$best=$customerSerpCheck->status===SerpCheckStatus::Completed?$customerSerpCheck->results->where('is_project_domain',true)->min('position'):null;return view('serp-checks.show',['project'=>$customerProject,'keyword'=>$customerKeyword,'check'=>$customerSerpCheck,'best'=>$best]);} public function tracking(Request $r,Project $customerProject,Keyword $customerKeyword,NextCheckAtCalculator $next):RedirectResponse{$this->authorize('update',$customerKeyword);$data=$r->validate(['tracking_enabled'=>['nullable','boolean'],'check_frequency'=>['required',Rule::enum(CheckFrequency::class)]]);$enabled=$r->boolean('tracking_enabled');$customerKeyword->forceFill(['tracking_enabled'=>$enabled,'check_frequency'=>$data['check_frequency'],'next_check_at'=>$enabled?$next->calculate($data['check_frequency']):null])->save();return back()->with('status',__('serp_checks.tracking_updated'));} }