Files
SkinbaseNova/app/Services/Studio/Providers/StoryStudioProvider.php

277 lines
9.5 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\Studio\Providers;
use App\Models\Story;
use App\Models\User;
use App\Services\Studio\Contracts\CreatorStudioProvider;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
final class StoryStudioProvider implements CreatorStudioProvider
{
public function key(): string
{
return 'stories';
}
public function label(): string
{
return 'Stories';
}
public function icon(): string
{
return 'fa-solid fa-feather-pointed';
}
public function createUrl(): string
{
return route('creator.stories.create');
}
public function indexUrl(): string
{
return route('studio.stories');
}
public function summary(User $user): array
{
$baseQuery = Story::query()->where('creator_id', $user->id);
$count = (clone $baseQuery)
->whereNotIn('status', ['archived'])
->count();
$draftCount = (clone $baseQuery)
->whereIn('status', ['draft', 'pending_review', 'rejected'])
->count();
$publishedCount = (clone $baseQuery)
->whereIn('status', ['published', 'scheduled'])
->count();
$recentPublishedCount = (clone $baseQuery)
->whereIn('status', ['published', 'scheduled'])
->where('published_at', '>=', now()->subDays(30))
->count();
$archivedCount = (clone $baseQuery)
->where('status', 'archived')
->count();
return [
'key' => $this->key(),
'label' => $this->label(),
'icon' => $this->icon(),
'count' => $count,
'draft_count' => $draftCount,
'published_count' => $publishedCount,
'archived_count' => $archivedCount,
'trend_value' => $recentPublishedCount,
'trend_label' => 'published in 30d',
'quick_action_label' => 'Create story',
'index_url' => $this->indexUrl(),
'create_url' => $this->createUrl(),
];
}
public function items(User $user, string $bucket = 'all', int $limit = 200): Collection
{
$query = Story::query()
->where('creator_id', $user->id)
->with(['tags'])
->orderByDesc('updated_at')
->limit($limit);
if ($bucket === 'drafts') {
$query->whereIn('status', ['draft', 'pending_review', 'rejected']);
} elseif ($bucket === 'scheduled') {
$query->where('status', 'scheduled');
} elseif ($bucket === 'archived') {
$query->where('status', 'archived');
} elseif ($bucket === 'published') {
$query->whereIn('status', ['published', 'scheduled']);
} else {
$query->where('status', '!=', 'archived');
}
return $query->get()->map(fn (Story $story): array => $this->mapItem($story));
}
public function topItems(User $user, int $limit = 5): Collection
{
return Story::query()
->where('creator_id', $user->id)
->whereIn('status', ['published', 'scheduled'])
->orderByDesc('views')
->orderByDesc('likes_count')
->limit($limit)
->get()
->map(fn (Story $story): array => $this->mapItem($story));
}
public function analytics(User $user): array
{
$totals = Story::query()
->where('creator_id', $user->id)
->where('status', '!=', 'archived')
->selectRaw('COALESCE(SUM(views), 0) as views')
->selectRaw('COALESCE(SUM(likes_count), 0) as appreciation')
->selectRaw('0 as shares')
->selectRaw('COALESCE(SUM(comments_count), 0) as comments')
->selectRaw('0 as saves')
->first();
return [
'views' => (int) ($totals->views ?? 0),
'appreciation' => (int) ($totals->appreciation ?? 0),
'shares' => 0,
'comments' => (int) ($totals->comments ?? 0),
'saves' => 0,
];
}
public function scheduledItems(User $user, int $limit = 50): Collection
{
return $this->items($user, 'scheduled', $limit);
}
private function mapItem(Story $story): array
{
$subtitle = $story->story_type ? ucfirst(str_replace('_', ' ', (string) $story->story_type)) : null;
$viewUrl = in_array($story->status, ['published', 'scheduled'], true)
? route('stories.show', ['slug' => $story->slug])
: route('creator.stories.preview', ['story' => $story->id]);
return [
'id' => sprintf('%s:%d', $this->key(), (int) $story->id),
'numeric_id' => (int) $story->id,
'module' => $this->key(),
'module_label' => $this->label(),
'module_icon' => $this->icon(),
'title' => $story->title,
'subtitle' => $subtitle,
'description' => $story->excerpt,
'status' => $story->status,
'visibility' => $story->status === 'published' ? 'public' : 'private',
'image_url' => $story->cover_url,
'preview_url' => route('creator.stories.preview', ['story' => $story->id]),
'view_url' => $viewUrl,
'edit_url' => route('creator.stories.edit', ['story' => $story->id]),
'manage_url' => route('creator.stories.edit', ['story' => $story->id]),
'analytics_url' => route('creator.stories.analytics', ['story' => $story->id]),
'create_url' => $this->createUrl(),
'actions' => $this->actionsFor($story, $story->status),
'created_at' => $story->created_at?->toIso8601String(),
'updated_at' => $story->updated_at?->toIso8601String(),
'published_at' => $story->published_at?->toIso8601String(),
'scheduled_at' => $story->scheduled_for?->toIso8601String(),
'featured' => (bool) $story->featured,
'activity_state' => in_array($story->status, ['published', 'scheduled'], true)
? 'active'
: ($story->status === 'archived' ? 'archived' : 'inactive'),
'metrics' => [
'views' => (int) $story->views,
'appreciation' => (int) $story->likes_count,
'shares' => 0,
'comments' => (int) $story->comments_count,
'saves' => 0,
],
'engagement_score' => (int) $story->views
+ ((int) $story->likes_count * 2)
+ ((int) $story->comments_count * 3),
'taxonomies' => [
'categories' => [],
'tags' => $story->tags->map(fn ($entry): array => [
'id' => (int) $entry->id,
'name' => (string) $entry->name,
'slug' => (string) $entry->slug,
])->values()->all(),
],
];
}
private function actionsFor(Story $story, string $status): array
{
$actions = [];
if (in_array($status, ['draft', 'pending_review', 'rejected'], true)) {
$actions[] = $this->requestAction(
'publish',
'Publish',
'fa-solid fa-rocket',
route('api.stories.update'),
[
'story_id' => (int) $story->id,
'status' => 'published',
],
null,
'put'
);
}
if (in_array($status, ['draft', 'pending_review', 'rejected', 'published', 'scheduled'], true)) {
$actions[] = $this->requestAction(
'archive',
'Archive',
'fa-solid fa-box-archive',
route('api.stories.update'),
[
'story_id' => (int) $story->id,
'status' => 'archived',
],
null,
'put'
);
}
if ($status === 'scheduled') {
$actions[] = $this->requestAction('publish_now', 'Publish now', 'fa-solid fa-bolt', route('api.studio.schedule.publishNow', ['module' => 'stories', 'id' => $story->id]), []);
$actions[] = $this->requestAction('unschedule', 'Unschedule', 'fa-solid fa-calendar-xmark', route('api.studio.schedule.unschedule', ['module' => 'stories', 'id' => $story->id]), []);
}
if ($status === 'archived') {
$actions[] = $this->requestAction(
'restore',
'Restore',
'fa-solid fa-rotate-left',
route('api.stories.update'),
[
'story_id' => (int) $story->id,
'status' => 'draft',
],
null,
'put'
);
}
$actions[] = $this->requestAction(
'delete',
'Delete',
'fa-solid fa-trash',
route('creator.stories.destroy', ['story' => $story->id]),
[],
'Delete this story permanently?',
'delete'
);
return $actions;
}
private function requestAction(string $key, string $label, string $icon, string $url, array $payload = [], ?string $confirm = null, string $method = 'post'): array
{
return [
'key' => $key,
'label' => $label,
'icon' => $icon,
'type' => 'request',
'method' => $method,
'url' => $url,
'payload' => $payload,
'confirm' => $confirm,
];
}
}