This commit is contained in:
2026-06-27 09:58:57 +03:30
parent 2b5a7c9b1b
commit 4a0a19fdff
@@ -297,40 +297,85 @@ public function suggestedMedia(Request $request)
{ {
$userId = auth()->id(); $userId = auth()->id();
$limit = (int) $request->input('limit', 20);
$limit = max(1, min($limit, 100));
$answersQuery = SurveyAnswer::where('user_id', $userId); $answersQuery = SurveyAnswer::where('user_id', $userId);
if ($request->filled('question_id')) { if ($request->filled('question_id')) {
$answersQuery->where('survey_question_id', $request->question_id); $answersQuery->where('survey_question_id', $request->question_id);
} }
$optionIds = $answersQuery->pluck('survey_option_id'); $optionIds = $answersQuery->pluck('survey_option_id');
if ($optionIds->isEmpty()) { // Tags behind the user's chosen options.
return response()->json([]); $tagIds = $optionIds->isEmpty()
? collect()
: DB::table('survey_option_tag')
->whereIn('survey_option_id', $optionIds)
->pluck('tag_id')
->unique()
->values();
// Visible to this user: public or their own.
$visible = function ($q) use ($userId) {
$q->where('visibility', 'public')->orWhere('user_id', $userId);
};
$eager = ['image', 'detailImage', 'categories', 'subCategories', 'tags'];
$media = collect();
if ($tagIds->isNotEmpty()) {
// 1) Media directly sharing the chosen tags, ranked by overlap.
$tagMatched = Media::query()
->whereHas('tags', fn ($q) => $q->whereIn('tags.id', $tagIds))
->withCount(['tags as match_count' => fn ($q) => $q->whereIn('tags.id', $tagIds)])
->where($visible)
->with($eager)
->orderByDesc('match_count')
->orderByDesc('created_at')
->get();
// 2) Broaden to "similar" media: same categories / sub-categories as
// the tag-matched media (so a thinly-tagged catalog still surfaces
// the rest of the topic, not just the one over-tagged item).
$categoryIds = $tagMatched->pluck('categories')->flatten(1)->pluck('id')->unique()->values();
$subCategoryIds = $tagMatched->pluck('subCategories')->flatten(1)->pluck('id')->unique()->values();
$similar = collect();
if ($categoryIds->isNotEmpty() || $subCategoryIds->isNotEmpty()) {
$similar = Media::query()
->where($visible)
->whereNotIn('id', $tagMatched->pluck('id'))
->where(function ($q) use ($categoryIds, $subCategoryIds) {
if ($categoryIds->isNotEmpty()) {
$q->orWhereHas('categories', fn ($c) => $c->whereIn('categories.id', $categoryIds));
}
if ($subCategoryIds->isNotEmpty()) {
$q->orWhereHas('subCategories', fn ($s) => $s->whereIn('sub_categories.id', $subCategoryIds));
}
})
->with($eager)
->orderByDesc('created_at')
->get()
->each(fn ($m) => $m->match_count = 0);
}
$media = $tagMatched->concat($similar);
} }
// Collect the tags behind the chosen options. // Fallback (no answers / no tags / nothing matched): popular public media,
$tagIds = DB::table('survey_option_tag') // so the suggestions row is never empty.
->whereIn('survey_option_id', $optionIds) if ($media->isEmpty()) {
->pluck('tag_id') $media = Media::query()
->unique() ->where($visible)
->values(); ->withCount('plays as plays_count')
->with($eager)
if ($tagIds->isEmpty()) { ->orderByDesc('plays_count')
return response()->json([]); ->orderByDesc('created_at')
->get();
} }
// Media sharing those tags, ranked by how many of them match. return response()->json($media->take($limit)->values());
$media = Media::query()
->whereHas('tags', fn ($q) => $q->whereIn('tags.id', $tagIds))
->withCount(['tags as match_count' => fn ($q) => $q->whereIn('tags.id', $tagIds)])
->where(function ($q) use ($userId) {
$q->where('visibility', 'public')->orWhere('user_id', $userId);
})
->with(['image', 'categories', 'subCategories', 'tags'])
->orderByDesc('match_count')
->orderByDesc('created_at')
->get();
return response()->json($media);
} }
private function syncOptions(SurveyQuestion $question, array $options): void private function syncOptions(SurveyQuestion $question, array $options): void