more fixes
This commit is contained in:
206
app/Http/Controllers/Admin/StoryAdminController.php
Normal file
206
app/Http/Controllers/Admin/StoryAdminController.php
Normal file
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Story;
|
||||
use App\Models\StoryTag;
|
||||
use App\Models\User;
|
||||
use App\Notifications\StoryStatusNotification;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class StoryAdminController extends Controller
|
||||
{
|
||||
public function index(): View
|
||||
{
|
||||
$stories = Story::query()
|
||||
->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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user