changes
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Tasks\Posts;
|
||||
|
||||
use App\Containers\Article\Models\Post;
|
||||
|
||||
class BulkDeletePostsTask
|
||||
{
|
||||
public function run(array $ids): int
|
||||
{
|
||||
return Post::whereIn('id', $ids)->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Tasks\Posts;
|
||||
|
||||
use App\Containers\Article\Enums\PostStatus;
|
||||
use App\Containers\Article\Models\Post;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class BulkUpdatePostStatusTask
|
||||
{
|
||||
public function run(array $ids, PostStatus $status, ?Carbon $publishAt = null): int
|
||||
{
|
||||
$data = ['status' => $status];
|
||||
|
||||
if ($publishAt !== null) {
|
||||
$data['publish_at'] = $publishAt;
|
||||
}
|
||||
|
||||
return Post::whereIn('id', $ids)->update($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Tasks\Posts;
|
||||
|
||||
use App\Containers\Article\Enums\PostStatus;
|
||||
use App\Containers\Article\Models\Post;
|
||||
use App\Services\Filament\Domain\Posts\PostDataProcessor;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CreatePostFromAiDataTask
|
||||
{
|
||||
/**
|
||||
* Создаёт пост из данных, полученных от AI
|
||||
*
|
||||
* @param array $newsData Данные от AI (title, body, authors, tags, category_id)
|
||||
* @param string|null $documentPath Путь к основному документу
|
||||
* @param array $mediaPaths Пути к медиафайлам
|
||||
* @param array $attachedFiles Прикреплённые файлы (для блока files)
|
||||
*/
|
||||
public function run(array $newsData, ?string $documentPath, array $mediaPaths, array $attachedFiles): Post
|
||||
{
|
||||
// Формируем контент в формате редактора
|
||||
$content = $this->buildContent($newsData, $attachedFiles);
|
||||
|
||||
// Обрабатываем изображения
|
||||
['preview' => $previewPath, 'images' => $imagesPaths] = $this->processImages($mediaPaths);
|
||||
|
||||
// Подготавливаем данные для создания поста
|
||||
$postData = $this->preparePostData($newsData, $content, $previewPath, $imagesPaths);
|
||||
|
||||
// Обрабатываем через PostDataProcessor (как в админке)
|
||||
$processedData = (new PostDataProcessor())->processCreate($postData);
|
||||
|
||||
// Создаём пост
|
||||
$post = Post::create($processedData);
|
||||
|
||||
// Сохраняем теги
|
||||
$this->syncTags($post, $newsData);
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Формирует контент в формате редактора
|
||||
*/
|
||||
private function buildContent(array $newsData, array $attachedFiles): array
|
||||
{
|
||||
$content = [
|
||||
[
|
||||
'type' => 'paragraph',
|
||||
'data' => [
|
||||
'seo_active' => true,
|
||||
'content' => $newsData['body'] ?? '',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if (!empty($attachedFiles)) {
|
||||
$content[] = [
|
||||
'type' => 'files',
|
||||
'data' => [
|
||||
'file' => $attachedFiles,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Обрабатывает изображения: все изображения идут в gallery, первое — preview
|
||||
*/
|
||||
private function processImages(array $mediaPaths): array
|
||||
{
|
||||
$imagesPaths = [];
|
||||
|
||||
$imageExtensions = ['jpg', 'jpeg', 'png', 'webp', 'gif', 'bmp', 'svg', 'tiff'];
|
||||
|
||||
foreach ($mediaPaths as $path) {
|
||||
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
|
||||
|
||||
if (in_array($extension, $imageExtensions)) {
|
||||
$imagesPaths[] = $path;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'preview' => !empty($imagesPaths) ? reset($imagesPaths) : null,
|
||||
'images' => $imagesPaths,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Подготавливает данные для создания поста
|
||||
*/
|
||||
private function preparePostData(array $newsData, array $content, ?string $previewPath, array $imagesPaths): array
|
||||
{
|
||||
$baseSlug = Str::slug($newsData['title'] ?? '');
|
||||
$slug = $this->generateUniqueSlug($baseSlug);
|
||||
|
||||
return [
|
||||
'title' => $newsData['title'] ?? 'Без названия',
|
||||
'slug' => $slug,
|
||||
'content' => $content,
|
||||
'authors' => $newsData['authors'] ?? [],
|
||||
'category_id' => $newsData['category_id'] ?? null,
|
||||
'status' => PostStatus::VERIFICATION,
|
||||
'user_id' => auth()->id(),
|
||||
'preview' => $previewPath,
|
||||
'images' => !empty($imagesPaths) ? $imagesPaths : null,
|
||||
'publish_setting' => [
|
||||
'publish_after' => false,
|
||||
'publish_at' => null,
|
||||
],
|
||||
'publication' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерирует уникальный slug
|
||||
*/
|
||||
private function generateUniqueSlug(string $baseSlug): string
|
||||
{
|
||||
$slug = $baseSlug;
|
||||
$count = 1;
|
||||
|
||||
while (Post::where('slug', $slug)->exists()) {
|
||||
$slug = $baseSlug . '-' . $count;
|
||||
$count++;
|
||||
}
|
||||
|
||||
return $slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохраняет теги поста
|
||||
*/
|
||||
private function syncTags(Post $post, array $newsData): void
|
||||
{
|
||||
if (!empty($newsData['tags'])) {
|
||||
$tagNames = array_map(
|
||||
fn($tag) => str_replace('#', '', $tag),
|
||||
$newsData['tags']
|
||||
);
|
||||
$post->syncTags($tagNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Tasks\Posts;
|
||||
|
||||
use App\Containers\Article\Models\Post;
|
||||
use App\Services\Filament\Domain\Posts\PostDataProcessor;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CreatePostTask
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PostDataProcessor $postDataProcessor,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Создает новый пост в БД
|
||||
*
|
||||
* @param array $data Данные поста
|
||||
* @return Post
|
||||
*/
|
||||
public function run(array $data): Post
|
||||
{
|
||||
$processedData = $this->postDataProcessor->processCreate($data);
|
||||
return Post::create($processedData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерирует уникальный slug
|
||||
*/
|
||||
public function generateUniqueSlug(string $baseSlug): string
|
||||
{
|
||||
$slug = $baseSlug;
|
||||
$count = 1;
|
||||
|
||||
while (Post::where('slug', $slug)->exists()) {
|
||||
$slug = $baseSlug . '-' . $count;
|
||||
$count++;
|
||||
}
|
||||
|
||||
return $slug;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Tasks\Posts;
|
||||
|
||||
use App\Containers\Article\Models\Post;
|
||||
|
||||
class DeletePostTask
|
||||
{
|
||||
public function run(Post $post): bool
|
||||
{
|
||||
return $post->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Tasks\Posts;
|
||||
|
||||
use App\Containers\Article\Enums\PostStatus;
|
||||
use App\Containers\Article\Models\Post;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
class GetAiPreparedPostsTask
|
||||
{
|
||||
public function run(): Collection
|
||||
{
|
||||
return Post::query()
|
||||
->with(['category', 'author'])
|
||||
->whereIn('status', [PostStatus::VERIFICATION->value, PostStatus::REJECTED->value])
|
||||
->orderBy('created_at', 'desc')
|
||||
->get([
|
||||
'id',
|
||||
'title',
|
||||
'slug',
|
||||
'preview_text',
|
||||
'content',
|
||||
'status',
|
||||
'authors',
|
||||
'preview',
|
||||
'images',
|
||||
'reading_time',
|
||||
'category_id',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Tasks\Posts;
|
||||
|
||||
use App\Containers\Article\Enums\PostStatus;
|
||||
use App\Containers\Article\Models\Category;
|
||||
use App\Containers\Widget\Models\Slider;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class GetPostFormDataTask
|
||||
{
|
||||
public function run(): array
|
||||
{
|
||||
return [
|
||||
'categories' => Category::all(['id', 'title']),
|
||||
'sliders' => Slider::where('is_active', true)->get(['id', 'title']),
|
||||
'statuses' => PostStatus::cases(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Tasks\Posts;
|
||||
|
||||
use App\Containers\Article\Models\Post;
|
||||
use App\Dto\MainSliderDTO;
|
||||
use App\Services\Filament\Domain\Posts\PostSliderService;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
class HandlePostSliderTask
|
||||
{
|
||||
/**
|
||||
* Обрабатывает слайдер для поста (создание/обновление/удаление)
|
||||
*
|
||||
* @param Post $post
|
||||
* @param array $slideData Данные слайдера
|
||||
* @param bool $isCreate Создание или обновление
|
||||
* @return void
|
||||
*/
|
||||
public function run(Post $post, array $slideData, bool $isCreate = false): void
|
||||
{
|
||||
// Если слайдер не передан или slider_id null - ничего не делаем
|
||||
if (empty($slideData) || empty($slideData['slider_id'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Если нужно удалить слайдер
|
||||
if (isset($slideData['slider_id']) && $slideData['slider_id'] === null) {
|
||||
$post->slide?->delete();
|
||||
return;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'is_active' => $slideData['is_active'] ?? false,
|
||||
'start_time' => $post->publish_at ?? Carbon::now(),
|
||||
'title' => $slideData['title'] ?? $post->title,
|
||||
'description' => $slideData['content'] ?? $post->preview_text,
|
||||
'image' => $slideData['image']['url'] ?? null,
|
||||
'color_theme' => $slideData['color_theme'] ?? '#ffffff',
|
||||
];
|
||||
|
||||
// Добавляем настройки слайда
|
||||
if (isset($slideData['settings'])) {
|
||||
$data = array_merge($data, $slideData['settings']);
|
||||
}
|
||||
|
||||
$sliderDTO = MainSliderDTO::fromArray($data);
|
||||
$sliderService = new PostSliderService($sliderDTO, $post);
|
||||
|
||||
// Обновляем или создаем слайдер
|
||||
if ($post->slide && !$isCreate) {
|
||||
$sliderService->update();
|
||||
} else {
|
||||
$sliderService->create();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Tasks\Posts;
|
||||
|
||||
use App\Containers\Article\Models\Post;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
|
||||
class ListPostsTask
|
||||
{
|
||||
public function run(array $filters = [], int $perPage = 20): LengthAwarePaginator
|
||||
{
|
||||
$query = Post::with(['category', 'author'])
|
||||
->orderBy('publish_at', 'desc');
|
||||
|
||||
if (!empty($filters['status'])) {
|
||||
$query->where('status', $filters['status']);
|
||||
}
|
||||
|
||||
if (!empty($filters['search'])) {
|
||||
$query->where('title', 'like', '%' . $filters['search'] . '%');
|
||||
}
|
||||
|
||||
return $query->paginate($perPage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Tasks\Posts;
|
||||
|
||||
use App\Containers\Article\Models\Post;
|
||||
use App\Services\Filament\Domain\Posts\VkPostPublisher;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class PublishPostToVkTask
|
||||
{
|
||||
/**
|
||||
* Публикует пост в VK
|
||||
*
|
||||
* @param Post $post
|
||||
* @param bool $shouldPublish Флаг публикации
|
||||
* @param bool $isUpdate Флаг обновления (true) или создания (false)
|
||||
* @return void
|
||||
*/
|
||||
public function run(Post $post, bool $shouldPublish, bool $isUpdate = false): void
|
||||
{
|
||||
if (!$shouldPublish) {
|
||||
return;
|
||||
}
|
||||
|
||||
$publisher = new VkPostPublisher();
|
||||
|
||||
if ($isUpdate) {
|
||||
// Проверяем, опубликован ли пост уже в VK
|
||||
$postRelation = DB::table('posts_vk_posts')
|
||||
->where('post_id', $post->id)
|
||||
->first();
|
||||
|
||||
if ($postRelation) {
|
||||
$publisher->update(['vk' => true], $post);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$publisher->publish(['vk' => true], $post);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Tasks\Posts;
|
||||
|
||||
use App\Containers\Article\Enums\PostStatus;
|
||||
use App\Containers\Article\Models\Post;
|
||||
use App\Services\Filament\Domain\Posts\PostNotificationService;
|
||||
|
||||
class SendPostNotificationTask
|
||||
{
|
||||
/**
|
||||
* Отправляет уведомления о статусе поста
|
||||
*
|
||||
* @param Post $post
|
||||
* @param string|null $newStatus Новый статус поста
|
||||
* @param bool $isCreation Флаг создания (true) или обновления (false)
|
||||
* @return void
|
||||
*/
|
||||
public function run(Post $post, ?string $newStatus = null, bool $isCreation = false): void
|
||||
{
|
||||
$notificationService = new PostNotificationService();
|
||||
|
||||
// При создании всегда отправляем уведомление
|
||||
if ($isCreation) {
|
||||
$notificationService->send($post);
|
||||
return;
|
||||
}
|
||||
|
||||
// При обновлении отправляем только при смене статуса
|
||||
$status = $newStatus ?? $post->status;
|
||||
|
||||
if ($status instanceof PostStatus) {
|
||||
if ($status === PostStatus::PUBLISHED) {
|
||||
$notificationService->sendSuccessNotification($post);
|
||||
} elseif ($status === PostStatus::REJECTED) {
|
||||
$notificationService->sendDeniedNotification($post);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Containers\Dashboard\Tasks\Posts;
|
||||
|
||||
use App\Containers\Article\Models\Post;
|
||||
use App\Services\Filament\Domain\Posts\PostDataProcessor;
|
||||
|
||||
class UpdatePostTask
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PostDataProcessor $postDataProcessor,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Обновляет существующий пост
|
||||
*
|
||||
* @param Post $post Пост для обновления
|
||||
* @param array $data Новые данные
|
||||
* @return Post
|
||||
*/
|
||||
public function run(Post $post, array $data): Post
|
||||
{
|
||||
$processedData = $this->postDataProcessor->processUpdate([
|
||||
...$post->toArray(),
|
||||
...$data,
|
||||
]);
|
||||
|
||||
$post->update($processedData);
|
||||
|
||||
return $post->fresh();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user