Merge branch 'main' of http://git.approagency.ir/Amirmahdi/back-meditation
This commit is contained in:
@@ -36,6 +36,7 @@ public function store(Request $request)
|
|||||||
'title' => 'required|string|max:255',
|
'title' => 'required|string|max:255',
|
||||||
'description' => 'nullable|string',
|
'description' => 'nullable|string',
|
||||||
'link' => 'nullable|string|max:500',
|
'link' => 'nullable|string|max:500',
|
||||||
|
'button_text' => 'nullable|string|max:255',
|
||||||
'start_date' => 'nullable|date',
|
'start_date' => 'nullable|date',
|
||||||
'end_date' => 'nullable|date|after_or_equal:start_date',
|
'end_date' => 'nullable|date|after_or_equal:start_date',
|
||||||
'image_id' => 'nullable|exists:images,id',
|
'image_id' => 'nullable|exists:images,id',
|
||||||
@@ -68,6 +69,7 @@ public function update(Request $request, $id)
|
|||||||
'title' => 'sometimes|string|max:255',
|
'title' => 'sometimes|string|max:255',
|
||||||
'description' => 'nullable|string',
|
'description' => 'nullable|string',
|
||||||
'link' => 'nullable|string|max:500',
|
'link' => 'nullable|string|max:500',
|
||||||
|
'button_text' => 'nullable|string|max:255',
|
||||||
'start_date' => 'nullable|date',
|
'start_date' => 'nullable|date',
|
||||||
'end_date' => 'nullable|date|after_or_equal:start_date',
|
'end_date' => 'nullable|date|after_or_equal:start_date',
|
||||||
'image_id' => 'nullable|exists:images,id',
|
'image_id' => 'nullable|exists:images,id',
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ public function store(Request $request)
|
|||||||
'version_name' => 'required|string|max:255',
|
'version_name' => 'required|string|max:255',
|
||||||
'version_code' => 'required|integer|min:0',
|
'version_code' => 'required|integer|min:0',
|
||||||
'link' => 'nullable|string|max:500',
|
'link' => 'nullable|string|max:500',
|
||||||
|
'button_text' => 'nullable|string|max:255',
|
||||||
'image_id' => 'nullable|exists:images,id',
|
'image_id' => 'nullable|exists:images,id',
|
||||||
'image' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192',
|
'image' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192',
|
||||||
]);
|
]);
|
||||||
@@ -66,6 +67,7 @@ public function update(Request $request, $id)
|
|||||||
'version_name' => 'sometimes|string|max:255',
|
'version_name' => 'sometimes|string|max:255',
|
||||||
'version_code' => 'sometimes|integer|min:0',
|
'version_code' => 'sometimes|integer|min:0',
|
||||||
'link' => 'nullable|string|max:500',
|
'link' => 'nullable|string|max:500',
|
||||||
|
'button_text' => 'nullable|string|max:255',
|
||||||
'image_id' => 'nullable|exists:images,id',
|
'image_id' => 'nullable|exists:images,id',
|
||||||
'image' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192',
|
'image' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192',
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -33,14 +33,14 @@ public function index(Request $request)
|
|||||||
$query->with('subcategories');
|
$query->with('subcategories');
|
||||||
}
|
}
|
||||||
|
|
||||||
$categories = $query->orderBy('name')->get();
|
$categories = $query->orderBy('order')->orderBy('name')->get();
|
||||||
|
|
||||||
// No type filter → also fold in the playlist (music) categories so the
|
// No type filter → also fold in the playlist (music) categories so the
|
||||||
// response contains every category across both tables.
|
// response contains every category across both tables.
|
||||||
if (!$request->filled('type')) {
|
if (!$request->filled('type')) {
|
||||||
$categories = $categories
|
$categories = $categories
|
||||||
->concat($this->playlistCategories($request))
|
->concat($this->playlistCategories($request))
|
||||||
->sortBy('name')
|
->sortBy([['order', 'asc'], ['name', 'asc']])
|
||||||
->values();
|
->values();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +88,7 @@ public function store(Request $request)
|
|||||||
Rule::unique('categories', 'name')->where('type', $request->input('type', Category::TYPE_MEDIA)),
|
Rule::unique('categories', 'name')->where('type', $request->input('type', Category::TYPE_MEDIA)),
|
||||||
],
|
],
|
||||||
'type' => ['nullable', Rule::in(Category::TYPES)],
|
'type' => ['nullable', Rule::in(Category::TYPES)],
|
||||||
|
'order' => 'nullable|integer',
|
||||||
'description' => 'nullable|string',
|
'description' => 'nullable|string',
|
||||||
'icon' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192',
|
'icon' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192',
|
||||||
]);
|
]);
|
||||||
@@ -95,6 +96,7 @@ public function store(Request $request)
|
|||||||
$category = Category::create([
|
$category = Category::create([
|
||||||
'name' => $data['name'],
|
'name' => $data['name'],
|
||||||
'type' => $data['type'] ?? Category::TYPE_MEDIA,
|
'type' => $data['type'] ?? Category::TYPE_MEDIA,
|
||||||
|
'order' => $data['order'] ?? 0,
|
||||||
'description' => $data['description'] ?? null,
|
'description' => $data['description'] ?? null,
|
||||||
'icon' => $request->hasFile('icon')
|
'icon' => $request->hasFile('icon')
|
||||||
? $this->storeUpload($request->file('icon'), 'categories/icons')
|
? $this->storeUpload($request->file('icon'), 'categories/icons')
|
||||||
@@ -158,6 +160,7 @@ public function update(Request $request, $id)
|
|||||||
->ignore($category->id),
|
->ignore($category->id),
|
||||||
],
|
],
|
||||||
'type' => ['nullable', Rule::in(Category::TYPES)],
|
'type' => ['nullable', Rule::in(Category::TYPES)],
|
||||||
|
'order' => 'nullable|integer',
|
||||||
'description' => 'nullable|string',
|
'description' => 'nullable|string',
|
||||||
'icon' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192',
|
'icon' => 'nullable|file|extensions:jpg,jpeg,png,gif,webp,bmp,svg|max:8192',
|
||||||
]);
|
]);
|
||||||
@@ -165,6 +168,9 @@ public function update(Request $request, $id)
|
|||||||
if (array_key_exists('type', $data) && $data['type'] !== null) {
|
if (array_key_exists('type', $data) && $data['type'] !== null) {
|
||||||
$category->type = $data['type'];
|
$category->type = $data['type'];
|
||||||
}
|
}
|
||||||
|
if (array_key_exists('order', $data) && $data['order'] !== null) {
|
||||||
|
$category->order = $data['order'];
|
||||||
|
}
|
||||||
if (array_key_exists('name', $data)) {
|
if (array_key_exists('name', $data)) {
|
||||||
$category->name = $data['name'];
|
$category->name = $data['name'];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
class Announcement extends Model
|
class Announcement extends Model
|
||||||
{
|
{
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'image_id', 'title', 'description', 'link', 'start_date', 'end_date',
|
'image_id', 'title', 'description', 'link', 'button_text', 'start_date', 'end_date',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
class AppVersion extends Model
|
class AppVersion extends Model
|
||||||
{
|
{
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'image_id', 'title', 'description', 'version_name', 'version_code', 'link',
|
'image_id', 'title', 'description', 'version_name', 'version_code', 'link', 'button_text',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
|
|||||||
@@ -16,7 +16,11 @@ class Category extends Model
|
|||||||
self::TYPE_BREATHING_TEMPLATE,
|
self::TYPE_BREATHING_TEMPLATE,
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $fillable = ['name', 'type', 'description', 'icon'];
|
protected $fillable = ['name', 'type', 'order', 'description', 'icon'];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'order' => 'integer',
|
||||||
|
];
|
||||||
|
|
||||||
protected $appends = ['icon_url'];
|
protected $appends = ['icon_url'];
|
||||||
|
|
||||||
|
|||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('announcements', function (Blueprint $table) {
|
||||||
|
$table->string('button_text')->nullable()->after('link');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('app_versions', function (Blueprint $table) {
|
||||||
|
$table->string('button_text')->nullable()->after('link');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('announcements', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('button_text');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('app_versions', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('button_text');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('categories', function (Blueprint $table) {
|
||||||
|
$table->integer('order')->default(0)->after('type')->index();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('categories', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('order');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user