feat
This commit is contained in:
@@ -193,48 +193,21 @@ public function destroy($id)
|
|||||||
return response()->json(['message' => 'Question deleted successfully']);
|
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)
|
public function answer(Request $request, $id)
|
||||||
{
|
{
|
||||||
$question = SurveyQuestion::findOrFail($id);
|
$question = SurveyQuestion::findOrFail($id);
|
||||||
|
|
||||||
$data = $request->validate([
|
$data = $request->validate([
|
||||||
'option_ids' => 'required|array|min:1',
|
'option_ids' => 'nullable|array',
|
||||||
'option_ids.*' => 'integer',
|
'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();
|
$userId = auth()->id();
|
||||||
|
|
||||||
DB::transaction(function () use ($question, $optionIds, $userId) {
|
DB::transaction(function () use ($question, $data, $userId) {
|
||||||
// Replace any previous answer for this user + question.
|
$this->syncAnswerFor($question, $data['option_ids'] ?? [], $userId);
|
||||||
$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);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return response()->json([
|
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.
|
// USER: suggest media based on the tags attached to the options this user has chosen.
|
||||||
public function suggestedMedia(Request $request)
|
public function suggestedMedia(Request $request)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -150,6 +150,7 @@
|
|||||||
});
|
});
|
||||||
// User: answer + read questions with their own answers only.
|
// User: answer + read questions with their own answers only.
|
||||||
Route::get('/survey-questions/suggested-media', [SurveyQuestionController::class, 'suggestedMedia']);
|
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::post('/survey-questions/{id}/answer', [SurveyQuestionController::class, 'answer']);
|
||||||
Route::apiResource('survey-questions', SurveyQuestionController::class);
|
Route::apiResource('survey-questions', SurveyQuestionController::class);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user