From 2e9a278a53b0979321bb0f2fb20bb891f557f808 Mon Sep 17 00:00:00 2001 From: Amirmahdi Nourkazemi Date: Mon, 8 Sep 2025 17:11:52 +0330 Subject: [PATCH] feat: add quetions feature --- app/Http/Controllers/QuestionController.php | 134 ++++++++++++++++++ app/Models/Category.php | 16 +++ app/Models/Question.php | 21 +++ app/Models/Tag.php | 16 +++ ...5_09_08_101008_create_categories_table.php | 29 ++++ .../2025_09_08_101008_create_tags_table.php | 29 ++++ ...25_09_08_101009_create_questions_table.php | 29 ++++ ...09_08_101629_create_question_tag_table.php | 30 ++++ database/seeders/QuestionSeeder.php | 70 +++++++++ routes/api.php | 10 ++ 10 files changed, 384 insertions(+) create mode 100644 app/Http/Controllers/QuestionController.php create mode 100644 app/Models/Category.php create mode 100644 app/Models/Question.php create mode 100644 app/Models/Tag.php create mode 100644 database/migrations/2025_09_08_101008_create_categories_table.php create mode 100644 database/migrations/2025_09_08_101008_create_tags_table.php create mode 100644 database/migrations/2025_09_08_101009_create_questions_table.php create mode 100644 database/migrations/2025_09_08_101629_create_question_tag_table.php create mode 100644 database/seeders/QuestionSeeder.php diff --git a/app/Http/Controllers/QuestionController.php b/app/Http/Controllers/QuestionController.php new file mode 100644 index 0000000..88acc2b --- /dev/null +++ b/app/Http/Controllers/QuestionController.php @@ -0,0 +1,134 @@ +has('tag')) { + $tag = $normalize($request->tag); + $query->whereHas('tags', function ($q) use ($tag) { + $q->whereRaw("REPLACE(name, '‌', ' ') LIKE ?", ["%{$tag}%"]); + }); + } + + if ($request->has('category')) { + $category = $normalize($request->category); + $query->whereHas('category', function ($q) use ($category) { + $q->whereRaw("REPLACE(name, '‌', ' ') LIKE ?", ["%{$category}%"]); + }); + } + + return response()->json($query->get()); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) + { + $data = $request->validate([ + 'title' => 'required|string', + 'category' => 'nullable|string', + 'tags' => 'nullable|array', + 'tags.*' => 'string' + ]); + + $category = null; + if (!empty($data['category'])) { + $category = Category::firstOrCreate(['name' => $data['category']]); + } + + $question = Question::create([ + 'title' => $data['title'], + 'category_id' => $category?->id + ]); + + if (!empty($data['tags'])) { + $tagIds = []; + foreach ($data['tags'] as $tagName) { + $tag = Tag::firstOrCreate(['name' => $tagName]); + $tagIds[] = $tag->id; + } + $question->tags()->sync($tagIds); + } + + return response()->json($question->load(['tags', 'category']), 201); + } + + /** + * Display the specified resource. + */ + public function show(Question $question) + { + return response()->json($question->load(['tags', 'category'])); + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, Question $question) + { + $data = $request->validate([ + 'title' => 'sometimes|string', + 'category' => 'nullable|string', + 'tags' => 'nullable|array', + 'tags.*' => 'string' + ]); + + if (isset($data['title'])) { + $question->update(['title' => $data['title']]); + } + + if (array_key_exists('category', $data)) { + if ($data['category']) { + $category = Category::firstOrCreate(['name' => $data['category']]); + $question->update(['category_id' => $category->id]); + } else { + $question->update(['category_id' => null]); + } + } + + if (isset($data['tags'])) { + $tagIds = []; + foreach ($data['tags'] as $tagName) { + $tag = Tag::firstOrCreate(['name' => $tagName]); + $tagIds[] = $tag->id; + } + $question->tags()->sync($tagIds); + } + + return response()->json($question->load(['tags', 'category'])); + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Question $question) + { + $question->delete(); + return response()->json(['message' => 'سوال حذف شد']); + } +} diff --git a/app/Models/Category.php b/app/Models/Category.php new file mode 100644 index 0000000..40735af --- /dev/null +++ b/app/Models/Category.php @@ -0,0 +1,16 @@ +hasMany(Question::class); + } +} + diff --git a/app/Models/Question.php b/app/Models/Question.php new file mode 100644 index 0000000..758de07 --- /dev/null +++ b/app/Models/Question.php @@ -0,0 +1,21 @@ +belongsToMany(Tag::class); + } + + public function category() + { + return $this->belongsTo(Category::class); + } +} + diff --git a/app/Models/Tag.php b/app/Models/Tag.php new file mode 100644 index 0000000..288db31 --- /dev/null +++ b/app/Models/Tag.php @@ -0,0 +1,16 @@ +belongsToMany(Question::class); + } +} + diff --git a/database/migrations/2025_09_08_101008_create_categories_table.php b/database/migrations/2025_09_08_101008_create_categories_table.php new file mode 100644 index 0000000..12fa604 --- /dev/null +++ b/database/migrations/2025_09_08_101008_create_categories_table.php @@ -0,0 +1,29 @@ +id(); + $table->string('name')->unique(); + $table->timestamps(); + }); +} + + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('categories'); + } +}; diff --git a/database/migrations/2025_09_08_101008_create_tags_table.php b/database/migrations/2025_09_08_101008_create_tags_table.php new file mode 100644 index 0000000..5a3229c --- /dev/null +++ b/database/migrations/2025_09_08_101008_create_tags_table.php @@ -0,0 +1,29 @@ +id(); + $table->string('name')->unique(); + $table->timestamps(); + }); + } + + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('tags'); + } +}; diff --git a/database/migrations/2025_09_08_101009_create_questions_table.php b/database/migrations/2025_09_08_101009_create_questions_table.php new file mode 100644 index 0000000..6960d02 --- /dev/null +++ b/database/migrations/2025_09_08_101009_create_questions_table.php @@ -0,0 +1,29 @@ +id(); + $table->string('title'); + $table->foreignId('category_id')->nullable()->constrained()->nullOnDelete(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('questions'); + } +}; diff --git a/database/migrations/2025_09_08_101629_create_question_tag_table.php b/database/migrations/2025_09_08_101629_create_question_tag_table.php new file mode 100644 index 0000000..166af21 --- /dev/null +++ b/database/migrations/2025_09_08_101629_create_question_tag_table.php @@ -0,0 +1,30 @@ +id(); + $table->foreignId('question_id')->constrained()->cascadeOnDelete(); + $table->foreignId('tag_id')->constrained()->cascadeOnDelete(); + $table->timestamps(); + }); +} + + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('question_tag'); + } +}; diff --git a/database/seeders/QuestionSeeder.php b/database/seeders/QuestionSeeder.php new file mode 100644 index 0000000..50bde3a --- /dev/null +++ b/database/seeders/QuestionSeeder.php @@ -0,0 +1,70 @@ + $categoryName]); + $question = Question::create([ + 'title' => $title, + 'category_id' => $category->id + ]); + + foreach ($tagNames as $tagName) { + $tag = Tag::firstOrCreate(['name' => $tagName]); + $question->tags()->attach($tag); + } + } + } +} diff --git a/routes/api.php b/routes/api.php index 19908ce..00ed769 100644 --- a/routes/api.php +++ b/routes/api.php @@ -10,6 +10,7 @@ use App\Http\Controllers\MoodController; use App\Http\Controllers\WorryController; use Illuminate\Support\Facades\Hash; +use App\Http\Controllers\QuestionController; Route::get('/test-hash', function() { $plain = 'amnk1380'; @@ -87,4 +88,13 @@ Route::delete('/{id}', 'destroy'); // Delete worry }); + + /// questions feature + + Route::get('/questions', [QuestionController::class, 'index']); + Route::post('/questions', [QuestionController::class, 'store']); + Route::get('/questions/{question}', [QuestionController::class, 'show']); + Route::put('/questions/{question}', [QuestionController::class, 'update']); + Route::delete('/questions/{question}', [QuestionController::class, 'destroy']); + }); \ No newline at end of file