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'; } }