Compare commits
No commits in common. 'b2f449b7f002d6224b69f74f0dd403ebe6d29c0a' and '7680c8edc2338133ea64a967df17245df972cbe2' have entirely different histories.
b2f449b7f0
...
7680c8edc2
13 changed files with 9437 additions and 401 deletions
@ -1,60 +0,0 @@ |
|||||||
<?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); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,58 +0,0 @@ |
|||||||
<?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)); |
|
||||||
} |
|
||||||
} |
|
@ -1,18 +0,0 @@ |
|||||||
<?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' |
|
||||||
]; |
|
||||||
} |
|
@ -1,25 +0,0 @@ |
|||||||
<?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; |
|
||||||
} |
|
||||||
} |
|
@ -1,30 +0,0 @@ |
|||||||
<?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); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,8 +0,0 @@ |
|||||||
<?php |
|
||||||
|
|
||||||
namespace App\Services; |
|
||||||
|
|
||||||
class QuestionGroupService |
|
||||||
{ |
|
||||||
|
|
||||||
} |
|
@ -1,142 +0,0 @@ |
|||||||
<?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; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,21 +0,0 @@ |
|||||||
<?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
@ -1,30 +0,0 @@ |
|||||||
<?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