feat: add like table

This commit is contained in:
2026-05-24 23:21:00 +03:30
parent d8f769e738
commit 5800f64f88
7 changed files with 399 additions and 3 deletions
+234
View File
@@ -0,0 +1,234 @@
<?php
namespace App\Http\Controllers;
use App\Models\Like;
use Illuminate\Http\Request;
class LikeController extends Controller
{
/**
* Like an item (music or media)
*/
public function like(Request $request)
{
$request->validate([
'type' => 'required|string|in:music,media',
'id' => 'required|integer',
]);
$model = $this->getModel($request->type, $request->id);
if (!$model) {
return response()->json([
'message' => 'Item not found'
], 404);
}
$result = $model->addLike();
return response()->json([
'message' => 'Item liked successfully',
'is_liked' => true,
'likes_count' => $model->likes_count
]);
}
/**
* Unlike an item
*/
public function unlike(Request $request)
{
$request->validate([
'type' => 'required|string|in:music,media',
'id' => 'required|integer',
]);
$model = $this->getModel($request->type, $request->id);
if (!$model) {
return response()->json([
'message' => 'Item not found'
], 404);
}
$result = $model->removeLike();
return response()->json([
'message' => 'Item unliked successfully',
'is_liked' => false,
'likes_count' => $model->likes_count
]);
}
/**
* Toggle like status
*/
public function toggleLike(Request $request)
{
$request->validate([
'type' => 'required|string|in:music,media',
'id' => 'required|integer',
]);
$model = $this->getModel($request->type, $request->id);
if (!$model) {
return response()->json([
'message' => 'Item not found'
], 404);
}
$result = $model->toggleLike();
return response()->json([
'message' => $result ? 'Item liked successfully' : 'Item unliked successfully',
'is_liked' => $model->is_liked,
'likes_count' => $model->likes_count
]);
}
/**
* Get all liked items for the authenticated user
*/
public function myLikedItems(Request $request)
{
$type = $request->get('type'); // Optional filter by type (music or media)
$query = Like::with('likeable')
->where('user_id', auth()->id());
if ($type) {
$modelClass = $this->getModelClass($type);
$query->where('likeable_type', $modelClass);
}
$likedItems = $query->latest()->paginate(20);
return response()->json([
'data' => $likedItems,
'total' => $likedItems->total(),
'types' => [
'music' => 'App\\Models\\Music',
'media' => 'App\\Models\\Media',
]
]);
// Transform the response
// $transformedItems = $likedItems->map(function ($likeItem) {
// $item = $likeItem->likeable;
// if (!$item) return null;
// $baseData = [
// 'like_id' => $likeItem->id,
// 'liked_at' => $likeItem->created_at,
// 'type' => class_basename($likeItem->likeable_type),
// 'is_liked' => true,
// ];
// // Add type-specific data
// if ($item instanceof \App\Models\Music) {
// return array_merge($baseData, [
// 'id' => $item->id,
// 'title' => $item->title,
// 'artist' => $item->artist,
// 'duration' => $item->duration_formatted ?? $item->duration,
// 'image_url' => $item->image_url,
// 'likes_count' => $item->likes_count,
// 'type_display' => 'music'
// ]);
// } elseif ($item instanceof \App\Models\Media) {
// return array_merge($baseData, [
// 'id' => $item->id,
// 'title' => $item->title,
// 'caption' => $item->caption,
// 'media_type' => $item->type,
// 'duration' => $item->duration,
// 'image_url' => $item->image->url ?? null,
// 'likes_count' => $item->likes_count,
// 'type_display' => 'media'
// ]);
// }
// return $baseData;
// })->filter();
// return response()->json([
// 'data' => $transformedItems,
// 'total' => $likedItems->total(),
// 'current_page' => $likedItems->currentPage(),
// 'last_page' => $likedItems->lastPage(),
// 'per_page' => $likedItems->perPage(),
// ]);
}
/**
* Check if specific item is liked by user
*/
public function checkLiked(Request $request)
{
$request->validate([
'type' => 'required|string|in:music,media',
'id' => 'required|integer',
]);
$model = $this->getModel($request->type, $request->id);
if (!$model) {
return response()->json([
'message' => 'Item not found'
], 404);
}
return response()->json([
'is_liked' => $model->is_liked,
'likes_count' => $model->likes_count
]);
}
/**
* Get top liked items
*/
public function topLiked(Request $request)
{
$type = $request->get('type', 'music'); // Default to music
$limit = $request->get('limit', 10);
$modelClass = $this->getModelClass($type);
$items = $modelClass::with(['image'])
->withCount('likes')
->where(function($query) use ($modelClass) {
if (property_exists($modelClass, 'type')) {
$query->where('type', 'public');
}
if (property_exists($modelClass, 'visibility')) {
$query->where('visibility', 'public');
}
})
->orderBy('likes_count', 'desc')
->limit($limit)
->get();
return response()->json([
'data' => $items,
'type' => $type,
'total' => $items->count()
]);
}
private function getModel($type, $id)
{
$modelClass = $this->getModelClass($type);
return $modelClass::find($id);
}
private function getModelClass($type)
{
$models = [
'music' => \App\Models\Music::class,
'media' => \App\Models\Media::class,
];
return $models[$type] ?? null;
}
}
+56
View File
@@ -0,0 +1,56 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class Like extends Model
{
protected $table = 'likes';
protected $fillable = [
'user_id',
'likeable_id',
'likeable_type',
];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function likeable(): MorphTo
{
return $this->morphTo();
}
// Helper methods
public static function getLikedItemsForUser($userId, $type = null)
{
$query = self::with('likeable')->where('user_id', $userId);
if ($type) {
$query->where('likeable_type', $type);
}
return $query->latest()->get();
}
public static function isLikedByUser($userId, $likeableId, $likeableType)
{
return self::where([
'user_id' => $userId,
'likeable_id' => $likeableId,
'likeable_type' => $likeableType,
])->exists();
}
public static function getLikeCount($likeableId, $likeableType)
{
return self::where([
'likeable_id' => $likeableId,
'likeable_type' => $likeableType,
])->count();
}
}
+3 -1
View File
@@ -33,7 +33,9 @@ class Media extends Model
'user_comment_id', 'user_comment_id',
'has_user_rated', 'has_user_rated',
'is_saved', 'is_saved',
'saved_count' 'saved_count',
'is_liked',
'likes_count'
]; ];
public function image() public function image()
{ {
+5 -2
View File
@@ -11,10 +11,11 @@
use App\Traits\HasRatings; use App\Traits\HasRatings;
use App\Traits\HasComments; use App\Traits\HasComments;
use App\Traits\HasSaves; use App\Traits\HasSaves;
use App\Traits\HasLikes;
class Music extends Model class Music extends Model
{ {
use HasFactory, HasRatings, HasComments , HasSaves; use HasFactory, HasRatings, HasComments , HasSaves,HasLikes;
protected $table = 'music'; protected $table = 'music';
@@ -54,7 +55,9 @@ public function tags(): BelongsToMany
'user_comment_id', 'user_comment_id',
'has_user_rated' , 'has_user_rated' ,
'is_saved', 'is_saved',
'saved_count' 'saved_count',
'is_liked',
'likes_count'
]; ];
public function getUrlAttribute() public function getUrlAttribute()
+59
View File
@@ -0,0 +1,59 @@
<?php
// app/Traits/HasLikes.php
namespace App\Traits;
use App\Models\Like;
use Illuminate\Database\Eloquent\Relations\MorphMany;
trait HasLikes
{
public function likes(): MorphMany
{
return $this->morphMany(Like::class, 'likeable');
}
public function getIsLikedAttribute()
{
if (!auth()->check()) return false;
return $this->likes()
->where('user_id', auth()->id())
->exists();
}
public function getLikesCountAttribute()
{
return $this->likes()->count();
}
public function toggleLike()
{
if ($this->getIsLikedAttribute()) {
return $this->removeLike();
} else {
return $this->addLike();
}
}
public function addLike()
{
if ($this->getIsLikedAttribute()) return false;
return Like::create([
'user_id' => auth()->id(),
'likeable_id' => $this->id,
'likeable_type' => get_class($this),
]);
}
public function removeLike()
{
if (!$this->getIsLikedAttribute()) return false;
return Like::where([
'user_id' => auth()->id(),
'likeable_id' => $this->id,
'likeable_type' => get_class($this),
])->delete();
}
}
@@ -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
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('likes', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->morphs('likeable');
$table->timestamps();
$table->unique(['user_id', 'likeable_id', 'likeable_type']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('likes');
}
};
+12
View File
@@ -21,6 +21,7 @@
use App\Http\Controllers\CommentController; use App\Http\Controllers\CommentController;
use App\Http\Controllers\MusicSubcategoryController; use App\Http\Controllers\MusicSubcategoryController;
use App\Http\Controllers\SaveController; use App\Http\Controllers\SaveController;
use App\Http\Controllers\LikeController;
Route::get('/test-hash', function() { Route::get('/test-hash', function() {
$plain = 'amnk1380'; $plain = 'amnk1380';
@@ -211,4 +212,15 @@
Route::get('/my-saved', [SaveController::class, 'mySavedItems']); Route::get('/my-saved', [SaveController::class, 'mySavedItems']);
Route::post('/check', [SaveController::class, 'checkSaved']); Route::post('/check', [SaveController::class, 'checkSaved']);
}); });
// Like routes (only for music and media)
Route::prefix('likes')->group(function () {
Route::post('/like', [LikeController::class, 'like']);
Route::post('/unlike', [LikeController::class, 'unlike']);
Route::post('/toggle', [LikeController::class, 'toggleLike']);
Route::get('/my-liked', [LikeController::class, 'myLikedItems']);
Route::post('/check', [LikeController::class, 'checkLiked']);
Route::get('/top-liked', [LikeController::class, 'topLiked']);
});
}); });