Merge pull request 'feat: add rating and comement' (#2) from rating/music into main
Reviewed-on: http://95.38.179.159:3000/Amirmahdi/back-meditation/pulls/2
This commit was merged in pull request #2.
This commit is contained in:
@@ -0,0 +1,147 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class CommentController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Add comment to any model
|
||||||
|
*/
|
||||||
|
public function addComment(Request $request, $type, $id)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'content' => 'required|string|max:1000',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$model = $this->getModel($type, $id);
|
||||||
|
$this->checkAccess($model);
|
||||||
|
|
||||||
|
$comment = $model->comments()->create([
|
||||||
|
'user_id' => auth()->id(),
|
||||||
|
'content' => $request->content,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Comment added successfully',
|
||||||
|
'comment' => $comment->load('user'),
|
||||||
|
], 201);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all comments for a model
|
||||||
|
*/
|
||||||
|
public function getComments($type, $id)
|
||||||
|
{
|
||||||
|
$model = $this->getModel($type, $id);
|
||||||
|
$this->checkAccess($model);
|
||||||
|
|
||||||
|
$comments = $model->comments()
|
||||||
|
->with('user')
|
||||||
|
->latest()
|
||||||
|
->paginate(20);
|
||||||
|
|
||||||
|
return response()->json($comments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update comment
|
||||||
|
*/
|
||||||
|
public function updateComment(Request $request, $type, $id, $commentId)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'content' => 'required|string|max:1000',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$comment = Comment::where('id', $commentId)
|
||||||
|
->where('user_id', auth()->id())
|
||||||
|
->where('commentable_id', $id)
|
||||||
|
->where('commentable_type', $this->getModelClass($type))
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
|
$comment->update([
|
||||||
|
'content' => $request->content,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Comment updated successfully',
|
||||||
|
'comment' => $comment->load('user'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete comment
|
||||||
|
*/
|
||||||
|
public function deleteComment($type, $id, $commentId)
|
||||||
|
{
|
||||||
|
$comment = Comment::where('id', $commentId)
|
||||||
|
->where('user_id', auth()->id())
|
||||||
|
->where('commentable_id', $id)
|
||||||
|
->where('commentable_type', $this->getModelClass($type))
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
|
$comment->delete();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Comment deleted successfully',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get most commented items
|
||||||
|
*/
|
||||||
|
public function mostCommented($type)
|
||||||
|
{
|
||||||
|
$modelClass = $this->getModelClass($type);
|
||||||
|
$userId = auth()->id();
|
||||||
|
|
||||||
|
$items = $modelClass::with(['image', 'user'])
|
||||||
|
->withCount('comments')
|
||||||
|
->where(function($query) use ($modelClass, $userId) {
|
||||||
|
if (property_exists($modelClass, 'type')) {
|
||||||
|
$query->where('type', 'public')
|
||||||
|
->orWhere(function($q) use ($userId) {
|
||||||
|
$q->where('type', 'private')
|
||||||
|
->where('user_id', $userId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->orderBy('comments_count', 'desc')
|
||||||
|
->limit(10)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return response()->json($items);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getModel($type, $id)
|
||||||
|
{
|
||||||
|
$modelClass = $this->getModelClass($type);
|
||||||
|
$model = $modelClass::findOrFail($id);
|
||||||
|
|
||||||
|
return $model;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getModelClass($type)
|
||||||
|
{
|
||||||
|
$models = [
|
||||||
|
'music' => \App\Models\Music::class,
|
||||||
|
'media' => \App\Models\Media::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!isset($models[$type])) {
|
||||||
|
abort(404, 'Invalid model type');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $models[$type];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkAccess($model)
|
||||||
|
{
|
||||||
|
if (property_exists($model, 'type') && $model->type === 'private') {
|
||||||
|
if (auth()->id() !== $model->user_id) {
|
||||||
|
abort(403, 'You do not have access to this item');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -321,7 +321,16 @@ public function filters(Request $request)
|
|||||||
|
|
||||||
public function show($id)
|
public function show($id)
|
||||||
{
|
{
|
||||||
$media = Media::with(['image', 'category', 'myNote', 'tags' ,'comments'])
|
$media = Media::with([
|
||||||
|
'image',
|
||||||
|
'category',
|
||||||
|
'myNote',
|
||||||
|
'tags',
|
||||||
|
'comments' => function($query) {
|
||||||
|
$query->with('user')->latest()->limit(10);
|
||||||
|
},
|
||||||
|
'ratings'
|
||||||
|
])
|
||||||
->where('id', $id)
|
->where('id', $id)
|
||||||
->where(function($query) {
|
->where(function($query) {
|
||||||
$query->where('visibility', 'public')
|
$query->where('visibility', 'public')
|
||||||
@@ -329,6 +338,15 @@ public function show($id)
|
|||||||
})
|
})
|
||||||
->firstOrFail();
|
->firstOrFail();
|
||||||
|
|
||||||
|
// Get user's specific comment
|
||||||
|
$userComment = $media->userComment();
|
||||||
|
|
||||||
|
// Get paginated comments
|
||||||
|
$comments = $media->comments()
|
||||||
|
->with('user')
|
||||||
|
->latest()
|
||||||
|
->paginate(15);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'id' => $media->id,
|
'id' => $media->id,
|
||||||
'title' => $media->title,
|
'title' => $media->title,
|
||||||
@@ -340,16 +358,26 @@ public function show($id)
|
|||||||
'visibility' => $media->visibility,
|
'visibility' => $media->visibility,
|
||||||
'created_at' => $media->created_at,
|
'created_at' => $media->created_at,
|
||||||
'updated_at' => $media->updated_at,
|
'updated_at' => $media->updated_at,
|
||||||
|
'is_premium' => $media->is_premium,
|
||||||
'image' => $media->image,
|
'image' => $media->image,
|
||||||
'category' => $media->category,
|
'category' => $media->category,
|
||||||
'tags' => $media->tags,
|
'tags' => $media->tags,
|
||||||
'myNote' => $media->myNote,
|
'myNote' => $media->myNote,
|
||||||
'is_saved' => $media->is_saved,
|
'is_saved' => $media->is_saved,
|
||||||
'is_premium' => $media->is_premium,
|
'statistics' => [
|
||||||
'comments' => $media->comments,
|
|
||||||
'average_rating' => $media->average_rating,
|
'average_rating' => $media->average_rating,
|
||||||
'your_rating' => $media->user_rating,
|
'total_ratings' => $media->ratings_count,
|
||||||
'comments_count' => $media->comments()->count(),
|
'total_comments' => $media->comments_count,
|
||||||
|
'rating_distribution' => $media->rating_distribution,
|
||||||
|
],
|
||||||
|
'user_interaction' => [
|
||||||
|
'has_rated' => $media->has_user_rated,
|
||||||
|
'user_rating' => $media->user_rating,
|
||||||
|
'has_commented' => $media->has_user_commented,
|
||||||
|
'user_comment' => $media->user_comment,
|
||||||
|
'user_comment_id' => $media->user_comment_id,
|
||||||
|
],
|
||||||
|
'comments' => $comments,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
public function rate(Request $request, $mediaId)
|
public function rate(Request $request, $mediaId)
|
||||||
|
|||||||
@@ -227,7 +227,15 @@ public function show($id)
|
|||||||
{
|
{
|
||||||
$userId = auth()->id();
|
$userId = auth()->id();
|
||||||
|
|
||||||
$music = Music::with(['image', 'playlist', 'tags'])
|
$music = Music::with([
|
||||||
|
'image',
|
||||||
|
'playlist',
|
||||||
|
'tags',
|
||||||
|
'comments' => function($query) {
|
||||||
|
$query->with('user')->latest()->limit(10);
|
||||||
|
},
|
||||||
|
'ratings'
|
||||||
|
])
|
||||||
->where(function($query) use ($userId) {
|
->where(function($query) use ($userId) {
|
||||||
$query->where('type', 'public')
|
$query->where('type', 'public')
|
||||||
->orWhere(function($q) use ($userId) {
|
->orWhere(function($q) use ($userId) {
|
||||||
@@ -237,7 +245,32 @@ public function show($id)
|
|||||||
})
|
})
|
||||||
->findOrFail($id);
|
->findOrFail($id);
|
||||||
|
|
||||||
return response()->json($music);
|
// Get user's specific comment
|
||||||
|
$userComment = $music->userComment();
|
||||||
|
|
||||||
|
// Get paginated comments for the response
|
||||||
|
$comments = $music->comments()
|
||||||
|
->with('user')
|
||||||
|
->latest()
|
||||||
|
->paginate(15);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'music' => $music,
|
||||||
|
'statistics' => [
|
||||||
|
'average_rating' => $music->average_rating,
|
||||||
|
'total_ratings' => $music->ratings_count,
|
||||||
|
'total_comments' => $music->comments_count,
|
||||||
|
'rating_distribution' => $music->rating_distribution,
|
||||||
|
],
|
||||||
|
'user_interaction' => [
|
||||||
|
'has_rated' => $music->has_user_rated,
|
||||||
|
'user_rating' => $music->user_rating,
|
||||||
|
'has_commented' => $music->has_user_commented,
|
||||||
|
'user_comment' => $music->user_comment,
|
||||||
|
'user_comment_id' => $music->user_comment_id,
|
||||||
|
],
|
||||||
|
'comments' => $comments,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Delete music
|
// ✅ Delete music
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class RatingController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Add or update rating for any model
|
||||||
|
*
|
||||||
|
* @param Request $request
|
||||||
|
* @param string $type (music, media, etc.)
|
||||||
|
* @param int $id
|
||||||
|
*/
|
||||||
|
public function rate(Request $request, $type, $id)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'stars' => 'required|integer|min:1|max:5',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$model = $this->getModel($type, $id);
|
||||||
|
$this->checkAccess($model);
|
||||||
|
|
||||||
|
$rating = $model->ratings()->updateOrCreate(
|
||||||
|
['user_id' => auth()->id()],
|
||||||
|
['stars' => $request->stars]
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Rating submitted successfully',
|
||||||
|
'rating' => $rating,
|
||||||
|
'average_rating' => $model->fresh()->average_rating,
|
||||||
|
'user_rating' => $rating->stars,
|
||||||
|
'total_ratings' => $model->fresh()->ratings_count,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user's rating for a model
|
||||||
|
*/
|
||||||
|
public function getUserRating($type, $id)
|
||||||
|
{
|
||||||
|
$model = $this->getModel($type, $id);
|
||||||
|
|
||||||
|
$rating = $model->ratings()
|
||||||
|
->where('user_id', auth()->id())
|
||||||
|
->first();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'rating' => $rating ? $rating->stars : null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete user's rating
|
||||||
|
*/
|
||||||
|
public function deleteRating($type, $id)
|
||||||
|
{
|
||||||
|
$model = $this->getModel($type, $id);
|
||||||
|
|
||||||
|
$deleted = $model->ratings()
|
||||||
|
->where('user_id', auth()->id())
|
||||||
|
->delete();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => $deleted ? 'Rating deleted successfully' : 'No rating found',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all ratings for a model
|
||||||
|
*/
|
||||||
|
public function getRatings($type, $id)
|
||||||
|
{
|
||||||
|
$model = $this->getModel($type, $id);
|
||||||
|
$this->checkAccess($model);
|
||||||
|
|
||||||
|
$ratings = $model->ratings()
|
||||||
|
->with('user')
|
||||||
|
->latest()
|
||||||
|
->paginate(20);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'average' => $model->average_rating,
|
||||||
|
'total' => $model->ratings_count,
|
||||||
|
'ratings' => $ratings,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get top rated items
|
||||||
|
*/
|
||||||
|
public function topRated($type)
|
||||||
|
{
|
||||||
|
$modelClass = $this->getModelClass($type);
|
||||||
|
$userId = auth()->id();
|
||||||
|
|
||||||
|
$items = $modelClass::with(['image', 'user'])
|
||||||
|
->withAvg('ratings', 'stars')
|
||||||
|
->where(function($query) use ($modelClass, $userId) {
|
||||||
|
if (method_exists($modelClass, 'isAccessible')) {
|
||||||
|
// Use model-specific access logic
|
||||||
|
} elseif (property_exists($modelClass, 'type')) {
|
||||||
|
$query->where('type', 'public')
|
||||||
|
->orWhere(function($q) use ($userId) {
|
||||||
|
$q->where('type', 'private')
|
||||||
|
->where('user_id', $userId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
->having('ratings_avg_stars', '>', 0)
|
||||||
|
->orderBy('ratings_avg_stars', 'desc')
|
||||||
|
->limit(10)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return response()->json($items);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getModel($type, $id)
|
||||||
|
{
|
||||||
|
$modelClass = $this->getModelClass($type);
|
||||||
|
$model = $modelClass::findOrFail($id);
|
||||||
|
|
||||||
|
return $model;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getModelClass($type)
|
||||||
|
{
|
||||||
|
$models = [
|
||||||
|
'music' => \App\Models\Music::class,
|
||||||
|
'media' => \App\Models\Media::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!isset($models[$type])) {
|
||||||
|
abort(404, 'Invalid model type');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $models[$type];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkAccess($model)
|
||||||
|
{
|
||||||
|
// Check if model has 'type' property (public/private)
|
||||||
|
if (property_exists($model, 'type') && $model->type === 'private') {
|
||||||
|
if (auth()->id() !== $model->user_id) {
|
||||||
|
abort(403, 'You do not have access to this item');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
-3
@@ -8,17 +8,44 @@ class Comment extends Model
|
|||||||
{
|
{
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'user_id',
|
'user_id',
|
||||||
'media_id',
|
'commentable_id',
|
||||||
|
'commentable_type',
|
||||||
'content',
|
'content',
|
||||||
];
|
];
|
||||||
|
protected $with = ['user'];
|
||||||
public function user()
|
public function user()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class);
|
return $this->belongsTo(User::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function commentable()
|
||||||
|
{
|
||||||
|
return $this->morphTo();
|
||||||
|
}
|
||||||
|
|
||||||
|
// For backward compatibility with Media
|
||||||
public function media()
|
public function media()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Media::class);
|
return $this->belongsTo(Media::class, 'commentable_id')->where('commentable_type', Media::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For Music
|
||||||
|
public function music()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Music::class, 'commentable_id')->where('commentable_type', Music::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if comment belongs to current user
|
||||||
|
public function getIsOwnerAttribute()
|
||||||
|
{
|
||||||
|
return auth()->check() && $this->user_id === auth()->id();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add timestamps formatted
|
||||||
|
public function getFormattedCreatedAtAttribute()
|
||||||
|
{
|
||||||
|
return $this->created_at->diffForHumans();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $appends = ['is_owner', 'formatted_created_at'];
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-24
@@ -3,9 +3,11 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use App\Traits\HasRatings;
|
||||||
|
use App\Traits\HasComments;
|
||||||
class Media extends Model
|
class Media extends Model
|
||||||
{
|
{
|
||||||
|
use HasRatings, HasComments;
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'user_id',
|
'user_id',
|
||||||
'image_id',
|
'image_id',
|
||||||
@@ -22,6 +24,12 @@ class Media extends Model
|
|||||||
protected $appends = [
|
protected $appends = [
|
||||||
'average_rating',
|
'average_rating',
|
||||||
'user_rating',
|
'user_rating',
|
||||||
|
'ratings_count',
|
||||||
|
'comments_count',
|
||||||
|
'has_user_commented', // Add this
|
||||||
|
'user_comment', // Add this
|
||||||
|
'user_comment_id', // Add this
|
||||||
|
'has_user_rated' // Add this
|
||||||
];
|
];
|
||||||
public function image()
|
public function image()
|
||||||
{
|
{
|
||||||
@@ -68,27 +76,4 @@ public function getIsSavedAttribute()
|
|||||||
->where('user_id', auth()->id())
|
->where('user_id', auth()->id())
|
||||||
->exists();
|
->exists();
|
||||||
}
|
}
|
||||||
public function ratings()
|
|
||||||
{
|
|
||||||
return $this->hasMany(Rating::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAverageRatingAttribute()
|
|
||||||
{
|
|
||||||
return round($this->ratings()->avg('stars'), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUserRatingAttribute()
|
|
||||||
{
|
|
||||||
if (!auth()->check()) return null;
|
|
||||||
|
|
||||||
return $this->ratings()
|
|
||||||
->where('user_id', auth()->id())
|
|
||||||
->value('stars');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function comments()
|
|
||||||
{
|
|
||||||
return $this->hasMany(Comment::class);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-3
@@ -7,9 +7,13 @@
|
|||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo; // ← Add this
|
use Illuminate\Database\Eloquent\Relations\BelongsTo; // ← Add this
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany; // ← Add this
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany; // ← Add this
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany; // ← Add this
|
use Illuminate\Database\Eloquent\Relations\HasMany; // ← Add this
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphMany; // ← Add this
|
||||||
|
use App\Traits\HasRatings;
|
||||||
|
use App\Traits\HasComments;
|
||||||
|
|
||||||
class Music extends Model
|
class Music extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory, HasRatings, HasComments;
|
||||||
protected $table = 'music';
|
protected $table = 'music';
|
||||||
|
|
||||||
|
|
||||||
@@ -39,8 +43,12 @@ public function tags(): BelongsToMany
|
|||||||
return $this->belongsToMany(Tag::class, 'music_tags');
|
return $this->belongsToMany(Tag::class, 'music_tags');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected $appends = ['url', 'image_url'];
|
protected $appends = ['url', 'image_url' , 'average_rating', 'user_rating', 'comments_count' , 'has_user_commented', // Add this
|
||||||
// Accessor for full URL
|
'user_comment', // Add this
|
||||||
|
'user_comment_id', // Add this
|
||||||
|
'has_user_rated' // Add this];
|
||||||
|
];
|
||||||
|
|
||||||
public function getUrlAttribute()
|
public function getUrlAttribute()
|
||||||
{
|
{
|
||||||
return asset('storage/' . $this->file_path);
|
return asset('storage/' . $this->file_path);
|
||||||
|
|||||||
+16
-3
@@ -8,17 +8,30 @@ class Rating extends Model
|
|||||||
{
|
{
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'user_id',
|
'user_id',
|
||||||
'media_id',
|
'rateable_id',
|
||||||
|
'rateable_type',
|
||||||
'stars',
|
'stars',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function media()
|
public function rateable()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Media::class);
|
return $this->morphTo();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function user()
|
public function user()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class);
|
return $this->belongsTo(User::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For backward compatibility with Media
|
||||||
|
public function media()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Media::class, 'rateable_id')->where('rateable_type', Media::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For Music
|
||||||
|
public function music()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Music::class, 'rateable_id')->where('rateable_type', Music::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
// app/Traits/HasComments.php
|
||||||
|
namespace App\Traits;
|
||||||
|
|
||||||
|
use App\Models\Comment;
|
||||||
|
|
||||||
|
trait HasComments
|
||||||
|
{
|
||||||
|
public function comments()
|
||||||
|
{
|
||||||
|
return $this->morphMany(Comment::class, 'commentable');
|
||||||
|
}
|
||||||
|
// Get user's specific comment
|
||||||
|
public function userComment()
|
||||||
|
{
|
||||||
|
if (!auth()->check()) return null;
|
||||||
|
|
||||||
|
return $this->comments()
|
||||||
|
->where('user_id', auth()->id())
|
||||||
|
->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user has commented
|
||||||
|
public function getHasUserCommentedAttribute()
|
||||||
|
{
|
||||||
|
return !is_null($this->userComment());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user's comment content
|
||||||
|
public function getUserCommentAttribute()
|
||||||
|
{
|
||||||
|
$comment = $this->userComment();
|
||||||
|
return $comment ? $comment->content : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user's comment id
|
||||||
|
public function getUserCommentIdAttribute()
|
||||||
|
{
|
||||||
|
$comment = $this->userComment();
|
||||||
|
return $comment ? $comment->id : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get latest comments with user info
|
||||||
|
public function getLatestCommentsAttribute()
|
||||||
|
{
|
||||||
|
return $this->comments()
|
||||||
|
->with('user')
|
||||||
|
->latest()
|
||||||
|
->limit(10)
|
||||||
|
->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get paginated comments
|
||||||
|
public function getPaginatedComments($perPage = 15)
|
||||||
|
{
|
||||||
|
return $this->comments()
|
||||||
|
->with('user')
|
||||||
|
->latest()
|
||||||
|
->paginate($perPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getCommentsCountAttribute()
|
||||||
|
{
|
||||||
|
return $this->comments()->count();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
// app/Traits/HasRatings.php
|
||||||
|
namespace App\Traits;
|
||||||
|
|
||||||
|
use App\Models\Rating;
|
||||||
|
|
||||||
|
trait HasRatings
|
||||||
|
{
|
||||||
|
public function ratings()
|
||||||
|
{
|
||||||
|
return $this->morphMany(Rating::class, 'rateable');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAverageRatingAttribute()
|
||||||
|
{
|
||||||
|
return round($this->ratings()->avg('stars'), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUserRatingAttribute()
|
||||||
|
{
|
||||||
|
if (!auth()->check()) return null;
|
||||||
|
|
||||||
|
return $this->ratings()
|
||||||
|
->where('user_id', auth()->id())
|
||||||
|
->value('stars');
|
||||||
|
}
|
||||||
|
// Get user's rating object
|
||||||
|
public function userRating()
|
||||||
|
{
|
||||||
|
if (!auth()->check()) return null;
|
||||||
|
|
||||||
|
return $this->ratings()
|
||||||
|
->where('user_id', auth()->id())
|
||||||
|
->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user has rated
|
||||||
|
public function getHasUserRatedAttribute()
|
||||||
|
{
|
||||||
|
return !is_null($this->userRating());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRatingsCountAttribute()
|
||||||
|
{
|
||||||
|
return $this->ratings()->count();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get rating distribution
|
||||||
|
public function getRatingDistributionAttribute()
|
||||||
|
{
|
||||||
|
$distribution = [];
|
||||||
|
for ($i = 1; $i <= 5; $i++) {
|
||||||
|
$distribution[$i] = $this->ratings()->where('stars', $i)->count();
|
||||||
|
}
|
||||||
|
return $distribution;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?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::table('comments', function (Blueprint $table) {
|
||||||
|
$table->dropForeign(['media_id']);
|
||||||
|
$table->dropColumn('media_id');
|
||||||
|
|
||||||
|
// Add polymorphic columns
|
||||||
|
$table->morphs('commentable');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('comments', function (Blueprint $table) {
|
||||||
|
$table->dropMorphs('commentable');
|
||||||
|
$table->foreignId('media_id')->constrained()->onDelete('cascade');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?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::dropIfExists('ratings');
|
||||||
|
|
||||||
|
Schema::create('ratings', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
$table->foreignId('user_id')
|
||||||
|
->constrained()
|
||||||
|
->cascadeOnDelete();
|
||||||
|
|
||||||
|
// polymorphic relation
|
||||||
|
$table->morphs('rateable');
|
||||||
|
|
||||||
|
$table->tinyInteger('stars'); // 1-5
|
||||||
|
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
// one rating per user per item
|
||||||
|
$table->unique([
|
||||||
|
'user_id',
|
||||||
|
'rateable_id',
|
||||||
|
'rateable_type'
|
||||||
|
], 'ratings_user_rateable_unique');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('ratings');
|
||||||
|
|
||||||
|
// optional: rollback old structure (if you still need it)
|
||||||
|
Schema::create('ratings', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
$table->foreignId('user_id')
|
||||||
|
->constrained()
|
||||||
|
->cascadeOnDelete();
|
||||||
|
|
||||||
|
$table->foreignId('media_id')
|
||||||
|
->constrained()
|
||||||
|
->cascadeOnDelete();
|
||||||
|
|
||||||
|
$table->tinyInteger('stars');
|
||||||
|
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->unique(['user_id', 'media_id']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -17,6 +17,9 @@
|
|||||||
use App\Http\Controllers\MediaController;
|
use App\Http\Controllers\MediaController;
|
||||||
use App\Http\Controllers\MusicCategoryController;
|
use App\Http\Controllers\MusicCategoryController;
|
||||||
use App\Http\Controllers\MusicPlaylistController;
|
use App\Http\Controllers\MusicPlaylistController;
|
||||||
|
use App\Http\Controllers\RatingController;
|
||||||
|
use App\Http\Controllers\CommentController;
|
||||||
|
|
||||||
|
|
||||||
Route::get('/test-hash', function() {
|
Route::get('/test-hash', function() {
|
||||||
$plain = 'amnk1380';
|
$plain = 'amnk1380';
|
||||||
@@ -169,4 +172,23 @@
|
|||||||
Route::delete('music/{musicId}/remove-from-playlist', [MusicController::class, 'removeFromPlaylist']);
|
Route::delete('music/{musicId}/remove-from-playlist', [MusicController::class, 'removeFromPlaylist']);
|
||||||
Route::post('music/update-order', [MusicController::class, 'updateOrder']);
|
Route::post('music/update-order', [MusicController::class, 'updateOrder']);
|
||||||
Route::apiResource('music', MusicController::class);
|
Route::apiResource('music', MusicController::class);
|
||||||
|
|
||||||
|
|
||||||
|
// Generic Rating Routes (works for both music and media)
|
||||||
|
Route::prefix('ratings')->group(function () {
|
||||||
|
Route::post('{type}/{id}', [RatingController::class, 'rate']);
|
||||||
|
Route::get('{type}/{id}/user', [RatingController::class, 'getUserRating']);
|
||||||
|
Route::delete('{type}/{id}', [RatingController::class, 'deleteRating']);
|
||||||
|
Route::get('{type}/{id}', [RatingController::class, 'getRatings']);
|
||||||
|
Route::get('top/{type}', [RatingController::class, 'topRated']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generic Comment Routes (works for both music and media)
|
||||||
|
Route::prefix('comments')->group(function () {
|
||||||
|
Route::post('{type}/{id}', [CommentController::class, 'addComment']);
|
||||||
|
Route::get('{type}/{id}', [CommentController::class, 'getComments']);
|
||||||
|
Route::put('{type}/{id}/{commentId}', [CommentController::class, 'updateComment']);
|
||||||
|
Route::delete('{type}/{id}/{commentId}', [CommentController::class, 'deleteComment']);
|
||||||
|
Route::get('most/{type}', [CommentController::class, 'mostCommented']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user