get('per_page', 15), 100); $sort = $request->string('sort', 'latest')->toString(); $query = Post::query() ->with('category') ->when( $request->filled('keyword'), fn ($q) => $q->where(function ($subQuery) use ($request) { $keyword = $request->string('keyword')->toString(); $subQuery ->where('title', 'like', "%{$keyword}%") ->orWhere('excerpt', 'like', "%{$keyword}%") ->orWhere('content', 'like', "%{$keyword}%"); }) ) ->when($request->filled('category_id'), fn ($q) => $q->where('post_category_id', (int) $request->get('category_id'))) ->when($request->filled('status'), fn ($q) => $q->where('status', $request->string('status')->toString())) ->when($request->filled('is_featured'), fn ($q) => $q->where('is_featured', $request->boolean('is_featured'))); match ($sort) { 'title_asc' => $query->orderBy('title'), 'title_desc' => $query->orderByDesc('title'), 'published_at_asc', 'oldest' => $query->orderBy('published_at')->orderBy('id'), 'most_viewed' => $query->orderByDesc('view_count')->orderByDesc('id'), default => $query->orderByDesc('published_at')->orderByDesc('id'), }; $paginator = $query->paginate($perPage); return response()->json([ 'error' => false, 'data' => [ 'items' => PostResource::collection($paginator->items()), 'total' => $paginator->total(), 'per_page' => $paginator->perPage(), 'current_page' => $paginator->currentPage(), 'last_page' => $paginator->lastPage(), ], ]); } public function store(PostStoreRequest $request): JsonResponse { $data = $request->validated(); $slugSource = $data['slug'] ?: $data['title']; $data['slug'] = GeneratesUniqueSlug::forModel(Post::class, $slugSource); $data['is_featured'] = (bool) ($data['is_featured'] ?? false); $data['view_count'] = (int) ($data['view_count'] ?? 0); $post = Post::create($data); $this->revalidation->revalidateDocs(); return response()->json([ 'error' => false, 'message' => 'Tạo bài viết thành công.', 'data' => new PostResource($post->load('category')), ], 201); } public function show(int $id): JsonResponse { $post = Post::with('category')->find($id); if (!$post) { return response()->json(['error' => true, 'message' => 'Không tìm thấy bài viết.'], 404); } return response()->json([ 'error' => false, 'data' => new PostResource($post), ]); } public function update(PostUpdateRequest $request, int $id): JsonResponse { $post = Post::find($id); if (!$post) { return response()->json(['error' => true, 'message' => 'Không tìm thấy bài viết.'], 404); } $data = $request->validated(); $slugSource = $data['slug'] ?: $data['title']; $data['slug'] = GeneratesUniqueSlug::forModel(Post::class, $slugSource, $post->id); $data['is_featured'] = (bool) ($data['is_featured'] ?? false); $data['view_count'] = (int) ($data['view_count'] ?? $post->view_count); $post->update($data); $this->revalidation->revalidateDocs(); return response()->json([ 'error' => false, 'message' => 'Cập nhật bài viết thành công.', 'data' => new PostResource($post->fresh()->load('category')), ]); } public function destroy(int $id): JsonResponse { $post = Post::find($id); if (!$post) { return response()->json(['error' => true, 'message' => 'Không tìm thấy bài viết.'], 404); } $post->delete(); $this->revalidation->revalidateDocs(); return response()->json([ 'error' => false, 'message' => 'Xoá bài viết thành công.', ]); } public function trashed(Request $request): JsonResponse { $perPage = min((int) $request->get('per_page', 15), 100); $paginator = Post::onlyTrashed() ->with('category') ->when( $request->filled('keyword'), fn ($q) => $q->where('title', 'like', '%' . $request->string('keyword')->toString() . '%') ) ->orderByDesc('deleted_at') ->paginate($perPage); return response()->json([ 'error' => false, 'data' => [ 'items' => PostResource::collection($paginator->items()), 'total' => $paginator->total(), 'per_page' => $paginator->perPage(), 'current_page' => $paginator->currentPage(), 'last_page' => $paginator->lastPage(), ], ]); } public function restore(int $id): JsonResponse { $post = Post::onlyTrashed()->find($id); if (!$post) { return response()->json(['error' => true, 'message' => 'Không tìm thấy bài viết.'], 404); } $post->restore(); $this->revalidation->revalidateDocs(); return response()->json(['error' => false, 'message' => 'Khôi phục bài viết thành công.']); } public function forceDelete(int $id): JsonResponse { $post = Post::onlyTrashed()->find($id); if (!$post) { return response()->json(['error' => true, 'message' => 'Không tìm thấy bài viết.'], 404); } $post->forceDelete(); return response()->json(['error' => false, 'message' => 'Xoá vĩnh viễn bài viết thành công.']); } public function bulkRestore(Request $request): JsonResponse { $request->validate(['ids' => 'required|array|min:1', 'ids.*' => 'integer']); $count = Post::onlyTrashed()->whereIn('id', $request->ids)->restore(); $this->revalidation->revalidateDocs(); return response()->json(['error' => false, 'message' => "Đã khôi phục {$count} bài viết."]); } public function bulkForceDelete(Request $request): JsonResponse { $request->validate(['ids' => 'required|array|min:1', 'ids.*' => 'integer']); $count = Post::onlyTrashed()->whereIn('id', $request->ids)->forceDelete(); return response()->json(['error' => false, 'message' => "Đã xoá vĩnh viễn {$count} bài viết."]); } public function bulkDelete(Request $request): JsonResponse { $request->validate(['ids' => 'required|array|min:1', 'ids.*' => 'integer']); $count = Post::whereIn('id', $request->ids)->delete(); $this->revalidation->revalidateDocs(); return response()->json(['error' => false, 'message' => "Đã xoá {$count} bài viết."]); } public function bulkUpdateStatus(Request $request): JsonResponse { $request->validate([ 'ids' => 'required|array|min:1', 'ids.*' => 'integer', 'status' => 'required|in:draft,published,hidden', ]); $count = Post::whereIn('id', $request->ids)->update(['status' => $request->status]); $this->revalidation->revalidateDocs(); return response()->json(['error' => false, 'message' => "Đã cập nhật trạng thái {$count} bài viết."]); } public function syncWordpress(Request $request): JsonResponse { $validated = $request->validate([ 'source_url' => 'nullable|url', 'per_page' => 'nullable|integer|min:1|max:100', 'max_pages' => 'nullable|integer|min:1|max:200', ]); $result = $this->wordpressSync->sync( $validated['source_url'] ?? null, (int) ($validated['per_page'] ?? 20), isset($validated['max_pages']) ? (int) $validated['max_pages'] : null, ); $this->revalidation->revalidateDocs(); return response()->json([ 'error' => false, 'message' => "Đồng bộ hoàn tất. Tạo mới {$result['created']}, cập nhật {$result['updated']}, bỏ qua {$result['skipped']}.", 'data' => $result, ]); } }