diff --git a/app/Http/Controllers/MediaController.php b/app/Http/Controllers/MediaController.php index f72cbb5..e48ce5b 100644 --- a/app/Http/Controllers/MediaController.php +++ b/app/Http/Controllers/MediaController.php @@ -74,7 +74,7 @@ public function store(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) { $q->where('visibility', 'public') ->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) { $baseQuery = Media::query() @@ -248,7 +321,7 @@ public function filters(Request $request) public function show($id) { - $media = Media::with(['image', 'category', 'myNote', 'tags']) + $media = Media::with(['image', 'category', 'myNote', 'tags' ,'comments']) ->where('id', $id) ->where(function($query) { $query->where('visibility', 'public') @@ -273,9 +346,62 @@ public function show($id) 'myNote' => $media->myNote, 'is_saved' => $media->is_saved, '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 public function update(Request $request, $id) { diff --git a/app/Models/Comment.php b/app/Models/Comment.php new file mode 100644 index 0000000..ae16986 --- /dev/null +++ b/app/Models/Comment.php @@ -0,0 +1,24 @@ +belongsTo(User::class); + } + + public function media() + { + return $this->belongsTo(Media::class); + } +} diff --git a/app/Models/Media.php b/app/Models/Media.php index 3c023cd..db37198 100644 --- a/app/Models/Media.php +++ b/app/Models/Media.php @@ -19,7 +19,10 @@ class Media extends Model 'visibility', 'is_premium' ]; - +protected $appends = [ + 'average_rating', + 'user_rating', +]; public function image() { return $this->belongsTo(Image::class); @@ -65,5 +68,27 @@ 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/Rating.php b/app/Models/Rating.php new file mode 100644 index 0000000..699305f --- /dev/null +++ b/app/Models/Rating.php @@ -0,0 +1,24 @@ +belongsTo(Media::class); + } + + public function user() + { + return $this->belongsTo(User::class); + } +} \ No newline at end of file diff --git a/database/migrations/2026_02_25_144901_create_ratings_table.php b/database/migrations/2026_02_25_144901_create_ratings_table.php new file mode 100644 index 0000000..babe04c --- /dev/null +++ b/database/migrations/2026_02_25_144901_create_ratings_table.php @@ -0,0 +1,32 @@ +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'); + } +}; diff --git a/database/migrations/2026_02_25_145337_create_comments_table.php b/database/migrations/2026_02_25_145337_create_comments_table.php new file mode 100644 index 0000000..5fba12b --- /dev/null +++ b/database/migrations/2026_02_25_145337_create_comments_table.php @@ -0,0 +1,30 @@ +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'); + } +}; diff --git a/routes/api.php b/routes/api.php index 0b25779..cd82291 100644 --- a/routes/api.php +++ b/routes/api.php @@ -143,7 +143,11 @@ Route::delete('/media/{id}', [MediaController::class, 'destroy']); Route::get('/media/{id}', [MediaController::class, 'show']); 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']); }); \ No newline at end of file