From 6f5224a5d93217be3ba5049a7de77bbe1c2856c2 Mon Sep 17 00:00:00 2001 From: AmirmahdiNourkazemi Date: Wed, 20 May 2026 12:19:10 +0330 Subject: [PATCH 1/3] feat: add rating and comement --- app/Http/Controllers/CommentController.php | 147 +++++++++++++++++ app/Http/Controllers/RatingController.php | 151 ++++++++++++++++++ app/Models/Comment.php | 17 +- app/Models/Media.php | 31 +--- app/Models/Music.php | 39 ++++- app/Models/Rating.php | 19 ++- app/Traits/HasComments.php | 23 +++ app/Traits/HasRatings.php | 32 ++++ ...05_20_061423_make_comments_polymorphic.php | 33 ++++ ..._05_20_070900_make_ratings_polymorphic.php | 65 ++++++++ routes/api.php | 22 +++ 11 files changed, 547 insertions(+), 32 deletions(-) create mode 100644 app/Http/Controllers/CommentController.php create mode 100644 app/Http/Controllers/RatingController.php create mode 100644 app/Traits/HasComments.php create mode 100644 app/Traits/HasRatings.php create mode 100644 database/migrations/2026_05_20_061423_make_comments_polymorphic.php create mode 100644 database/migrations/2026_05_20_070900_make_ratings_polymorphic.php diff --git a/app/Http/Controllers/CommentController.php b/app/Http/Controllers/CommentController.php new file mode 100644 index 0000000..d59f142 --- /dev/null +++ b/app/Http/Controllers/CommentController.php @@ -0,0 +1,147 @@ +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'); + } + } + } +} diff --git a/app/Http/Controllers/RatingController.php b/app/Http/Controllers/RatingController.php new file mode 100644 index 0000000..e1c2d37 --- /dev/null +++ b/app/Http/Controllers/RatingController.php @@ -0,0 +1,151 @@ +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'); + } + } + } +} diff --git a/app/Models/Comment.php b/app/Models/Comment.php index ae16986..d629b8c 100644 --- a/app/Models/Comment.php +++ b/app/Models/Comment.php @@ -8,7 +8,8 @@ class Comment extends Model { protected $fillable = [ 'user_id', - 'media_id', + 'commentable_id', + 'commentable_type', 'content', ]; @@ -17,8 +18,20 @@ public function user() return $this->belongsTo(User::class); } + public function commentable() + { + return $this->morphTo(); + } + + // For backward compatibility with 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); } } diff --git a/app/Models/Media.php b/app/Models/Media.php index db37198..3445d44 100644 --- a/app/Models/Media.php +++ b/app/Models/Media.php @@ -3,9 +3,11 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; - +use App\Traits\HasRatings; +use App\Traits\HasComments; class Media extends Model { + use HasRatings, HasComments; protected $fillable = [ 'user_id', 'image_id', @@ -21,7 +23,9 @@ class Media extends Model ]; protected $appends = [ 'average_rating', - 'user_rating', + 'user_rating', + 'ratings_count', + 'comments_count' ]; public function image() { @@ -68,27 +72,4 @@ public function getIsSavedAttribute() ->where('user_id', auth()->id()) ->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); -} } diff --git a/app/Models/Music.php b/app/Models/Music.php index b372274..cf13e10 100644 --- a/app/Models/Music.php +++ b/app/Models/Music.php @@ -7,9 +7,13 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; // ← Add this use Illuminate\Database\Eloquent\Relations\BelongsToMany; // ← 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 { - use HasFactory; + use HasFactory, HasRatings, HasComments; protected $table = 'music'; @@ -39,7 +43,38 @@ public function tags(): BelongsToMany return $this->belongsToMany(Tag::class, 'music_tags'); } - protected $appends = ['url', 'image_url']; + protected $appends = ['url', 'image_url' , 'average_rating', 'user_rating', 'comments_count']; + + // New rating and comment relationships + public function ratings(): MorphMany + { + return $this->morphMany(Rating::class, 'rateable'); + } + + public function comments(): MorphMany + { + return $this->morphMany(Comment::class, 'commentable'); + } + + // Accessors for ratings + public function getAverageRatingAttribute(): float + { + return round($this->ratings()->avg('stars'), 1); + } + + public function getUserRatingAttribute(): ?int + { + if (!auth()->check()) return null; + + return $this->ratings() + ->where('user_id', auth()->id()) + ->value('stars'); + } + + public function getCommentsCountAttribute(): int + { + return $this->comments()->count(); + } // Accessor for full URL public function getUrlAttribute() { diff --git a/app/Models/Rating.php b/app/Models/Rating.php index 699305f..864f0b9 100644 --- a/app/Models/Rating.php +++ b/app/Models/Rating.php @@ -8,17 +8,30 @@ class Rating extends Model { protected $fillable = [ 'user_id', - 'media_id', + 'rateable_id', + 'rateable_type', 'stars', ]; - public function media() + public function rateable() { - return $this->belongsTo(Media::class); + return $this->morphTo(); } public function user() { 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); + } } \ No newline at end of file diff --git a/app/Traits/HasComments.php b/app/Traits/HasComments.php new file mode 100644 index 0000000..d3acc5b --- /dev/null +++ b/app/Traits/HasComments.php @@ -0,0 +1,23 @@ +morphMany(Comment::class, 'commentable'); + } + + public function getLatestCommentsAttribute() + { + return $this->comments()->latest()->limit(5)->get(); + } + + public function getCommentsCountAttribute() + { + return $this->comments()->count(); + } +} \ No newline at end of file diff --git a/app/Traits/HasRatings.php b/app/Traits/HasRatings.php new file mode 100644 index 0000000..a38557c --- /dev/null +++ b/app/Traits/HasRatings.php @@ -0,0 +1,32 @@ +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'); + } + + public function getRatingsCountAttribute() + { + return $this->ratings()->count(); + } +} \ No newline at end of file diff --git a/database/migrations/2026_05_20_061423_make_comments_polymorphic.php b/database/migrations/2026_05_20_061423_make_comments_polymorphic.php new file mode 100644 index 0000000..877f4e2 --- /dev/null +++ b/database/migrations/2026_05_20_061423_make_comments_polymorphic.php @@ -0,0 +1,33 @@ +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'); + }); + } +}; diff --git a/database/migrations/2026_05_20_070900_make_ratings_polymorphic.php b/database/migrations/2026_05_20_070900_make_ratings_polymorphic.php new file mode 100644 index 0000000..1fe4270 --- /dev/null +++ b/database/migrations/2026_05_20_070900_make_ratings_polymorphic.php @@ -0,0 +1,65 @@ +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']); + }); + } +}; \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index eeeee92..264cb51 100644 --- a/routes/api.php +++ b/routes/api.php @@ -17,6 +17,9 @@ use App\Http\Controllers\MediaController; use App\Http\Controllers\MusicCategoryController; use App\Http\Controllers\MusicPlaylistController; +use App\Http\Controllers\RatingController; +use App\Http\Controllers\CommentController; + Route::get('/test-hash', function() { $plain = 'amnk1380'; @@ -169,4 +172,23 @@ Route::delete('music/{musicId}/remove-from-playlist', [MusicController::class, 'removeFromPlaylist']); Route::post('music/update-order', [MusicController::class, 'updateOrder']); 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']); + }); }); \ No newline at end of file From 02448db3466b01007b8a267a73b884c2119d2d03 Mon Sep 17 00:00:00 2001 From: AmirmahdiNourkazemi Date: Wed, 20 May 2026 15:42:10 +0330 Subject: [PATCH 2/3] refactor --- app/Http/Controllers/MediaController.php | 4 +-- app/Models/Music.php | 31 ------------------------ 2 files changed, 2 insertions(+), 33 deletions(-) diff --git a/app/Http/Controllers/MediaController.php b/app/Http/Controllers/MediaController.php index e48ce5b..5bc147f 100644 --- a/app/Http/Controllers/MediaController.php +++ b/app/Http/Controllers/MediaController.php @@ -321,7 +321,7 @@ public function filters(Request $request) public function show($id) { - $media = Media::with(['image', 'category', 'myNote', 'tags' ,'comments']) + $media = Media::with(['image', 'category', 'myNote', 'tags']) ->where('id', $id) ->where(function($query) { $query->where('visibility', 'public') @@ -348,7 +348,7 @@ public function show($id) 'is_premium' => $media->is_premium, 'comments' => $media->comments, 'average_rating' => $media->average_rating, - 'your_rating' => $media->user_rating, + 'user_rating' => $media->user_rating, 'comments_count' => $media->comments()->count(), ]); } diff --git a/app/Models/Music.php b/app/Models/Music.php index cf13e10..64e8c1e 100644 --- a/app/Models/Music.php +++ b/app/Models/Music.php @@ -45,37 +45,6 @@ public function tags(): BelongsToMany protected $appends = ['url', 'image_url' , 'average_rating', 'user_rating', 'comments_count']; - // New rating and comment relationships - public function ratings(): MorphMany - { - return $this->morphMany(Rating::class, 'rateable'); - } - - public function comments(): MorphMany - { - return $this->morphMany(Comment::class, 'commentable'); - } - - // Accessors for ratings - public function getAverageRatingAttribute(): float - { - return round($this->ratings()->avg('stars'), 1); - } - - public function getUserRatingAttribute(): ?int - { - if (!auth()->check()) return null; - - return $this->ratings() - ->where('user_id', auth()->id()) - ->value('stars'); - } - - public function getCommentsCountAttribute(): int - { - return $this->comments()->count(); - } - // Accessor for full URL public function getUrlAttribute() { return asset('storage/' . $this->file_path); From d0d48866f0abe39822d9c8ed3b9ac8a71df9a170 Mon Sep 17 00:00:00 2001 From: AmirmahdiNourkazemi Date: Wed, 20 May 2026 15:52:28 +0330 Subject: [PATCH 3/3] feat: user rating and comment --- app/Http/Controllers/MediaController.php | 48 +++++++++++++++++++----- app/Http/Controllers/MusicController.php | 41 ++++++++++++++++++-- app/Models/Comment.php | 16 +++++++- app/Models/Media.php | 6 ++- app/Models/Music.php | 6 ++- app/Traits/HasComments.php | 48 +++++++++++++++++++++++- app/Traits/HasRatings.php | 25 ++++++++++++ 7 files changed, 171 insertions(+), 19 deletions(-) diff --git a/app/Http/Controllers/MediaController.php b/app/Http/Controllers/MediaController.php index 5bc147f..93e652f 100644 --- a/app/Http/Controllers/MediaController.php +++ b/app/Http/Controllers/MediaController.php @@ -319,9 +319,18 @@ public function filters(Request $request) ]); } - public function show($id) - { - $media = Media::with(['image', 'category', 'myNote', 'tags']) + public function show($id) +{ + $media = Media::with([ + 'image', + 'category', + 'myNote', + 'tags', + 'comments' => function($query) { + $query->with('user')->latest()->limit(10); + }, + 'ratings' + ]) ->where('id', $id) ->where(function($query) { $query->where('visibility', 'public') @@ -329,7 +338,16 @@ public function show($id) }) ->firstOrFail(); - return response()->json([ + // Get user's specific comment + $userComment = $media->userComment(); + + // Get paginated comments + $comments = $media->comments() + ->with('user') + ->latest() + ->paginate(15); + + return response()->json([ 'id' => $media->id, 'title' => $media->title, 'caption' => $media->caption, @@ -340,18 +358,28 @@ public function show($id) 'visibility' => $media->visibility, 'created_at' => $media->created_at, 'updated_at' => $media->updated_at, + 'is_premium' => $media->is_premium, 'image' => $media->image, 'category' => $media->category, 'tags' => $media->tags, 'myNote' => $media->myNote, 'is_saved' => $media->is_saved, - 'is_premium' => $media->is_premium, - 'comments' => $media->comments, - 'average_rating' => $media->average_rating, - 'user_rating' => $media->user_rating, - 'comments_count' => $media->comments()->count(), + 'statistics' => [ + 'average_rating' => $media->average_rating, + 'total_ratings' => $media->ratings_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) { $request->validate([ diff --git a/app/Http/Controllers/MusicController.php b/app/Http/Controllers/MusicController.php index d880ff4..b50d1d7 100644 --- a/app/Http/Controllers/MusicController.php +++ b/app/Http/Controllers/MusicController.php @@ -223,11 +223,19 @@ public function update(Request $request, $id) // ✅ Get all public + user private music - public function show($id) +public function show($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) { $query->where('type', 'public') ->orWhere(function($q) use ($userId) { @@ -236,8 +244,33 @@ public function show($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 diff --git a/app/Models/Comment.php b/app/Models/Comment.php index d629b8c..f5286d8 100644 --- a/app/Models/Comment.php +++ b/app/Models/Comment.php @@ -12,7 +12,7 @@ class Comment extends Model 'commentable_type', 'content', ]; - + protected $with = ['user']; public function user() { return $this->belongsTo(User::class); @@ -34,4 +34,18 @@ 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']; } diff --git a/app/Models/Media.php b/app/Models/Media.php index 3445d44..cf16fd3 100644 --- a/app/Models/Media.php +++ b/app/Models/Media.php @@ -25,7 +25,11 @@ class Media extends Model 'average_rating', 'user_rating', 'ratings_count', - 'comments_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() { diff --git a/app/Models/Music.php b/app/Models/Music.php index 64e8c1e..0dbb00c 100644 --- a/app/Models/Music.php +++ b/app/Models/Music.php @@ -43,7 +43,11 @@ public function tags(): BelongsToMany return $this->belongsToMany(Tag::class, 'music_tags'); } - protected $appends = ['url', 'image_url' , 'average_rating', 'user_rating', 'comments_count']; + protected $appends = ['url', 'image_url' , 'average_rating', 'user_rating', 'comments_count' , 'has_user_commented', // Add this + 'user_comment', // Add this + 'user_comment_id', // Add this + 'has_user_rated' // Add this]; + ]; public function getUrlAttribute() { diff --git a/app/Traits/HasComments.php b/app/Traits/HasComments.php index d3acc5b..0ef401f 100644 --- a/app/Traits/HasComments.php +++ b/app/Traits/HasComments.php @@ -10,11 +10,55 @@ 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()->latest()->limit(5)->get(); + 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() { diff --git a/app/Traits/HasRatings.php b/app/Traits/HasRatings.php index a38557c..426834b 100644 --- a/app/Traits/HasRatings.php +++ b/app/Traits/HasRatings.php @@ -24,9 +24,34 @@ public function getUserRatingAttribute() ->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; + } } \ No newline at end of file