['space', 'nature', ...], // up to 5 slugs * 'top_categories' => ['wallpapers', ...], // up to 3 slugs * 'followed_creators' => [1, 5, 23, ...], // user IDs * ] */ final class UserPreferenceService { private const CACHE_TTL = 300; // 5 minutes public function build(User $user): array { return Cache::remember( "user.prefs.{$user->id}", self::CACHE_TTL, fn () => $this->compute($user) ); } private function compute(User $user): array { return [ 'top_tags' => $this->topTags($user), 'top_categories' => $this->topCategories($user), 'followed_creators' => $this->followedCreatorIds($user), ]; } /** Top tag slugs derived from the user's favourited artworks */ private function topTags(User $user, int $limit = 5): array { return DB::table('artwork_favourites as af') ->join('artwork_tag as at', 'at.artwork_id', '=', 'af.artwork_id') ->join('tags as t', 't.id', '=', 'at.tag_id') ->where('af.user_id', $user->id) ->where('t.is_active', true) ->selectRaw('t.slug, COUNT(*) as cnt') ->groupBy('t.id', 't.slug') ->orderByDesc('cnt') ->limit($limit) ->pluck('slug') ->values() ->all(); } /** Top category slugs derived from the user's favourited artworks */ private function topCategories(User $user, int $limit = 3): array { return DB::table('artwork_favourites as af') ->join('artwork_category as ac', 'ac.artwork_id', '=', 'af.artwork_id') ->join('categories as c', 'c.id', '=', 'ac.category_id') ->where('af.user_id', $user->id) ->whereNull('c.deleted_at') ->selectRaw('c.slug, COUNT(*) as cnt') ->groupBy('c.id', 'c.slug') ->orderByDesc('cnt') ->limit($limit) ->pluck('slug') ->values() ->all(); } /** IDs of creators the user follows, latest follows first */ private function followedCreatorIds(User $user, int $limit = 100): array { return DB::table('user_followers') ->where('follower_id', $user->id) ->orderByDesc('created_at') ->limit($limit) ->pluck('user_id') ->values() ->all(); } }