Passed
Pull Request — main (#100)
by Tan
05:49
created

PostService::findBySlug()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 8
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 14
rs 10
1
<?php
2
3
namespace CSlant\Blog\Api\Services;
4
5
use CSlant\Blog\Api\Supports\Queries\QueryPost;
6
use CSlant\Blog\Core\Enums\StatusEnum;
7
use CSlant\Blog\Core\Facades\Base\SlugHelper;
8
use CSlant\Blog\Core\Http\Responses\Base\BaseHttpResponse;
9
use CSlant\Blog\Core\Models\Post;
10
use CSlant\Blog\Core\Models\Slug;
11
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
12
use Illuminate\Support\Arr;
13
14
/**
15
 * Class PostService
16
 *
17
 * @package CSlant\Blog\Api\Services
18
 *
19
 * @method BaseHttpResponse httpResponse()
20
 */
21
class PostService
22
{
23
    /**
24
     * @param  array<string, mixed>  $filters
25
     *
26
     * @return LengthAwarePaginator<int, Post>
27
     */
28
    public function getCustomFilters(array $filters): LengthAwarePaginator
29
    {
30
        $query = Post::query()->withCount(['comments', 'likes'])->with(['comments', 'likes']);
31
32
        $query = QueryPost::setBaseCustomFilterQuery($query, $filters);
33
34
        $query = $query
35
            ->wherePublished()
36
            ->orderBy(
37
                Arr::get($filters, 'order_by', 'updated_at'),
38
                Arr::get($filters, 'order', 'desc')
39
            );
40
41
        return $query->paginate((int) $filters['per_page']);
42
    }
43
44
    /**
45
     * Find post by slug
46
     *
47
     * @param string $slug
48
     * @return null|Post
49
     */
50
    public function findBySlug(string $slug): ?Post
51
    {
52
        /** @var Slug $slugModel */
53
        $slugModel = SlugHelper::getSlug($slug, SlugHelper::getPrefix(Post::getBaseModel()));
54
55
        if (!$slugModel) {
0 ignored issues
show
introduced by
$slugModel is of type CSlant\Blog\Core\Models\Slug, thus it always evaluated to true.
Loading history...
56
            return null;
57
        }
58
59
        return Post::query()
60
            ->where('id', $slugModel->reference_id)
61
            ->where('status', StatusEnum::PUBLISHED)
62
            ->with(['categories', 'tags'])
63
            ->first();
64
    }
65
66
    /**
67
     * Get navigation posts based on content relevance
68
     *
69
     * @param Post $currentPost
70
     * @return array{previous: null|Post, next: null|Post}
71
     */
72
    public function getNavigationPosts(Post $currentPost): array
73
    {
74
        $categoryIds = $currentPost->categories->pluck('id')->toArray();
75
        $tagIds = $currentPost->tags->pluck('id')->toArray();
76
77
        if (empty($categoryIds) && empty($tagIds)) {
78
            return ['previous' => null, 'next' => null];
79
        }
80
81
        // Get all published posts except current one
82
        $posts = Post::query()
83
            ->wherePublished()
84
            ->where('id', '!=', $currentPost->id)
85
            ->with(['slugable', 'categories', 'tags', 'author'])
86
            ->get();
87
88
        if ($posts->isEmpty()) {
89
            return ['previous' => null, 'next' => null];
90
        }
91
92
        // Calculate relevance score for each post
93
        $scoredPosts = $posts->map(function ($post) use ($categoryIds, $tagIds) {
94
            $post->load(['categories', 'tags']);
95
96
            $postCategoryIds = $post->categories->pluck('id')->toArray();
97
            $postTagIds = $post->tags->pluck('id')->toArray();
98
99
            // Calculate shared categories and tags
100
            $sharedCategories = count(array_intersect($categoryIds, $postCategoryIds));
101
            $sharedTags = count(array_intersect($tagIds, $postTagIds));
102
103
            // Weight categories higher than tags
104
            $relevanceScore = ($sharedCategories * 3) + ($sharedTags * 1);
105
106
            $post->relevance_score = $relevanceScore;
107
108
            return $post;
109
        });
110
111
        // Filter posts with relevance score > 0
112
        $relevantPosts = $scoredPosts
113
            ->filter(function ($post) {
114
                return $post->relevance_score > 0;
115
            })
116
            ->sortByDesc('relevance_score')
117
            ->values();
118
119
        if ($relevantPosts->isEmpty()) {
120
            return ['previous' => null, 'next' => null];
121
        }
122
123
        // Group posts by relevance score
124
        $groupedByScore = $relevantPosts->groupBy('relevance_score');
125
        $scores = $groupedByScore->keys()->sortDesc();
126
127
        $previous = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $previous is dead and can be removed.
Loading history...
128
        $next = null;
129
130
        // Get highest scoring post as previous
131
        $highestScorePosts = $groupedByScore->get($scores->first());
132
        $previous = $highestScorePosts->first();
133
134
        // For next, try to get a different post
135
        if ($scores->count() > 1) {
136
            // If we have multiple score levels, get from second highest
137
            $secondHighestPosts = $groupedByScore->get($scores->get(1));
138
            $next = $secondHighestPosts->first();
139
        } else {
140
            // If all posts have same score, get second post if available
141
            $next = $highestScorePosts->count() > 1 ? $highestScorePosts->get(1) : null;
142
        }
143
144
        return [
145
            'previous' => $previous,
146
            'next' => $next,
147
        ];
148
    }
149
}
150