Studio: make grid checkbox rectangular and commit table changes
This commit is contained in:
165
app/Services/Studio/StudioBulkActionService.php
Normal file
165
app/Services/Studio/StudioBulkActionService.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Studio;
|
||||
|
||||
use App\Models\Artwork;
|
||||
use App\Models\Tag;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Handles bulk operations on artworks for the Studio module.
|
||||
*/
|
||||
final class StudioBulkActionService
|
||||
{
|
||||
/**
|
||||
* Execute a bulk action on the given artwork IDs, enforcing ownership.
|
||||
*
|
||||
* @param int $userId The authenticated user ID
|
||||
* @param string $action publish|unpublish|archive|unarchive|delete|change_category|add_tags|remove_tags
|
||||
* @param array $artworkIds Array of artwork IDs
|
||||
* @param array $params Extra params (category_id, tag_ids)
|
||||
* @return array{success: int, failed: int, errors: array}
|
||||
*/
|
||||
public function execute(int $userId, string $action, array $artworkIds, array $params = []): array
|
||||
{
|
||||
$result = ['success' => 0, 'failed' => 0, 'errors' => []];
|
||||
|
||||
// Validate ownership — fetch only artworks belonging to this user
|
||||
$query = Artwork::where('user_id', $userId);
|
||||
if ($action === 'unarchive') {
|
||||
$query->onlyTrashed();
|
||||
}
|
||||
$artworks = $query->whereIn('id', $artworkIds)->get();
|
||||
|
||||
$foundIds = $artworks->pluck('id')->all();
|
||||
$missingIds = array_diff($artworkIds, $foundIds);
|
||||
foreach ($missingIds as $id) {
|
||||
$result['failed']++;
|
||||
$result['errors'][] = "Artwork #{$id}: not found or not owned by you";
|
||||
}
|
||||
|
||||
if ($artworks->isEmpty()) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
foreach ($artworks as $artwork) {
|
||||
$this->applyAction($artwork, $action, $params);
|
||||
$result['success']++;
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
// Reindex affected artworks in Meilisearch
|
||||
$this->reindexArtworks($artworks);
|
||||
|
||||
Log::info('Studio bulk action completed', [
|
||||
'user_id' => $userId,
|
||||
'action' => $action,
|
||||
'count' => $result['success'],
|
||||
'ids' => $foundIds,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
DB::rollBack();
|
||||
$result['failed'] += $result['success'];
|
||||
$result['success'] = 0;
|
||||
$result['errors'][] = 'Transaction failed: ' . $e->getMessage();
|
||||
|
||||
Log::error('Studio bulk action failed', [
|
||||
'user_id' => $userId,
|
||||
'action' => $action,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function applyAction(Artwork $artwork, string $action, array $params): void
|
||||
{
|
||||
match ($action) {
|
||||
'publish' => $this->publish($artwork),
|
||||
'unpublish' => $this->unpublish($artwork),
|
||||
'archive' => $artwork->delete(), // Soft delete
|
||||
'unarchive' => $artwork->restore(),
|
||||
'delete' => $artwork->forceDelete(),
|
||||
'change_category' => $this->changeCategory($artwork, $params),
|
||||
'add_tags' => $this->addTags($artwork, $params),
|
||||
'remove_tags' => $this->removeTags($artwork, $params),
|
||||
default => throw new \InvalidArgumentException("Unknown action: {$action}"),
|
||||
};
|
||||
}
|
||||
|
||||
private function publish(Artwork $artwork): void
|
||||
{
|
||||
$artwork->update([
|
||||
'is_public' => true,
|
||||
'published_at' => $artwork->published_at ?? now(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function unpublish(Artwork $artwork): void
|
||||
{
|
||||
$artwork->update(['is_public' => false]);
|
||||
}
|
||||
|
||||
private function changeCategory(Artwork $artwork, array $params): void
|
||||
{
|
||||
if (empty($params['category_id'])) {
|
||||
throw new \InvalidArgumentException('category_id required for change_category');
|
||||
}
|
||||
|
||||
$artwork->categories()->sync([(int) $params['category_id']]);
|
||||
}
|
||||
|
||||
private function addTags(Artwork $artwork, array $params): void
|
||||
{
|
||||
if (empty($params['tag_ids'])) {
|
||||
throw new \InvalidArgumentException('tag_ids required for add_tags');
|
||||
}
|
||||
|
||||
$pivotData = [];
|
||||
foreach ((array) $params['tag_ids'] as $tagId) {
|
||||
$pivotData[(int) $tagId] = ['source' => 'studio_bulk', 'confidence' => 1.0];
|
||||
}
|
||||
|
||||
$artwork->tags()->syncWithoutDetaching($pivotData);
|
||||
|
||||
// Increment usage counts
|
||||
Tag::whereIn('id', array_keys($pivotData))
|
||||
->increment('usage_count');
|
||||
}
|
||||
|
||||
private function removeTags(Artwork $artwork, array $params): void
|
||||
{
|
||||
if (empty($params['tag_ids'])) {
|
||||
throw new \InvalidArgumentException('tag_ids required for remove_tags');
|
||||
}
|
||||
|
||||
$tagIds = array_map('intval', (array) $params['tag_ids']);
|
||||
$artwork->tags()->detach($tagIds);
|
||||
|
||||
Tag::whereIn('id', $tagIds)
|
||||
->where('usage_count', '>', 0)
|
||||
->decrement('usage_count');
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger Meilisearch reindex for the given artworks.
|
||||
*/
|
||||
private function reindexArtworks(\Illuminate\Database\Eloquent\Collection $artworks): void
|
||||
{
|
||||
try {
|
||||
$artworks->each->searchable();
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('Studio: Failed to reindex artworks after bulk action', [
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user