Files
SkinbaseNova/app/Http/Controllers/DashboardController.php
2026-03-12 07:22:38 +01:00

285 lines
11 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Models\Artwork;
use App\Models\Story;
use App\Models\User;
use App\Support\AvatarUrl;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
final class DashboardController extends Controller
{
public function index(Request $request)
{
$user = $request->user();
return view('dashboard', [
'page_title' => 'Dashboard',
'dashboard_user_name' => $user?->username ?: $user?->name ?: 'Creator',
'dashboard_is_creator' => Artwork::query()->where('user_id', $user->id)->exists(),
]);
}
public function activity(Request $request): JsonResponse
{
$user = $request->user();
$notificationItems = $user->notifications()
->latest()
->limit(12)
->get()
->map(function ($notification): array {
return [
'id' => (string) $notification->id,
'type' => 'notification',
'message' => $this->notificationMessage((array) $notification->data),
'reference_id' => (string) ($notification->id ?? ''),
'created_at' => $notification->created_at?->toIso8601String(),
'is_unread' => $notification->read_at === null,
'actor' => null,
];
});
$followItems = DB::table('user_followers as uf')
->join('users as follower', 'follower.id', '=', 'uf.follower_id')
->leftJoin('user_profiles as fp', 'fp.user_id', '=', 'follower.id')
->where('uf.user_id', $user->id)
->select([
'uf.follower_id as actor_id',
'follower.username as actor_username',
'follower.name as actor_name',
'fp.avatar_hash as actor_avatar_hash',
'uf.created_at',
])
->orderByDesc('uf.created_at')
->limit(10)
->get()
->map(function ($row): array {
return [
'id' => 'follow-' . (string) $row->actor_id . '-' . Carbon::parse((string) $row->created_at)->timestamp,
'type' => 'new_follower',
'message' => 'started following you',
'reference_id' => (string) $row->actor_id,
'created_at' => Carbon::parse((string) $row->created_at)->toIso8601String(),
'is_unread' => false,
'actor' => [
'id' => (int) $row->actor_id,
'name' => $row->actor_name,
'username' => $row->actor_username,
'avatar' => AvatarUrl::forUser((int) $row->actor_id, $row->actor_avatar_hash, 64),
],
];
});
$commentItems = DB::table('artwork_comments as c')
->join('artworks as a', 'a.id', '=', 'c.artwork_id')
->join('users as commenter', 'commenter.id', '=', 'c.user_id')
->leftJoin('user_profiles as cp', 'cp.user_id', '=', 'commenter.id')
->where('a.user_id', $user->id)
->where('c.user_id', '!=', $user->id)
->where('c.is_approved', true)
->whereNull('c.deleted_at')
->select([
'c.id as comment_id',
'c.created_at',
'a.id as artwork_id',
'a.slug as artwork_slug',
'a.title as artwork_title',
'commenter.id as actor_id',
'commenter.username as actor_username',
'commenter.name as actor_name',
'cp.avatar_hash as actor_avatar_hash',
])
->orderByDesc('c.created_at')
->limit(10)
->get()
->map(function ($row): array {
return [
'id' => 'comment-' . (string) $row->comment_id,
'type' => 'comment',
'message' => 'commented on your artwork',
'reference_id' => (string) $row->artwork_id,
'created_at' => Carbon::parse((string) $row->created_at)->toIso8601String(),
'is_unread' => false,
'actor' => [
'id' => (int) $row->actor_id,
'name' => $row->actor_name,
'username' => $row->actor_username,
'avatar' => AvatarUrl::forUser((int) $row->actor_id, $row->actor_avatar_hash, 64),
],
'context' => [
'artwork_id' => (int) $row->artwork_id,
'artwork_title' => $row->artwork_title,
'artwork_url' => '/art/' . $row->artwork_id . '/' . $row->artwork_slug,
],
];
});
$items = collect()
->concat($notificationItems)
->concat($followItems)
->concat($commentItems)
->sortByDesc(fn (array $item) => (string) ($item['created_at'] ?? ''))
->take(20)
->values();
return response()->json([
'data' => $items,
]);
}
public function analytics(Request $request): JsonResponse
{
$user = $request->user();
$artworksCount = Artwork::query()->where('user_id', $user->id)->count();
$storyAggregate = Story::query()
->where('creator_id', $user->id)
->selectRaw('COUNT(*) as total_stories, COALESCE(SUM(views),0) as total_story_views, COALESCE(SUM(likes_count),0) as total_story_likes')
->first();
$stats = $user->statistics;
$followersCount = (int) ($stats?->followers_count ?? $user->followers()->count());
$artworkLikes = (int) ($stats?->favorites_received_count ?? 0);
$storyLikes = (int) ($storyAggregate?->total_story_likes ?? 0);
return response()->json([
'data' => [
'is_creator' => $artworksCount > 0,
'total_artworks' => $artworksCount,
'total_stories' => (int) ($storyAggregate?->total_stories ?? 0),
'total_story_views' => (int) ($storyAggregate?->total_story_views ?? 0),
'total_followers' => $followersCount,
'total_likes' => $artworkLikes + $storyLikes,
],
]);
}
public function trendingArtworks(): JsonResponse
{
$cacheKey = 'dashboard:trending-artworks:v1';
$data = Cache::remember($cacheKey, 300, function (): array {
return Artwork::query()
->select('artworks.*')
->leftJoin('artwork_stats as s', 's.artwork_id', '=', 'artworks.id')
->public()
->with(['user.profile', 'stats'])
->orderByRaw('COALESCE(s.ranking_score, 0) DESC')
->orderByRaw('COALESCE(s.heat_score, 0) DESC')
->orderByRaw('COALESCE(s.favorites, 0) DESC')
->orderByRaw('COALESCE(s.views, 0) DESC')
->orderByRaw('COALESCE(s.comments_count, 0) DESC')
->limit(8)
->get()
->map(function (Artwork $artwork): array {
return [
'id' => $artwork->id,
'title' => $artwork->title,
'url' => '/art/' . $artwork->id . '/' . $artwork->slug,
'thumbnail' => $artwork->thumbUrl('md') ?? $artwork->thumbnail_url,
'likes' => (int) ($artwork->stats?->favorites ?? 0),
'views' => (int) ($artwork->stats?->views ?? 0),
'comments' => (int) ($artwork->stats?->comments_count ?? 0),
'creator' => [
'id' => (int) $artwork->user_id,
'username' => $artwork->user?->username,
'name' => $artwork->user?->name,
'url' => $artwork->user?->username ? '/@' . $artwork->user->username : null,
],
];
})
->values()
->all();
});
return response()->json(['data' => $data]);
}
public function recommendedCreators(Request $request): JsonResponse
{
$user = $request->user();
$cacheKey = 'dashboard:recommended-creators:' . $user->id . ':v1';
$data = Cache::remember($cacheKey, 600, function () use ($user): array {
$followingIds = DB::table('user_followers')
->where('follower_id', $user->id)
->pluck('user_id')
->map(fn ($id) => (int) $id)
->all();
$excludeIds = array_values(array_unique(array_merge([$user->id], $followingIds)));
return User::query()
->from('users')
->leftJoin('user_profiles as up', 'up.user_id', '=', 'users.id')
->leftJoin('user_statistics as us', 'us.user_id', '=', 'users.id')
->where('users.is_active', true)
->whereNotIn('users.id', $excludeIds)
->whereExists(function ($q): void {
$q->select(DB::raw(1))
->from('artworks')
->whereColumn('artworks.user_id', 'users.id')
->where('artworks.is_public', true)
->where('artworks.is_approved', true)
->whereNull('artworks.deleted_at');
})
->select([
'users.id',
'users.username',
'users.name',
'up.avatar_hash',
DB::raw('COALESCE(us.followers_count, 0) as followers_count'),
DB::raw('COALESCE(us.uploads_count, 0) as uploads_count'),
])
->orderByDesc('followers_count')
->orderByDesc('uploads_count')
->limit(6)
->get()
->map(function ($row): array {
$username = (string) ($row->username ?? '');
return [
'id' => (int) $row->id,
'username' => $username,
'name' => $row->name,
'url' => $username !== '' ? '/@' . $username : null,
'avatar' => AvatarUrl::forUser((int) $row->id, $row->avatar_hash, 64),
'followers_count' => (int) $row->followers_count,
'uploads_count' => (int) $row->uploads_count,
];
})
->values()
->all();
});
return response()->json(['data' => $data]);
}
private function notificationMessage(array $payload): string
{
$title = trim((string) ($payload['title'] ?? ''));
if ($title !== '') {
return $title;
}
$message = trim((string) ($payload['message'] ?? ''));
if ($message !== '') {
return $message;
}
$type = trim((string) ($payload['type'] ?? 'Notification'));
return $type !== '' ? $type : 'New notification';
}
}