395 lines
13 KiB
PHP
395 lines
13 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Http\Resources\UserTestAnswerResource;
|
|
use App\Http\Resources\UserTestResource;
|
|
use App\Models\Log;
|
|
use App\Models\Question;
|
|
use App\Models\Test;
|
|
use App\Models\UserTest;
|
|
use App\Models\UserTestAnswer;
|
|
use Illuminate\Http\Request;
|
|
|
|
class UserTestController extends Controller
|
|
{
|
|
/**
|
|
* @OA\Get(
|
|
* path="/api/user-tests",
|
|
* summary="Get list of user tests for the authenticated user",
|
|
* description="Retrieve all UserTests belonging to the authenticated user, with related category, test, and answers.",
|
|
* tags={"UserTests"},
|
|
* security={{"bearerAuth":{}}},
|
|
* @OA\Response(
|
|
* response=200,
|
|
* description="List of user tests",
|
|
* @OA\JsonContent(
|
|
* type="object",
|
|
* @OA\Property(
|
|
* property="data",
|
|
* type="array",
|
|
* @OA\Items(ref="#/components/schemas/UserTestResource")
|
|
* )
|
|
* )
|
|
* )
|
|
* )
|
|
*/
|
|
public function index(Request $request)
|
|
{
|
|
$user = $request->user();
|
|
$userTests = UserTest::where('user_id', $user->id)
|
|
->orderBy('id', 'desc')
|
|
->with(['user', 'category', 'answers.question'])
|
|
->get();
|
|
|
|
return UserTestResource::collection($userTests);
|
|
}
|
|
|
|
/**
|
|
* @OA\Post(
|
|
* path="/api/user-tests",
|
|
* summary="Create a new user test",
|
|
* description="Generate a new UserTest for the authenticated user with questions from a specific category and difficulty range.",
|
|
* tags={"UserTests"},
|
|
* security={{"bearerAuth":{}}},
|
|
* @OA\RequestBody(
|
|
* required=true,
|
|
* @OA\JsonContent(
|
|
* required={"category_id"},
|
|
* @OA\Property(property="category_id", type="integer", example=3, description="Category from which to select questions"),
|
|
* @OA\Property(property="min_difficulty", type="integer", nullable=true, example=0, description="Minimum difficulty of questions"),
|
|
* @OA\Property(property="max_difficulty", type="integer", nullable=true, example=10, description="Maximum difficulty of questions")
|
|
* )
|
|
* ),
|
|
* @OA\Response(
|
|
* response=422,
|
|
* description="Validation error or no questions found"
|
|
* )
|
|
* )
|
|
*/
|
|
public function store(Request $request)
|
|
{
|
|
$this->authorize('create', UserTest::class);
|
|
|
|
$fields = $request->validate([
|
|
'category_id' => 'required|exists:categories,id',
|
|
'min_difficulty' => 'nullable|integer',
|
|
'max_difficulty' => 'nullable|integer',
|
|
]);
|
|
|
|
$minDifficulty = $fields['min_difficulty'] ?? 0;
|
|
$maxDifficulty = $fields['max_difficulty'] ?? 10;
|
|
|
|
$questions = Question::where('category_id', $fields['category_id'])
|
|
->whereBetween('difficulty', [$minDifficulty, $maxDifficulty])
|
|
->inRandomOrder()
|
|
->take(10)
|
|
->get();
|
|
|
|
if ($questions->isEmpty()) {
|
|
return response()->json(['message' => 'No questions found for this criteria'], 422);
|
|
}
|
|
|
|
$userTest = new UserTest();
|
|
$userTest->category_id = $fields['category_id'];
|
|
$userTest->user_id = $request->user()->id;
|
|
$userTest->closed_at = now()->addDay();
|
|
$userTest->save();
|
|
$userTest->generateEmptyAnswers($questions);
|
|
|
|
return new UserTestResource($userTest);
|
|
|
|
}
|
|
|
|
/**
|
|
* @OA\Post(
|
|
* path="/api/user-tests/by-test",
|
|
* summary="Create a UserTest based on an existing Test",
|
|
* description="Generates a UserTest from an existing Test, including all its questions. Only available if the Test is available.",
|
|
* tags={"UserTests"},
|
|
* security={{"bearerAuth":{}}},
|
|
* @OA\RequestBody(
|
|
* required=true,
|
|
* @OA\JsonContent(
|
|
* required={"test_id"},
|
|
* @OA\Property(property="test_id", type="integer", example=1, description="ID of the existing Test to create a UserTest from")
|
|
* )
|
|
* ),
|
|
* @OA\Response(
|
|
* response=200,
|
|
* description="UserTest created successfully",
|
|
* @OA\JsonContent(ref="#/components/schemas/UserTestResource")
|
|
* ),
|
|
* @OA\Response(
|
|
* response=409,
|
|
* description="Test is not available at this time"
|
|
* )
|
|
* )
|
|
*/
|
|
public function storeByTest(Request $request)
|
|
{
|
|
$this->authorize('create', UserTest::class);
|
|
|
|
$fields = $request->validate([
|
|
'test_id' => 'required|exists:tests,id'
|
|
]);
|
|
|
|
$test = Test::findOrFail($fields['test_id']);
|
|
|
|
if(!$test->isAvailable())
|
|
return response(['message' => 'This test isn\'t available now!'], 409);
|
|
|
|
$test->load(['questions']);
|
|
|
|
$userTest = new UserTest();
|
|
$userTest->category_id = $test->category_id;
|
|
$userTest->test_id = $test->id;
|
|
$userTest->closed_at = $test->closed_at;
|
|
$userTest->user_id = $request->user()->id;
|
|
$userTest->save();
|
|
$userTest->generateEmptyAnswers($test->questions->toArray());
|
|
|
|
return new UserTestResource($userTest);
|
|
}
|
|
|
|
/**
|
|
* @OA\Post(
|
|
* path="/api/user-test-answers/{answer}/submit",
|
|
* summary="Submit an answer for a UserTestAnswer",
|
|
* description="Records the user's answer for a specific UserTestAnswer. The answer must match the question type (string for text, integer for single/multiple choice).",
|
|
* tags={"UserTestAnswers"},
|
|
* security={{"bearerAuth":{}}},
|
|
* @OA\Parameter(
|
|
* name="answer",
|
|
* in="path",
|
|
* required=true,
|
|
* description="ID of the UserTestAnswer to submit",
|
|
* @OA\Schema(type="integer")
|
|
* ),
|
|
* @OA\RequestBody(
|
|
* required=true,
|
|
* @OA\JsonContent(
|
|
* required={"answer"},
|
|
* @OA\Property(
|
|
* property="answer",
|
|
* type="array",
|
|
* description="Array of answers. Should be string(s) if the question type is 'text' or integer(s) otherwise",
|
|
* @OA\Items(
|
|
* oneOf={
|
|
* @OA\Schema(type="string"),
|
|
* @OA\Schema(type="integer")
|
|
* }
|
|
* )
|
|
* )
|
|
* )
|
|
* ),
|
|
* @OA\Response(
|
|
* response=200,
|
|
* description="Answer recorded successfully",
|
|
* @OA\JsonContent(
|
|
* @OA\Property(property="message", type="string", example="Answer is recorded")
|
|
* )
|
|
* ),
|
|
* @OA\Response(
|
|
* response=401,
|
|
* description="Unauthorized"
|
|
* ),
|
|
* @OA\Response(
|
|
* response=422,
|
|
* description="Validation error (wrong type or empty array)"
|
|
* ),
|
|
* @OA\Response(
|
|
* response=403,
|
|
* description="Forbidden (user cannot edit this answer)"
|
|
* )
|
|
* )
|
|
*/
|
|
public function answerByTest(Request $request, UserTestAnswer $answer)
|
|
{
|
|
$answer->load(['userTest', 'question']);
|
|
$answerType = $answer->question->type == 'text' ? 'string' : 'integer';
|
|
|
|
$this->authorize('canBeEdit', $answer->userTest);
|
|
|
|
$fields = $request->validate([
|
|
'answer' => 'required|array|min:1',
|
|
'answer.*' => 'required|' . $answerType
|
|
]);
|
|
$answer->answer = $fields['answer'];
|
|
$answer->save();
|
|
|
|
return ['message' => 'Answer is recorded'];
|
|
}
|
|
|
|
/**
|
|
* @OA\Get(
|
|
* path="/api/user-test-answers/{answer}",
|
|
* summary="Get a UserTestAnswer",
|
|
* description="Retrieve a specific UserTestAnswer by ID. Only the owner or authorized user can view it.",
|
|
* tags={"UserTestAnswers"},
|
|
* security={{"bearerAuth":{}}},
|
|
* @OA\Parameter(
|
|
* name="answer",
|
|
* in="path",
|
|
* required=true,
|
|
* description="ID of the UserTestAnswer to retrieve",
|
|
* @OA\Schema(type="integer")
|
|
* ),
|
|
* @OA\Response(
|
|
* response=200,
|
|
* description="UserTestAnswer retrieved successfully",
|
|
* @OA\JsonContent(ref="#/components/schemas/UserTestAnswerResource")
|
|
* ),
|
|
* @OA\Response(
|
|
* response=401,
|
|
* description="Unauthorized"
|
|
* ),
|
|
* @OA\Response(
|
|
* response=403,
|
|
* description="Forbidden: user cannot view this answer"
|
|
* ),
|
|
* @OA\Response(
|
|
* response=404,
|
|
* description="UserTestAnswer not found"
|
|
* )
|
|
* )
|
|
*/
|
|
public function showAnswer(UserTestAnswer $answer)
|
|
{
|
|
$answer->load(['userTest', 'question']);
|
|
|
|
$this->authorize('view', $answer->userTest);
|
|
|
|
return new UserTestAnswerResource($answer);
|
|
}
|
|
|
|
/**
|
|
* @OA\Post(
|
|
* path="/api/user-tests/{userTest}/complete",
|
|
* summary="Complete a UserTest",
|
|
* description="Marks a UserTest as completed, calculates the score based on answers, and prevents further editing.",
|
|
* tags={"UserTests"},
|
|
* security={{"bearerAuth":{}}},
|
|
* @OA\Parameter(
|
|
* name="userTest",
|
|
* in="path",
|
|
* required=true,
|
|
* description="ID of the UserTest to complete",
|
|
* @OA\Schema(type="integer")
|
|
* ),
|
|
* @OA\Response(
|
|
* response=200,
|
|
* description="Test successfully completed",
|
|
* @OA\JsonContent(
|
|
* @OA\Property(property="message", type="string", example="Test is completed")
|
|
* )
|
|
* ),
|
|
* @OA\Response(
|
|
* response=403,
|
|
* description="Forbidden: user cannot edit this test"
|
|
* )
|
|
* )
|
|
*/
|
|
public function completeTest(UserTest $userTest)
|
|
{
|
|
$this->authorize('canBeEdit', $userTest);
|
|
|
|
$userTest->load(['category', 'answers.question', 'user']);
|
|
$userTest->completeTest();
|
|
|
|
return ['message' => 'Test is completed'];
|
|
}
|
|
|
|
/**
|
|
* @OA\Get(
|
|
* path="/api/user-tests/{userTest}",
|
|
* summary="Get a single UserTest",
|
|
* description="Retrieve a UserTest with its answers, question details, category, and user info.",
|
|
* tags={"UserTests"},
|
|
* security={{"bearerAuth":{}}},
|
|
* @OA\Parameter(
|
|
* name="userTest",
|
|
* in="path",
|
|
* required=true,
|
|
* description="ID of the UserTest to retrieve",
|
|
* @OA\Schema(type="integer")
|
|
* ),
|
|
* @OA\Response(
|
|
* response=200,
|
|
* description="UserTest details",
|
|
* @OA\JsonContent(ref="#/components/schemas/UserTestResource")
|
|
* ),
|
|
* @OA\Response(
|
|
* response=401,
|
|
* description="Unauthorized"
|
|
* ),
|
|
* @OA\Response(
|
|
* response=403,
|
|
* description="Forbidden: user cannot view this test"
|
|
* ),
|
|
* @OA\Response(
|
|
* response=404,
|
|
* description="UserTest not found"
|
|
* )
|
|
* )
|
|
*/
|
|
public function show(UserTest $userTest)
|
|
{
|
|
$this->authorize('view', $userTest);
|
|
$userTest->load(['user', 'category', 'answers.question']);
|
|
|
|
return new UserTestResource($userTest);
|
|
}
|
|
|
|
/**
|
|
* @OA\Delete(
|
|
* path="/api/user-tests/{userTest}",
|
|
* summary="Delete a UserTest",
|
|
* description="Delete a UserTest by ID. Only the owner or admin can delete it.",
|
|
* tags={"UserTests"},
|
|
* security={{"bearerAuth":{}}},
|
|
* @OA\Parameter(
|
|
* name="userTest",
|
|
* in="path",
|
|
* required=true,
|
|
* description="ID of the UserTest to delete",
|
|
* @OA\Schema(type="integer")
|
|
* ),
|
|
* @OA\Response(
|
|
* response=200,
|
|
* description="UserTest deleted successfully",
|
|
* @OA\JsonContent(
|
|
* type="object",
|
|
* @OA\Property(property="message", type="string", example="The UserTest was deleted")
|
|
* )
|
|
* ),
|
|
* @OA\Response(
|
|
* response=401,
|
|
* description="Unauthorized"
|
|
* ),
|
|
* @OA\Response(
|
|
* response=403,
|
|
* description="Forbidden: user cannot delete this test"
|
|
* ),
|
|
* @OA\Response(
|
|
* response=404,
|
|
* description="UserTest not found"
|
|
* )
|
|
* )
|
|
*/
|
|
public function destroy(Request $request, UserTest $userTest)
|
|
{
|
|
$this->authorize('delete', $userTest);
|
|
$userTest->load('user');
|
|
|
|
$username = $userTest->user ? $userTest->user->username : 'unknown';
|
|
$userTestId = $userTest->id;
|
|
|
|
$userTest->delete();
|
|
|
|
Log::writeLog("UserTest #$userTestId ($username)' is deleted by " . $request->user()->username);
|
|
|
|
return ['message' => 'The UserTest was deleted'];
|
|
}
|
|
}
|