with(['creator']) ->latest('created_at') ->paginate(25); return view('admin.stories.index', ['stories' => $stories]); } public function review(): View { $stories = Story::query() ->with(['creator']) ->where('status', 'pending_review') ->orderByDesc('submitted_for_review_at') ->paginate(25); return view('admin.stories.review', ['stories' => $stories]); } public function create(): View { return view('admin.stories.create', [ 'creators' => User::query()->orderBy('username')->limit(200)->get(['id', 'username']), 'tags' => StoryTag::query()->orderBy('name')->get(['id', 'name']), ]); } public function store(Request $request): RedirectResponse { $validated = $request->validate([ 'creator_id' => ['required', 'integer', 'exists:users,id'], 'title' => ['required', 'string', 'max:255'], 'excerpt' => ['nullable', 'string', 'max:500'], 'cover_image' => ['nullable', 'string', 'max:500'], 'content' => ['required', 'string'], 'story_type' => ['required', 'in:creator_story,tutorial,interview,project_breakdown,announcement,resource'], 'status' => ['required', Rule::in(['draft', 'pending_review', 'published', 'scheduled', 'archived', 'rejected'])], 'tags' => ['nullable', 'array'], 'tags.*' => ['integer', 'exists:story_tags,id'], ]); $story = Story::query()->create([ 'creator_id' => (int) $validated['creator_id'], 'title' => $validated['title'], 'slug' => $this->uniqueSlug($validated['title']), 'excerpt' => $validated['excerpt'] ?? null, 'cover_image' => $validated['cover_image'] ?? null, 'content' => $validated['content'], 'story_type' => $validated['story_type'], 'reading_time' => max(1, (int) ceil(str_word_count(strip_tags((string) $validated['content'])) / 200)), 'status' => $validated['status'], 'published_at' => $validated['status'] === 'published' ? now() : null, 'submitted_for_review_at' => $validated['status'] === 'pending_review' ? now() : null, ]); if (! empty($validated['tags'])) { $story->tags()->sync($validated['tags']); } return redirect()->route('admin.stories.edit', ['story' => $story->id]) ->with('status', 'Story created.'); } public function edit(Story $story): View { $story->load('tags'); return view('admin.stories.edit', [ 'story' => $story, 'creators' => User::query()->orderBy('username')->limit(200)->get(['id', 'username']), 'tags' => StoryTag::query()->orderBy('name')->get(['id', 'name']), ]); } public function update(Request $request, Story $story): RedirectResponse { $validated = $request->validate([ 'creator_id' => ['required', 'integer', 'exists:users,id'], 'title' => ['required', 'string', 'max:255'], 'excerpt' => ['nullable', 'string', 'max:500'], 'cover_image' => ['nullable', 'string', 'max:500'], 'content' => ['required', 'string'], 'story_type' => ['required', 'in:creator_story,tutorial,interview,project_breakdown,announcement,resource'], 'status' => ['required', Rule::in(['draft', 'pending_review', 'published', 'scheduled', 'archived', 'rejected'])], 'tags' => ['nullable', 'array'], 'tags.*' => ['integer', 'exists:story_tags,id'], ]); $story->update([ 'creator_id' => (int) $validated['creator_id'], 'title' => $validated['title'], 'excerpt' => $validated['excerpt'] ?? null, 'cover_image' => $validated['cover_image'] ?? null, 'content' => $validated['content'], 'story_type' => $validated['story_type'], 'reading_time' => max(1, (int) ceil(str_word_count(strip_tags((string) $validated['content'])) / 200)), 'status' => $validated['status'], 'published_at' => $validated['status'] === 'published' ? ($story->published_at ?? now()) : $story->published_at, 'submitted_for_review_at' => $validated['status'] === 'pending_review' ? ($story->submitted_for_review_at ?? now()) : $story->submitted_for_review_at, ]); $story->tags()->sync($validated['tags'] ?? []); return back()->with('status', 'Story updated.'); } public function destroy(Story $story): RedirectResponse { $story->delete(); return redirect()->route('admin.stories.index')->with('status', 'Story deleted.'); } public function publish(Story $story): RedirectResponse { $story->update([ 'status' => 'published', 'published_at' => $story->published_at ?? now(), 'reviewed_at' => now(), ]); $story->creator?->notify(new StoryStatusNotification($story, 'published')); return back()->with('status', 'Story published.'); } public function show(Story $story): View { return view('admin.stories.show', [ 'story' => $story->load(['creator', 'tags']), ]); } public function approve(Request $request, Story $story): RedirectResponse { $story->update([ 'status' => 'published', 'published_at' => $story->published_at ?? now(), 'reviewed_at' => now(), 'reviewed_by_id' => (int) $request->user()->id, 'rejected_reason' => null, ]); $story->creator?->notify(new StoryStatusNotification($story, 'approved')); return back()->with('status', 'Story approved and published.'); } public function reject(Request $request, Story $story): RedirectResponse { $validated = $request->validate([ 'reason' => ['required', 'string', 'max:1000'], ]); $story->update([ 'status' => 'rejected', 'reviewed_at' => now(), 'reviewed_by_id' => (int) $request->user()->id, 'rejected_reason' => $validated['reason'], ]); $story->creator?->notify(new StoryStatusNotification($story, 'rejected', $validated['reason'])); return back()->with('status', 'Story rejected and creator notified.'); } public function moderateComments(): View { return view('admin.stories.comments-moderation'); } private function uniqueSlug(string $title): string { $base = Str::slug($title); $slug = $base; $n = 2; while (Story::query()->where('slug', $slug)->exists()) { $slug = $base . '-' . $n; $n++; } return $slug; } }