authorize('viewAny', Project::class); $archived = $request->string('filter')->toString() === 'archived'; $projects = $request->user()->projects() ->where('status', $archived ? ProjectStatus::Archived : ProjectStatus::Active) ->withCount(['keywords' => fn ($query) => $query->active()]) ->orderByDesc('id')->paginate(15)->withQueryString(); return view('projects.index', compact('projects', 'archived')); } public function create(): View { $this->authorize('create', Project::class); return view('projects.create'); } public function store(StoreProjectRequest $request, ProjectUrlNormalizer $normalizer): RedirectResponse { $normalized = $normalizer->normalize($request->string('website_url')->toString()); try { $project = $request->user()->projects()->create([ 'name' => $request->string('name')->trim()->toString(), 'domain' => $normalized->domain, 'url' => $normalized->url, ]); } catch (QueryException $exception) { $this->throwDuplicateValidation($exception); } return redirect()->route('projects.show', $project)->with('status', 'Proje oluşturuldu.'); } public function show(Project $customerProject): View { $this->authorize('view', $customerProject); $customerProject->load(['keywords' => fn ($q) => $q->active()->orderBy('phrase'), 'keywords.serpChecks' => fn ($q) => $q->where('status', 'completed')->latest('checked_at')->with(['results' => fn ($r) => $r->where('is_project_domain', true)->orderBy('position')])]); $rows = $customerProject->keywords->map(function ($keyword) { $checks=$keyword->serpChecks->filter(fn($c)=>$c->results->isNotEmpty())->values();$current=$checks->get(0);$previous=$checks->get(1);$rank=$current?->results->min('position');$prior=$previous?->results->min('position');return ['current'=>$rank,'change'=>($rank&&$prior)?$prior-$rank:null,'checked_at'=>$current?->checked_at]; }); $measured=$rows->whereNotNull('current'); return view('projects.show', ['project'=>$customerProject,'summary'=>['keywords'=>$customerProject->keywords->count(),'last_check'=>$rows->max('checked_at'),'average'=>$measured->isNotEmpty()?round($measured->avg('current'),1):null,'top10'=>$measured->where('current','<=',10)->count(),'winners'=>$rows->where('change','>',0)->count(),'losers'=>$rows->where('change','<',0)->count()]]); } public function edit(Project $customerProject): View { $this->authorize('update', $customerProject); return view('projects.edit', ['project' => $customerProject]); } public function update(UpdateProjectRequest $request, Project $customerProject, ProjectUrlNormalizer $normalizer): RedirectResponse { $normalized = $normalizer->normalize($request->string('website_url')->toString()); try { $customerProject->update([ 'name' => $request->string('name')->trim()->toString(), 'domain' => $normalized->domain, 'url' => $normalized->url, ]); } catch (QueryException $exception) { $this->throwDuplicateValidation($exception); } return redirect()->route('projects.show', $customerProject)->with('status', 'Proje güncellendi.'); } public function archive(Project $customerProject): RedirectResponse { $this->authorize('archive', $customerProject); $customerProject->archive(); return redirect()->route('projects.index')->with('status', 'Proje arşivlendi.'); } public function restore(Project $customerProject): RedirectResponse { $this->authorize('restore', $customerProject); $customerProject->restoreProject(); return redirect()->route('projects.show', $customerProject)->with('status', 'Proje yeniden etkinleştirildi.'); } private function throwDuplicateValidation(QueryException $exception): never { if (! in_array((string) $exception->getCode(), ['23000', '19'], true)) throw $exception; throw ValidationException::withMessages(['website_url' => 'Bu alan adı hesabınızda zaten kayıtlı.']); } }