diff --git a/app/Http/Controllers/AppFeedbackController.php b/app/Http/Controllers/AppFeedbackController.php new file mode 100644 index 0000000..8333cc4 --- /dev/null +++ b/app/Http/Controllers/AppFeedbackController.php @@ -0,0 +1,85 @@ +id())->first(); + + return response()->json($feedback); + } + + // USER: submit or update feedback (one editable row per user). + public function store(Request $request) + { + $data = $request->validate([ + 'stars' => 'nullable|integer|min:1|max:5', + 'content' => 'nullable|string|max:2000', + ]); + + if (!$request->filled('stars') && !$request->filled('content')) { + throw ValidationException::withMessages([ + 'content' => ['Either a rating or a comment is required.'], + ]); + } + + $feedback = AppFeedback::firstOrNew(['user_id' => auth()->id()]); + + if ($request->has('stars')) { + $feedback->stars = $data['stars'] ?? null; + } + if ($request->has('content')) { + $feedback->content = $data['content'] ?? null; + } + + $feedback->save(); + + return response()->json([ + 'message' => 'Feedback submitted successfully', + 'feedback' => $feedback, + ]); + } + + // ADMIN: monitor all feedback, with a summary header. + public function adminIndex(Request $request) + { + $query = AppFeedback::with('user'); + + if ($request->filled('has_comment')) { + $query->whereNotNull('content')->where('content', '!=', ''); + } + if ($request->filled('stars')) { + $query->where('stars', $request->integer('stars')); + } + + $feedback = $query->latest()->paginate($request->integer('per_page', 20)); + + $base = AppFeedback::query(); + + return response()->json([ + 'summary' => [ + 'total' => (clone $base)->count(), + 'rated' => (clone $base)->whereNotNull('stars')->count(), + 'with_comment' => (clone $base)->whereNotNull('content')->where('content', '!=', '')->count(), + 'average_stars' => round((float) (clone $base)->whereNotNull('stars')->avg('stars'), 2), + ], + 'feedback' => $feedback, + ]); + } + + // ADMIN: remove a feedback entry (moderation). + public function adminDestroy($id) + { + $feedback = AppFeedback::findOrFail($id); + $feedback->delete(); + + return response()->json(['message' => 'Feedback deleted successfully']); + } +} diff --git a/app/Models/AppFeedback.php b/app/Models/AppFeedback.php new file mode 100644 index 0000000..5f5cf15 --- /dev/null +++ b/app/Models/AppFeedback.php @@ -0,0 +1,22 @@ + 'integer', + ]; + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } +} diff --git a/database/migrations/2026_06_06_100000_create_app_feedback_table.php b/database/migrations/2026_06_06_100000_create_app_feedback_table.php new file mode 100644 index 0000000..504a5da --- /dev/null +++ b/database/migrations/2026_06_06_100000_create_app_feedback_table.php @@ -0,0 +1,33 @@ +id(); + $table->foreignId('user_id')->constrained('users')->cascadeOnDelete(); + $table->unsignedTinyInteger('stars')->nullable(); // 1-5 overall rating + $table->text('content')->nullable(); // idea / comment + $table->timestamps(); + + $table->unique('user_id'); // one feedback row per user (edited in place) + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('app_feedback'); + } +}; diff --git a/routes/api.php b/routes/api.php index d4c0078..9f45b46 100644 --- a/routes/api.php +++ b/routes/api.php @@ -13,6 +13,7 @@ use App\Http\Controllers\QuestionController; use App\Http\Controllers\SurveyQuestionController; use App\Http\Controllers\ChatTopicController; +use App\Http\Controllers\AppFeedbackController; use App\Http\Controllers\SliderController; use App\Http\Controllers\SceneController; use App\Http\Controllers\BellSoundController; @@ -129,6 +130,15 @@ Route::apiResource('chat-topics', ChatTopicController::class); + /// app feedback — ideas & reviews (نظرات و ایده‌ها) + Route::get('/app-feedback', [AppFeedbackController::class, 'mine']); + Route::post('/app-feedback', [AppFeedbackController::class, 'store']); + Route::middleware('abilities:admin')->group(function () { + Route::get('/admin/app-feedback', [AppFeedbackController::class, 'adminIndex']); + Route::delete('/admin/app-feedback/{id}', [AppFeedbackController::class, 'adminDestroy']); + }); + + /// survey questions feature (question + description + single/multi options, answered by users) // Admin: see all users' answers (must be registered before the resource so it isn't caught by {survey_question}). Route::middleware('abilities:admin')->group(function () {