This commit is contained in:
2026-06-10 12:14:31 +03:30
parent c0ff35c0a8
commit a1a858abbf
2 changed files with 82 additions and 32 deletions
@@ -193,48 +193,21 @@ public function destroy($id)
return response()->json(['message' => 'Question deleted successfully']);
}
// USER submits their answer(s) for a question.
// USER submits their answer(s) for a single question.
// An empty option_ids array (or omitting it) means "no answer" / skip.
public function answer(Request $request, $id)
{
$question = SurveyQuestion::findOrFail($id);
$data = $request->validate([
'option_ids' => 'required|array|min:1',
'option_ids' => 'nullable|array',
'option_ids.*' => 'integer',
]);
$optionIds = array_values(array_unique($data['option_ids']));
// Every selected option must belong to this question.
$validOptionIds = $question->options()->pluck('id')->all();
if (array_diff($optionIds, $validOptionIds)) {
throw ValidationException::withMessages([
'option_ids' => ['One or more options do not belong to this question.'],
]);
}
// Enforce single vs multiple selection.
if ($question->type === 'single' && count($optionIds) > 1) {
throw ValidationException::withMessages([
'option_ids' => ['This question allows only a single option.'],
]);
}
$userId = auth()->id();
DB::transaction(function () use ($question, $optionIds, $userId) {
// Replace any previous answer for this user + question.
$question->answers()->where('user_id', $userId)->delete();
$rows = array_map(fn ($optionId) => [
'user_id' => $userId,
'survey_question_id' => $question->id,
'survey_option_id' => $optionId,
'created_at' => now(),
'updated_at' => now(),
], $optionIds);
$question->answers()->getRelated()->insert($rows);
DB::transaction(function () use ($question, $data, $userId) {
$this->syncAnswerFor($question, $data['option_ids'] ?? [], $userId);
});
return response()->json([
@@ -243,6 +216,82 @@ public function answer(Request $request, $id)
]);
}
// USER submits answers for many questions at once. Each item may carry an
// empty option_ids (skip), and the whole answers array may be empty too.
public function bulkAnswer(Request $request)
{
$data = $request->validate([
'answers' => 'present|array',
'answers.*.question_id' => 'required|integer|exists:survey_questions,id',
'answers.*.option_ids' => 'nullable|array',
'answers.*.option_ids.*' => 'integer',
]);
$userId = auth()->id();
$items = $data['answers'] ?? [];
$questions = SurveyQuestion::with('options')
->whereIn('id', collect($items)->pluck('question_id')->unique()->all())
->get()
->keyBy('id');
DB::transaction(function () use ($items, $questions, $userId) {
foreach ($items as $item) {
$question = $questions->get($item['question_id']);
if (!$question) {
continue;
}
$this->syncAnswerFor($question, $item['option_ids'] ?? [], $userId);
}
});
return response()->json([
'message' => 'Answers submitted successfully',
'questions' => SurveyQuestion::with(['options', 'userAnswers'])
->where('is_active', true)
->orderBy('order')
->get(),
]);
}
// Validate and replace a single user's answer set for one question.
// Empty $optionIds clears the answer (the user chose not to answer it).
private function syncAnswerFor(SurveyQuestion $question, array $optionIds, int $userId): void
{
$optionIds = array_values(array_unique(array_map('intval', $optionIds)));
// Every selected option must belong to this question.
$validOptionIds = $question->options()->pluck('id')->all();
if (array_diff($optionIds, $validOptionIds)) {
throw ValidationException::withMessages([
'option_ids' => ["One or more options do not belong to question #{$question->id}."],
]);
}
// Enforce single vs multiple selection.
if ($question->type === 'single' && count($optionIds) > 1) {
throw ValidationException::withMessages([
'option_ids' => ["Question #{$question->id} allows only a single option."],
]);
}
// Replace any previous answer for this user + question.
$question->answers()->where('user_id', $userId)->delete();
if ($optionIds) {
$now = now();
$rows = array_map(fn ($optionId) => [
'user_id' => $userId,
'survey_question_id' => $question->id,
'survey_option_id' => $optionId,
'created_at' => $now,
'updated_at' => $now,
], $optionIds);
$question->answers()->getRelated()->insert($rows);
}
}
// USER: suggest media based on the tags attached to the options this user has chosen.
public function suggestedMedia(Request $request)
{
+1
View File
@@ -150,6 +150,7 @@
});
// User: answer + read questions with their own answers only.
Route::get('/survey-questions/suggested-media', [SurveyQuestionController::class, 'suggestedMedia']);
Route::post('/survey-questions/answers', [SurveyQuestionController::class, 'bulkAnswer']);
Route::post('/survey-questions/{id}/answer', [SurveyQuestionController::class, 'answer']);
Route::apiResource('survey-questions', SurveyQuestionController::class);