feat: add feedback api
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\AppFeedback;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class AppFeedbackController extends Controller
|
||||
{
|
||||
// USER: get the current user's feedback (null if none yet).
|
||||
public function mine()
|
||||
{
|
||||
$feedback = AppFeedback::where('user_id', auth()->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']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class AppFeedback extends Model
|
||||
{
|
||||
protected $table = 'app_feedback';
|
||||
|
||||
protected $fillable = ['user_id', 'stars', 'content'];
|
||||
|
||||
protected $casts = [
|
||||
'stars' => 'integer',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* App-level feedback (نظرات و ایدهها): one editable entry per user holding an
|
||||
* overall star rating and/or an idea/comment about the application.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('app_feedback', function (Blueprint $table) {
|
||||
$table->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');
|
||||
}
|
||||
};
|
||||
@@ -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 () {
|
||||
|
||||
Reference in New Issue
Block a user