diff --git a/app/Http/Controllers/FaqCategoryController.php b/app/Http/Controllers/FaqCategoryController.php new file mode 100644 index 0000000..11f2e6f --- /dev/null +++ b/app/Http/Controllers/FaqCategoryController.php @@ -0,0 +1,60 @@ +withCount('faqs'); + + if (!$request->boolean('include_inactive')) { + $query->where('is_active', true); + } + + return response()->json($query->orderBy('order')->get()); + } + + public function show($id) + { + return response()->json(FaqCategory::with('faqs')->withCount('faqs')->findOrFail($id)); + } + + public function store(Request $request) + { + $data = $request->validate([ + 'name' => 'required|string|max:255', + 'order' => 'nullable|integer', + 'is_active' => 'nullable|boolean', + ]); + + $category = FaqCategory::create($data); + + return response()->json(['message' => 'FAQ category created', 'category' => $category], 201); + } + + public function update(Request $request, $id) + { + $category = FaqCategory::findOrFail($id); + + $data = $request->validate([ + 'name' => 'sometimes|string|max:255', + 'order' => 'nullable|integer', + 'is_active' => 'nullable|boolean', + ]); + + $category->update($data); + + return response()->json(['message' => 'FAQ category updated', 'category' => $category]); + } + + public function destroy($id) + { + FaqCategory::findOrFail($id)->delete(); // faqs cascade + + return response()->json(['message' => 'FAQ category deleted']); + } +} diff --git a/app/Http/Controllers/FaqController.php b/app/Http/Controllers/FaqController.php new file mode 100644 index 0000000..4d5ebce --- /dev/null +++ b/app/Http/Controllers/FaqController.php @@ -0,0 +1,74 @@ +boolean('include_inactive'); + + $categories = FaqCategory::query() + ->with(['faqs' => function ($q) use ($includeInactive) { + if (!$includeInactive) { + $q->where('is_active', true); + } + $q->orderBy('order'); + }]) + ->when(!$includeInactive, fn ($q) => $q->where('is_active', true)) + ->orderBy('order') + ->get(); + + return response()->json($categories); + } + + public function show($id) + { + return response()->json(Faq::with('category')->findOrFail($id)); + } + + public function store(Request $request) + { + $data = $request->validate([ + 'faq_category_id' => 'required|exists:faq_categories,id', + 'question' => 'required|string|max:500', + 'answer' => 'required|string', + 'order' => 'nullable|integer', + 'is_active' => 'nullable|boolean', + ]); + + $faq = Faq::create($data); + + return response()->json(['message' => 'FAQ created', 'faq' => $faq->load('category')], 201); + } + + public function update(Request $request, $id) + { + $faq = Faq::findOrFail($id); + + $data = $request->validate([ + 'faq_category_id' => 'sometimes|exists:faq_categories,id', + 'question' => 'sometimes|string|max:500', + 'answer' => 'sometimes|string', + 'order' => 'nullable|integer', + 'is_active' => 'nullable|boolean', + ]); + + $faq->update($data); + + return response()->json(['message' => 'FAQ updated', 'faq' => $faq->load('category')]); + } + + public function destroy($id) + { + Faq::findOrFail($id)->delete(); + + return response()->json(['message' => 'FAQ deleted']); + } +} diff --git a/app/Models/Faq.php b/app/Models/Faq.php new file mode 100644 index 0000000..8218764 --- /dev/null +++ b/app/Models/Faq.php @@ -0,0 +1,21 @@ + 'boolean', + 'order' => 'integer', + ]; + + public function category(): BelongsTo + { + return $this->belongsTo(FaqCategory::class, 'faq_category_id'); + } +} diff --git a/app/Models/FaqCategory.php b/app/Models/FaqCategory.php new file mode 100644 index 0000000..ee36939 --- /dev/null +++ b/app/Models/FaqCategory.php @@ -0,0 +1,21 @@ + 'boolean', + 'order' => 'integer', + ]; + + public function faqs(): HasMany + { + return $this->hasMany(Faq::class)->orderBy('order'); + } +} diff --git a/database/migrations/2026_06_15_160000_create_faq_categories_table.php b/database/migrations/2026_06_15_160000_create_faq_categories_table.php new file mode 100644 index 0000000..a5a8ccb --- /dev/null +++ b/database/migrations/2026_06_15_160000_create_faq_categories_table.php @@ -0,0 +1,24 @@ +id(); + $table->string('name'); + $table->integer('order')->default(0); + $table->boolean('is_active')->default(true); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('faq_categories'); + } +}; diff --git a/database/migrations/2026_06_15_160100_create_faqs_table.php b/database/migrations/2026_06_15_160100_create_faqs_table.php new file mode 100644 index 0000000..03b87d8 --- /dev/null +++ b/database/migrations/2026_06_15_160100_create_faqs_table.php @@ -0,0 +1,26 @@ +id(); + $table->foreignId('faq_category_id')->constrained('faq_categories')->cascadeOnDelete(); + $table->string('question'); + $table->text('answer'); + $table->integer('order')->default(0); + $table->boolean('is_active')->default(true); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('faqs'); + } +}; diff --git a/routes/api.php b/routes/api.php index 7ed365d..31c7016 100644 --- a/routes/api.php +++ b/routes/api.php @@ -15,6 +15,8 @@ use App\Http\Controllers\SurveyQuestionController; use App\Http\Controllers\ChatTopicController; use App\Http\Controllers\AppFeedbackController; +use App\Http\Controllers\FaqController; +use App\Http\Controllers\FaqCategoryController; use App\Http\Controllers\SliderController; use App\Http\Controllers\SceneController; use App\Http\Controllers\ThemeController; @@ -136,6 +138,15 @@ Route::apiResource('chat-topics', ChatTopicController::class); + /// FAQ (سوالات متداول) — app reads grouped /faqs; admin manages categories & items + Route::get('/faqs', [FaqController::class, 'index']); // app: categories + their questions + Route::apiResource('faq-categories', FaqCategoryController::class); + Route::post('/faqs', [FaqController::class, 'store']); + Route::get('/faqs/{id}', [FaqController::class, 'show']); + Route::put('/faqs/{id}', [FaqController::class, 'update']); + Route::delete('/faqs/{id}', [FaqController::class, 'destroy']); + + /// app feedback — ideas & reviews (نظرات و ایده‌ها) Route::get('/app-feedback', [AppFeedbackController::class, 'mine']); Route::post('/app-feedback', [AppFeedbackController::class, 'store']);