feat: add rating and comement
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-25
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+37
-2
@@ -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()
|
||||
{
|
||||
|
||||
+16
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
// app/Traits/HasComments.php
|
||||
namespace App\Traits;
|
||||
|
||||
use App\Models\Comment;
|
||||
|
||||
trait HasComments
|
||||
{
|
||||
public function comments()
|
||||
{
|
||||
return $this->morphMany(Comment::class, 'commentable');
|
||||
}
|
||||
|
||||
public function getLatestCommentsAttribute()
|
||||
{
|
||||
return $this->comments()->latest()->limit(5)->get();
|
||||
}
|
||||
|
||||
public function getCommentsCountAttribute()
|
||||
{
|
||||
return $this->comments()->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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');
|
||||
}
|
||||
|
||||
public function getRatingsCountAttribute()
|
||||
{
|
||||
return $this->ratings()->count();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user