JezK
Edit File: ArticleService.php
<?php namespace App\Services; use App\Enums\ArticleStatus; use App\Models\Article; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Collection; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Storage; class ArticleService { public function __construct( protected SlugService $slugService ) {} /** * Get paginated articles for admin panel. */ public function getAdminArticles(array $filters = [], int $perPage = 15): LengthAwarePaginator { $query = Article::with(['author', 'categories', 'tags']); if (!empty($filters['search'])) { $query->search($filters['search']); } if (!empty($filters['status'])) { $query->where('status', $filters['status']); } if (!empty($filters['category'])) { $query->whereHas('categories', fn ($q) => $q->where('categories.id', $filters['category'])); } if (isset($filters['trashed']) && $filters['trashed']) { $query->onlyTrashed(); } return $query->latest()->paginate($perPage)->withQueryString(); } /** * Get published articles for public API. */ public function getPublishedArticles(array $filters = [], int $perPage = 10): LengthAwarePaginator { $query = Article::with(['author', 'categories', 'tags']) ->published(); if (!empty($filters['search'])) { $query->search($filters['search']); } if (!empty($filters['category'])) { $query->whereHas('categories', fn ($q) => $q->where('categories.slug', $filters['category'])); } if (!empty($filters['tag'])) { $query->whereHas('tags', fn ($q) => $q->where('tags.slug', $filters['tag'])); } $sortField = $filters['sort'] ?? 'published_at'; $sortOrder = $filters['order'] ?? 'desc'; $allowedSorts = ['published_at', 'title', 'view_count']; if (in_array($sortField, $allowedSorts)) { $query->orderBy($sortField, $sortOrder === 'asc' ? 'asc' : 'desc'); } return $query->paginate(min($perPage, 50))->withQueryString(); } /** * Get featured articles. */ public function getFeaturedArticles(int $limit = 5): Collection { return Article::with(['author', 'categories']) ->published() ->featured() ->latest('published_at') ->limit($limit) ->get(); } /** * Find article by slug (public). */ public function findBySlug(string $slug): ?Article { $article = Article::with(['author', 'categories', 'tags', 'images', 'comments']) ->published() ->where('slug', $slug) ->first(); if ($article) { $article->increment('view_count'); } return $article; } /** * Find article by ID (admin). */ public function findById(int $id): ?Article { return Article::with(['author', 'categories', 'tags', 'images'])->find($id); } /** * Create a new article. */ public function create(array $data, ?UploadedFile $featuredImage = null, ?UploadedFile $ogImage = null): Article { $data['slug'] = $this->slugService->generate(Article::class, $data['title']); if ($featuredImage) { $data['featured_image'] = $featuredImage->store('articles/featured', 'public'); } if ($ogImage) { $data['og_image'] = $ogImage->store('articles/og', 'public'); } // Handle publish if (($data['status'] ?? 'draft') === ArticleStatus::PUBLISHED->value) { $data['published_at'] = $data['published_at'] ?? now(); } $article = Article::create($data); // Sync categories and tags if (!empty($data['categories'])) { $article->categories()->sync($data['categories']); } if (!empty($data['tags'])) { $article->tags()->sync($data['tags']); } return $article->load(['author', 'categories', 'tags']); } /** * Update an existing article. */ public function update(Article $article, array $data, ?UploadedFile $featuredImage = null, ?UploadedFile $ogImage = null): Article { if (isset($data['title']) && $data['title'] !== $article->title) { $data['slug'] = $this->slugService->generate(Article::class, $data['title'], $article->id); } if ($featuredImage) { // Delete old image if ($article->featured_image) { Storage::disk('public')->delete($article->featured_image); } $data['featured_image'] = $featuredImage->store('articles/featured', 'public'); } if ($ogImage) { if ($article->og_image) { Storage::disk('public')->delete($article->og_image); } $data['og_image'] = $ogImage->store('articles/og', 'public'); } // Handle publish if (($data['status'] ?? null) === ArticleStatus::PUBLISHED->value && !$article->published_at) { $data['published_at'] = now(); } $article->update($data); if (isset($data['categories'])) { $article->categories()->sync($data['categories']); } if (isset($data['tags'])) { $article->tags()->sync($data['tags']); } return $article->fresh(['author', 'categories', 'tags', 'images']); } /** * Soft delete an article. */ public function delete(Article $article): bool { return $article->delete(); } /** * Restore a soft-deleted article. */ public function restore(int $id): ?Article { $article = Article::onlyTrashed()->find($id); $article?->restore(); return $article; } /** * Permanently delete an article. */ public function forceDelete(int $id): bool { $article = Article::onlyTrashed()->find($id); if ($article) { if ($article->featured_image) { Storage::disk('public')->delete($article->featured_image); } if ($article->og_image) { Storage::disk('public')->delete($article->og_image); } return $article->forceDelete(); } return false; } /** * Get dashboard statistics. */ public function getStats(): array { return [ 'total' => Article::withTrashed()->count(), 'published' => Article::published()->count(), 'draft' => Article::draft()->count(), 'scheduled' => Article::scheduled()->count(), 'trashed' => Article::onlyTrashed()->count(), 'total_views' => Article::sum('view_count'), ]; } /** * Publish scheduled articles. */ public function publishScheduledArticles(): int { $articles = Article::where('status', ArticleStatus::SCHEDULED) ->where('scheduled_at', '<=', now()) ->get(); foreach ($articles as $article) { /** @var Article $article */ $article->update([ 'status' => ArticleStatus::PUBLISHED, 'published_at' => $article->scheduled_at, ]); } return $articles->count(); } }