HoshiAI-be/app/Http/Controllers/UserTestController.php
2025-11-12 23:04:55 +01:00

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'];
}
}