Compare commits
2 Commits
7680c8edc2
...
b2f449b7f0
Author | SHA1 | Date |
---|---|---|
|
b2f449b7f0 | 6 days ago |
|
ae46a4ccec | 6 days ago |
13 changed files with 401 additions and 9437 deletions
@ -0,0 +1,60 @@ |
||||
<?php |
||||
|
||||
namespace App\Http\Controllers\Api; |
||||
|
||||
use App\Http\Controllers\Controller; |
||||
use App\Http\Requests\CreateExerciseRequest; |
||||
use App\Services\ExerciseService; |
||||
use App\Services\ExerciseSkillService; |
||||
use App\Services\QuestionGroupService; |
||||
use App\Services\QuestionService; |
||||
use App\Services\SkillService; |
||||
use Illuminate\Support\Facades\DB; |
||||
|
||||
class ExerciseController extends Controller |
||||
{ |
||||
public function __construct( |
||||
ExerciseService $exerciseService, |
||||
ExerciseSkillService $exerciseSkillService, |
||||
SkillService $skillService, |
||||
QuestionService $questionService, |
||||
) |
||||
{ |
||||
$this->exerciseService = $exerciseService; |
||||
$this->skillService = $skillService; |
||||
$this->exerciseSkillService = $exerciseSkillService; |
||||
$this->questionService = $questionService; |
||||
|
||||
} |
||||
|
||||
public function create(CreateExerciseRequest $request) |
||||
{ |
||||
DB::beginTransaction(); |
||||
try { |
||||
$data = $request->all(); |
||||
$groups = json_decode($data['groups']); |
||||
$dataExercises = $request->except('groups','skill'); |
||||
$dataSkills = $data['skill']; |
||||
|
||||
$exerciseId = $this->exerciseService->createExercise($dataExercises); |
||||
$skillIds = $this->skillService->getSkillIds($dataSkills); |
||||
$this->exerciseSkillService->handleSkillsForExercise($skillIds, $exerciseId); |
||||
$this->questionService->createQuestion($groups, $exerciseId); |
||||
|
||||
DB::commit(); |
||||
|
||||
return response()->json([ |
||||
'success' => true, |
||||
'message' => 'Exercise created successfully', |
||||
'exercise_id' => $exerciseId |
||||
], 201); |
||||
} catch (\Exception $e) { |
||||
DB::rollBack(); |
||||
return response()->json([ |
||||
'success' => false, |
||||
'message' => 'Failed to create exercise', |
||||
'error' => $e->getMessage() |
||||
], 500); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,58 @@ |
||||
<?php |
||||
|
||||
namespace App\Http\Requests; |
||||
|
||||
use Illuminate\Foundation\Http\FormRequest; |
||||
use Illuminate\Contracts\Validation\Validator; |
||||
use Illuminate\Http\Exceptions\HttpResponseException; |
||||
|
||||
class CreateExerciseRequest extends FormRequest |
||||
{ |
||||
/** |
||||
* Determine if the user is authorized to make this request. |
||||
*/ |
||||
public function authorize(): bool |
||||
{ |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Get the validation rules that apply to the request. |
||||
* |
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string> |
||||
*/ |
||||
public function rules(): array |
||||
{ |
||||
return [ |
||||
'name' => 'required|string', |
||||
'grade_id' => 'required|integer', |
||||
'subject_id' => 'required|integer', |
||||
'category_id' => 'required|integer', |
||||
'skill' => 'required|array', |
||||
'level' => 'required|integer', |
||||
'groups' => 'required' |
||||
]; |
||||
} |
||||
|
||||
public function messages(): array |
||||
{ |
||||
return [ |
||||
'name.required' => 'Vui lòng nhập tên bài.', |
||||
'grade_id.required' => 'Vui lòng nhập khối lớp.', |
||||
'subject_id.required' => 'Vui lòng nhập môn học.', |
||||
'category_id.required' => 'Vui lòng nhập danh mục', |
||||
'skill.required' => 'Vui lòng nhập tên kĩ năng.', |
||||
'level.required' => 'Vui lòng nhập trình độ.', |
||||
'groups.required' => 'Vui lòng nhập danh sách các nhóm câu hỏi.' |
||||
]; |
||||
} |
||||
|
||||
protected function failedValidation(Validator $validator) |
||||
{ |
||||
throw new HttpResponseException(response()->json([ |
||||
'success' => false, |
||||
'message' => 'Dữ liệu không hợp lệ', |
||||
'errors' => $validator->errors() |
||||
], 422)); |
||||
} |
||||
} |
@ -0,0 +1,18 @@ |
||||
<?php |
||||
|
||||
namespace App\Models; |
||||
|
||||
use Illuminate\Database\Eloquent\Model; |
||||
use Illuminate\Database\Eloquent\SoftDeletes; |
||||
|
||||
class ExerciseSkill extends Model |
||||
{ |
||||
use SoftDeletes; |
||||
|
||||
protected $table = 'se_exercise_skills'; |
||||
|
||||
protected $fillable = [ |
||||
'exercise_id', |
||||
'skill_id' |
||||
]; |
||||
} |
@ -0,0 +1,25 @@ |
||||
<?php |
||||
|
||||
namespace App\Services; |
||||
|
||||
use App\Models\Exercise; |
||||
|
||||
class ExerciseService |
||||
{ |
||||
public function createExercise($dataExercise) |
||||
{ |
||||
$dataCreateExercise = [ |
||||
'lesson_name' => $dataExercise['name'] ?? null, |
||||
'description' => $dataExercise['description'] ?? null, |
||||
'subject_id' => $dataExercise['subject_id'] ?? null, |
||||
'level' => $dataExercise['level'] ?? null, |
||||
'status' => $dataExercise['status'] ?? null, |
||||
'category_id' => $dataExercise['category_id'] ?? null, |
||||
'year' => $dataExercise['year'] ?? null, |
||||
]; |
||||
|
||||
$exercise = Exercise::create($dataCreateExercise); |
||||
|
||||
return $exercise->id; |
||||
} |
||||
} |
@ -0,0 +1,30 @@ |
||||
<?php |
||||
|
||||
namespace App\Services; |
||||
|
||||
use App\Models\ExerciseSkill; |
||||
|
||||
class ExerciseSkillService |
||||
{ |
||||
/** |
||||
* Xử lý lưu các kỹ năng (skills) cho một bài tập (exercise) |
||||
* |
||||
* @param array $skills |
||||
* @param int $exerciseId |
||||
* @return void |
||||
*/ |
||||
public function handleSkillsForExercise($skillIds, $exerciseId) |
||||
{ |
||||
$data = []; |
||||
foreach ($skillIds as $skillId) { |
||||
$data[] = [ |
||||
'exercise_id' => $exerciseId, |
||||
'skill_id' => $skillId, |
||||
]; |
||||
} |
||||
|
||||
if (!empty($data)) { |
||||
ExerciseSkill::insert($data); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,8 @@ |
||||
<?php |
||||
|
||||
namespace App\Services; |
||||
|
||||
class QuestionGroupService |
||||
{ |
||||
|
||||
} |
@ -0,0 +1,142 @@ |
||||
<?php |
||||
|
||||
namespace App\Services; |
||||
|
||||
use App\Models\Question; |
||||
use App\Models\QuestionBlank; |
||||
use App\Models\QuestionChoice; |
||||
use App\Models\QuestionGroup; |
||||
use Illuminate\Support\Facades\DB; |
||||
|
||||
class QuestionService |
||||
{ |
||||
|
||||
public function __construct( |
||||
QuestionGroupService $questionGroupService |
||||
) |
||||
{ |
||||
$this->questionGroupService = $questionGroupService; |
||||
} |
||||
|
||||
public function createQuestion($data, $exerciseId) |
||||
{ |
||||
$now = now(); |
||||
$groupData = []; |
||||
$dataQuestion = []; |
||||
$dataQuestionChoices = []; |
||||
$dataQuestionBlank = []; |
||||
$questionMap = []; |
||||
|
||||
DB::beginTransaction(); |
||||
try { |
||||
// 1. Chuẩn bị groupData |
||||
foreach ($data as $groupKey => $value) { |
||||
$groupData[] = [ |
||||
'content' => $value->title, |
||||
'exercise_id' => $exerciseId, |
||||
'is_question_order_fixed' => $value->is_question_order_fixed, |
||||
'is_option_order_fixed' => $value->is_option_order_fixed, |
||||
'position' => $groupKey + 1, |
||||
'paragraph' => $value->paragraph, |
||||
'created_at' => $now, |
||||
'updated_at' => $now, |
||||
]; |
||||
} |
||||
|
||||
// 2. Insert group & lấy lại id |
||||
QuestionGroup::insert($groupData); |
||||
$groupIds = QuestionGroup::where('exercise_id', $exerciseId) |
||||
->orderByDesc('id') |
||||
->take(count($groupData)) |
||||
->get() |
||||
->reverse() |
||||
->pluck('id') |
||||
->values() |
||||
->toArray(); |
||||
|
||||
// 3. Chuẩn bị question |
||||
$questionIndex = 0; |
||||
foreach ($data as $groupKey => $value) { |
||||
$groupId = $groupIds[$groupKey]; |
||||
foreach ($value->questions as $qIndex => $question) { |
||||
$dataQuestion[] = [ |
||||
'exercise_id' => $exerciseId, |
||||
'content' => $question->content, |
||||
'description' => $question->description ?? null, |
||||
'group_id' => $groupId, |
||||
'question_type_id' => 1, |
||||
'level' => 2, |
||||
'score' => $question->score ?? null, |
||||
'explanation' => $question->explanation ?? null, |
||||
'hint' => $question->hint ?? null, |
||||
'created_at' => $now, |
||||
'updated_at' => $now, |
||||
'custom_key' => "{$groupKey}_{$qIndex}", // dùng để map lại |
||||
]; |
||||
$questionMap["{$groupKey}_{$qIndex}"] = [ |
||||
'type' => $question->type, |
||||
'options' => $question->options ?? [], |
||||
'answers' => $question->answers ?? [], |
||||
]; |
||||
$questionIndex++; |
||||
} |
||||
} |
||||
|
||||
// 4. Insert question & lấy lại theo custom_key |
||||
Question::insert($dataQuestion); |
||||
|
||||
$questions = Question::where('exercise_id', $exerciseId)->get(); |
||||
// $questionsByKey = $questions->keyBy(function ($item) { |
||||
// return "{$item->group_id}_{$item->position}"; // hoặc dùng custom_key nếu có |
||||
// }); |
||||
|
||||
// 5. Mapping lại options/answers |
||||
$qIndex = 0; |
||||
foreach ($questionMap as $key => $meta) { |
||||
$questionId = $questions[$qIndex]->id ?? null; |
||||
if (!$questionId) continue; |
||||
|
||||
if ($meta['type'] == 'multiple_choice') { |
||||
foreach ($meta['options'] as $optKey => $option) { |
||||
$dataQuestionChoices[] = [ |
||||
'question_id' => $questionId, |
||||
'label' => $option->label, |
||||
'content' => $option->content, |
||||
'is_correct' => $option->is_correct, |
||||
'position' => $optKey + 1, |
||||
'created_at' => $now, |
||||
'updated_at' => $now, |
||||
]; |
||||
} |
||||
} else { |
||||
foreach ($meta['answers'] as $ansKey => $answer) { |
||||
$dataQuestionBlank[] = [ |
||||
'question_id' => $questionId, |
||||
'correct_answer' => $answer->answer_true, |
||||
'other_answers' => json_encode($answer->other_answers), |
||||
'position' => $ansKey + 1, |
||||
'created_at' => $now, |
||||
'updated_at' => $now, |
||||
]; |
||||
} |
||||
} |
||||
|
||||
$qIndex++; |
||||
} |
||||
|
||||
if (!empty($dataQuestionChoices)) { |
||||
QuestionChoice::insert($dataQuestionChoices); |
||||
} |
||||
|
||||
if (!empty($dataQuestionBlank)) { |
||||
QuestionBlank::insert($dataQuestionBlank); |
||||
} |
||||
|
||||
DB::commit(); |
||||
} catch (\Throwable $e) { |
||||
DB::rollBack(); |
||||
throw $e; |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,21 @@ |
||||
<?php |
||||
|
||||
namespace App\Services; |
||||
|
||||
use App\Models\Skill; |
||||
use Illuminate\Support\Str; |
||||
|
||||
class SkillService |
||||
{ |
||||
public function getSkillIds($skills) |
||||
{ |
||||
$skillIds = []; |
||||
foreach ($skills as $value) { |
||||
$skill = Skill::where('code', $value)->first(); |
||||
if ($skill) { |
||||
$skillIds[] = $skill->id; |
||||
} |
||||
} |
||||
return $skillIds; |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,30 @@ |
||||
<?php |
||||
|
||||
use Illuminate\Database\Migrations\Migration; |
||||
use Illuminate\Database\Schema\Blueprint; |
||||
use Illuminate\Support\Facades\Schema; |
||||
|
||||
return new class extends Migration |
||||
{ |
||||
/** |
||||
* Run the migrations. |
||||
*/ |
||||
public function up(): void |
||||
{ |
||||
Schema::create('se_exercise_skills', function (Blueprint $table) { |
||||
$table->id(); |
||||
$table->unsignedBigInteger('exercise_id'); |
||||
$table->unsignedBigInteger('skill_id'); |
||||
$table->timestamps(); |
||||
$table->softDeletes(); |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Reverse the migrations. |
||||
*/ |
||||
public function down(): void |
||||
{ |
||||
Schema::dropIfExists('se_exercise_skills'); |
||||
} |
||||
}; |
Loading…
Reference in new issue