feat: add ratings

This commit is contained in:
2026-02-25 19:37:53 +03:30
parent 4d0823ef86
commit 676522d70d
7 changed files with 270 additions and 5 deletions
+128 -2
View File
@@ -74,7 +74,7 @@ public function store(Request $request)
} }
public function index(Request $request) public function index(Request $request)
{ {
$query = Media::with(['image', 'category', 'myNote', 'tags']) $query = Media::with(['image', 'category', 'myNote', 'tags','comments'])
->where(function ($q) { ->where(function ($q) {
$q->where('visibility', 'public') $q->where('visibility', 'public')
->orWhere('user_id', auth()->id()); ->orWhere('user_id', auth()->id());
@@ -179,6 +179,79 @@ public function index(Request $request)
); );
} }
public function submitFeedback(Request $request, $mediaId)
{
$request->validate([
'stars' => 'nullable|integer|min:1|max:5',
'content' => 'nullable|string|max:2000',
]);
// حداقل یکی باید ارسال شود
if (!$request->filled('stars') && !$request->filled('content')) {
return response()->json([
'message' => 'Stars or comment is required'
], 422);
}
$media = Media::where('id', $mediaId)
->where(function ($q) {
$q->where('visibility', 'public')
->orWhere('user_id', auth()->id());
})
->firstOrFail();
DB::beginTransaction();
try {
$rating = null;
$comment = null;
/*
|--------------------------------------------------------------------------
| Handle Rating (update or create)
|--------------------------------------------------------------------------
*/
if ($request->filled('stars')) {
$rating = $media->ratings()->updateOrCreate(
['user_id' => auth()->id()],
['stars' => $request->stars]
);
}
/*
|--------------------------------------------------------------------------
| 💬 Handle Comment (create only if exists)
|--------------------------------------------------------------------------
*/
if ($request->filled('content')) {
$comment = $media->comments()->create([
'user_id' => auth()->id(),
'content' => $request->content,
]);
}
DB::commit();
return response()->json([
'message' => 'Feedback submitted successfully',
'average_rating' => $media->fresh()->average_rating,
'your_rating' => optional($rating)->stars,
'comment' => $comment ? $comment->load('user') : null,
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'message' => 'Something went wrong',
'error' => $e->getMessage()
], 500);
}
}
public function filters(Request $request) public function filters(Request $request)
{ {
$baseQuery = Media::query() $baseQuery = Media::query()
@@ -248,7 +321,7 @@ public function filters(Request $request)
public function show($id) public function show($id)
{ {
$media = Media::with(['image', 'category', 'myNote', 'tags']) $media = Media::with(['image', 'category', 'myNote', 'tags' ,'comments'])
->where('id', $id) ->where('id', $id)
->where(function($query) { ->where(function($query) {
$query->where('visibility', 'public') $query->where('visibility', 'public')
@@ -273,9 +346,62 @@ public function show($id)
'myNote' => $media->myNote, 'myNote' => $media->myNote,
'is_saved' => $media->is_saved, 'is_saved' => $media->is_saved,
'is_premium' => $media->is_premium, 'is_premium' => $media->is_premium,
'comments' => $media->comments,
'average_rating' => $media->average_rating,
'your_rating' => $media->user_rating,
'comments_count' => $media->comments()->count(),
]); ]);
} }
public function rate(Request $request, $mediaId)
{
$request->validate([
'stars' => 'required|integer|min:1|max:5'
]);
$media = Media::findOrFail($mediaId);
$rating = $media->ratings()->updateOrCreate(
['user_id' => auth()->id()],
['stars' => $request->stars]
);
return response()->json([
'message' => 'Rating saved',
'average_rating' => $media->fresh()->average_rating,
'your_rating' => $rating->stars
]);
}
public function storeComment(Request $request, $mediaId)
{
$request->validate([
'content' => 'required|string|max:1000'
]);
$media = Media::findOrFail($mediaId);
$comment = $media->comments()->create([
'user_id' => auth()->id(),
'content' => $request->content
]);
return response()->json([
'message' => 'Comment added',
'comment' => $comment->load('user')
]);
}
public function getComments($mediaId)
{
$media = Media::findOrFail($mediaId);
return response()->json(
$media->comments()
->with('user')
->latest()
->paginate(10)
);
}
// UPDATE media // UPDATE media
public function update(Request $request, $id) public function update(Request $request, $id)
{ {
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
protected $fillable = [
'user_id',
'media_id',
'content',
];
public function user()
{
return $this->belongsTo(User::class);
}
public function media()
{
return $this->belongsTo(Media::class);
}
}
+27 -2
View File
@@ -19,7 +19,10 @@ class Media extends Model
'visibility', 'visibility',
'is_premium' 'is_premium'
]; ];
protected $appends = [
'average_rating',
'user_rating',
];
public function image() public function image()
{ {
return $this->belongsTo(Image::class); return $this->belongsTo(Image::class);
@@ -65,5 +68,27 @@ 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);
}
} }
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Rating extends Model
{
protected $fillable = [
'user_id',
'media_id',
'stars',
];
public function media()
{
return $this->belongsTo(Media::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
}
@@ -0,0 +1,32 @@
<?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('ratings', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('media_id')->constrained()->cascadeOnDelete();
$table->tinyInteger('stars'); // 1-5
$table->timestamps();
$table->unique(['user_id', 'media_id']);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('ratings');
}
};
@@ -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('comments', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->foreignId('media_id')->constrained()->cascadeOnDelete();
$table->text('content');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('comments');
}
};
+4
View File
@@ -143,6 +143,10 @@
Route::delete('/media/{id}', [MediaController::class, 'destroy']); Route::delete('/media/{id}', [MediaController::class, 'destroy']);
Route::get('/media/{id}', [MediaController::class, 'show']); Route::get('/media/{id}', [MediaController::class, 'show']);
Route::post('/media/{id}/save', [MediaController::class, 'toggleSaveMedia']); Route::post('/media/{id}/save', [MediaController::class, 'toggleSaveMedia']);
Route::post('/media/{id}/rate', [MediaController::class, 'rate']);
Route::post('/media/{id}/comment', [MediaController::class, 'storeComment']);
Route::get('/media/{id}/comments', [MediaController::class, 'getComments']);
Route::post('/media/{id}/feedback', [MediaController::class, 'submitFeedback']);
Route::post('/media/{id}/note', [MediaController::class, 'storeNote']); Route::post('/media/{id}/note', [MediaController::class, 'storeNote']);