Tests
This commit is contained in:
parent
590a346781
commit
8e2d51660a
@ -8,6 +8,10 @@ HoshiAI-be — the backend part of the HoshiAI project for the Web Programming c
|
|||||||
* [L5-Swagger](https://github.com/DarkaOnLine/L5-Swagger)
|
* [L5-Swagger](https://github.com/DarkaOnLine/L5-Swagger)
|
||||||
* [mobiledetect/mobiledetectlib](https://packagist.org/packages/mobiledetect/mobiledetectlib)
|
* [mobiledetect/mobiledetectlib](https://packagist.org/packages/mobiledetect/mobiledetectlib)
|
||||||
|
|
||||||
|
## External API
|
||||||
|
* [ip-api.com](http://ip-api.com/)
|
||||||
|
* [openai.com](https://openai.com/)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
1. Install php and composer
|
1. Install php and composer
|
||||||
|
|||||||
267
app/Http/Controllers/TestController.php
Normal file
267
app/Http/Controllers/TestController.php
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Resources\TestResource;
|
||||||
|
use App\Models\Log;
|
||||||
|
use App\Models\Test;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class TestController extends Controller
|
||||||
|
{
|
||||||
|
const PAGINATION_COUNT = 20;
|
||||||
|
private const FIELD_RULES = [
|
||||||
|
'title' => 'required|string|max:255',
|
||||||
|
'description' => 'required|string',
|
||||||
|
'category_id' => 'nullable|exists:categories,id',
|
||||||
|
'questions' => 'required|array|min:1',
|
||||||
|
'questions.*' => 'exists:questions,id',
|
||||||
|
'closed_at' => 'nullable|date',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Get(
|
||||||
|
* path="/api/tests",
|
||||||
|
* summary="Get a list of tests (paginated)",
|
||||||
|
* description="Retrieve a paginated list of tests. Optionally filter by category_id.",
|
||||||
|
* tags={"Tests"},
|
||||||
|
* @OA\Parameter(
|
||||||
|
* name="category_id",
|
||||||
|
* in="query",
|
||||||
|
* required=false,
|
||||||
|
* description="Filter tests by category ID",
|
||||||
|
* @OA\Schema(type="integer")
|
||||||
|
* ),
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="Paginated list of tests",
|
||||||
|
* @OA\JsonContent(
|
||||||
|
* type="object",
|
||||||
|
* @OA\Property(
|
||||||
|
* property="data",
|
||||||
|
* type="array",
|
||||||
|
* @OA\Items(ref="#/components/schemas/TestResource")
|
||||||
|
* ),
|
||||||
|
* @OA\Property(
|
||||||
|
* property="links",
|
||||||
|
* type="object",
|
||||||
|
* @OA\Property(property="first", type="string", example="http://api.example.com/tests?page=1"),
|
||||||
|
* @OA\Property(property="last", type="string", example="http://api.example.com/tests?page=10"),
|
||||||
|
* @OA\Property(property="prev", type="string", nullable=true, example=null),
|
||||||
|
* @OA\Property(property="next", type="string", nullable=true, example="http://api.example.com/tests?page=2")
|
||||||
|
* ),
|
||||||
|
* @OA\Property(
|
||||||
|
* property="meta",
|
||||||
|
* type="object",
|
||||||
|
* @OA\Property(property="current_page", type="integer", example=1),
|
||||||
|
* @OA\Property(property="from", type="integer", example=1),
|
||||||
|
* @OA\Property(property="last_page", type="integer", example=10),
|
||||||
|
* @OA\Property(property="path", type="string", example="http://api.example.com/tests"),
|
||||||
|
* @OA\Property(property="per_page", type="integer", example=15),
|
||||||
|
* @OA\Property(property="to", type="integer", example=15),
|
||||||
|
* @OA\Property(property="total", type="integer", example=150)
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* ),
|
||||||
|
* @OA\Response(
|
||||||
|
* response=401,
|
||||||
|
* description="Unauthorized"
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$query = Test::with(['author', 'category']);
|
||||||
|
|
||||||
|
if ($request->has('category_id')) {
|
||||||
|
$query->where('category_id', $request->query('category_id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$questions = $query->paginate(self::PAGINATION_COUNT);
|
||||||
|
|
||||||
|
return TestResource::collection($questions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Post(
|
||||||
|
* path="/api/tests",
|
||||||
|
* summary="Create a new test (only admin or creator)",
|
||||||
|
* description="Store a new test in the system (only admin or creator).",
|
||||||
|
* tags={"Tests"},
|
||||||
|
* security={{"bearerAuth":{}}},
|
||||||
|
* @OA\RequestBody(
|
||||||
|
* required=true,
|
||||||
|
* @OA\JsonContent(
|
||||||
|
* required={"title","closed_at","questions"},
|
||||||
|
* @OA\Property(property="title", type="string", maxLength=255, example="Sample Test"),
|
||||||
|
* @OA\Property(property="description", type="string", nullable=true, example="Optional description"),
|
||||||
|
* @OA\Property(property="closed_at", type="string", format="date-time", nullable=true, example="2025-12-01T23:59:59Z"),
|
||||||
|
* @OA\Property(property="category_id", type="integer", nullable=true, example=3),
|
||||||
|
* @OA\Property(
|
||||||
|
* property="questions",
|
||||||
|
* type="array",
|
||||||
|
* description="Array of question IDs to attach to this test",
|
||||||
|
* @OA\Items(type="integer", example=1)
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* ),
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="Test created successfully",
|
||||||
|
* @OA\JsonContent(ref="#/components/schemas/TestResource")
|
||||||
|
* ),
|
||||||
|
* @OA\Response(
|
||||||
|
* response=401,
|
||||||
|
* description="Unauthorized"
|
||||||
|
* ),
|
||||||
|
* @OA\Response(
|
||||||
|
* response=422,
|
||||||
|
* description="Validation error"
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$this->authorize('create', Test::class);
|
||||||
|
$fields = $request->validate(self::FIELD_RULES);
|
||||||
|
|
||||||
|
$fields['author_id'] = $request->user()->id;
|
||||||
|
|
||||||
|
$test = Test::create($fields);
|
||||||
|
$test->questions()->sync($fields['questions']);
|
||||||
|
|
||||||
|
$test->load(['category', 'author', 'questions']);
|
||||||
|
Log::writeLog("Test '" . $test->title . "' is created by " . $request->user()->username);
|
||||||
|
return new TestResource($test);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Get(
|
||||||
|
* path="/api/tests/{id}",
|
||||||
|
* summary="Get a single test",
|
||||||
|
* description="Retrieve a single test with its questions, category, and author",
|
||||||
|
* tags={"Tests"},
|
||||||
|
* @OA\Parameter(
|
||||||
|
* name="id",
|
||||||
|
* in="path",
|
||||||
|
* required=true,
|
||||||
|
* description="Test ID",
|
||||||
|
* @OA\Schema(type="integer")
|
||||||
|
* ),
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="Test retrieved successfully",
|
||||||
|
* @OA\JsonContent(ref="#/components/schemas/TestResource")
|
||||||
|
* ),
|
||||||
|
* @OA\Response(
|
||||||
|
* response=404,
|
||||||
|
* description="Test not found"
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function show(Test $test)
|
||||||
|
{
|
||||||
|
$test->load(['author', 'category', 'questions']);
|
||||||
|
return new TestResource($test);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Put(
|
||||||
|
* path="/api/tests/{id}",
|
||||||
|
* summary="Update a test (only admin or creator)",
|
||||||
|
* description="Update a test's data and associated questions (only admin/creator).",
|
||||||
|
* tags={"Tests"},
|
||||||
|
* security={{"bearerAuth":{}}},
|
||||||
|
* @OA\Parameter(
|
||||||
|
* name="id",
|
||||||
|
* in="path",
|
||||||
|
* required=true,
|
||||||
|
* description="Test ID",
|
||||||
|
* @OA\Schema(type="integer")
|
||||||
|
* ),
|
||||||
|
* @OA\RequestBody(
|
||||||
|
* required=true,
|
||||||
|
* @OA\JsonContent(
|
||||||
|
* required={"title","questions"},
|
||||||
|
* @OA\Property(property="title", type="string", maxLength=255, example="Updated Test Title"),
|
||||||
|
* @OA\Property(property="description", type="string", nullable=true, example="Optional description"),
|
||||||
|
* @OA\Property(property="category_id", type="integer", nullable=true, example=3),
|
||||||
|
* @OA\Property(property="closed_at", type="string", format="date-time", nullable=true, example="2025-12-01T23:59:59Z"),
|
||||||
|
* @OA\Property(
|
||||||
|
* property="questions",
|
||||||
|
* type="array",
|
||||||
|
* description="Array of question IDs to attach to this test",
|
||||||
|
* @OA\Items(type="integer", example=1)
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* ),
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="Test updated successfully",
|
||||||
|
* @OA\JsonContent(ref="#/components/schemas/TestResource")
|
||||||
|
* ),
|
||||||
|
* @OA\Response(
|
||||||
|
* response=401,
|
||||||
|
* description="Unauthorized"
|
||||||
|
* ),
|
||||||
|
* @OA\Response(
|
||||||
|
* response=422,
|
||||||
|
* description="Validation error"
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function update(Request $request, Test $test)
|
||||||
|
{
|
||||||
|
$this->authorize('update', $test);
|
||||||
|
$fields = $request->validate(self::FIELD_RULES);
|
||||||
|
|
||||||
|
$test->update($fields);
|
||||||
|
$test->questions()->sync($fields['questions']);
|
||||||
|
|
||||||
|
Log::writeLog("Test '" . $test->title . "' is updated by " . $request->user()->username);
|
||||||
|
|
||||||
|
$test->load(['category', 'author', 'questions']);
|
||||||
|
return new TestResource($test);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OA\Delete(
|
||||||
|
* path="/api/tests/{id}",
|
||||||
|
* summary="Delete a test (only admin or creator)",
|
||||||
|
* description="Delete a test by ID (only admin or creator).",
|
||||||
|
* tags={"Tests"},
|
||||||
|
* security={{"bearerAuth":{}}},
|
||||||
|
* @OA\Parameter(
|
||||||
|
* name="id",
|
||||||
|
* in="path",
|
||||||
|
* required=true,
|
||||||
|
* description="Test ID",
|
||||||
|
* @OA\Schema(type="integer")
|
||||||
|
* ),
|
||||||
|
* @OA\Response(
|
||||||
|
* response=200,
|
||||||
|
* description="Test deleted successfully",
|
||||||
|
* @OA\JsonContent(
|
||||||
|
* type="object",
|
||||||
|
* @OA\Property(property="message", type="string", example="The test was deleted")
|
||||||
|
* )
|
||||||
|
* ),
|
||||||
|
* @OA\Response(
|
||||||
|
* response=401,
|
||||||
|
* description="Unauthorized"
|
||||||
|
* ),
|
||||||
|
* @OA\Response(
|
||||||
|
* response=404,
|
||||||
|
* description="Test not found"
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function destroy(Request $request, Test $test)
|
||||||
|
{
|
||||||
|
$this->authorize('delete', $test);
|
||||||
|
$test->delete();
|
||||||
|
Log::writeLog("Test '" . $test->title . "' is deleted by " . $request->user()->username);
|
||||||
|
|
||||||
|
return ['message' => 'The hitcount was deleted'];
|
||||||
|
}
|
||||||
|
}
|
||||||
57
app/Http/Resources/TestResource.php
Normal file
57
app/Http/Resources/TestResource.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class TestResource extends JsonResource
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @OA\Schema(
|
||||||
|
* schema="TestResource",
|
||||||
|
* type="object",
|
||||||
|
* title="Test",
|
||||||
|
* description="Test resource",
|
||||||
|
* @OA\Property(property="id", type="integer", example=1),
|
||||||
|
* @OA\Property(property="title", type="string", example="Sample Test"),
|
||||||
|
* @OA\Property(property="description", type="string", nullable=true, example="Optional description"),
|
||||||
|
*
|
||||||
|
* @OA\Property(property="category_id", type="integer", nullable=true, example=3),
|
||||||
|
* @OA\Property(property="category", ref="#/components/schemas/CategoryResource"),
|
||||||
|
*
|
||||||
|
* @OA\Property(
|
||||||
|
* property="questions",
|
||||||
|
* type="array",
|
||||||
|
* @OA\Items(ref="#/components/schemas/QuestionResource")
|
||||||
|
* ),
|
||||||
|
*
|
||||||
|
* @OA\Property(property="author_id", type="integer", nullable=true, example=2),
|
||||||
|
* @OA\Property(property="author", ref="#/components/schemas/UserResource"),
|
||||||
|
*
|
||||||
|
* @OA\Property(property="closed_at", type="string", format="date-time", nullable=true, example="2025-11-11T10:30:00Z"),
|
||||||
|
* @OA\Property(property="created_at", type="string", format="date-time", example="2025-11-11T10:00:00Z"),
|
||||||
|
* @OA\Property(property="updated_at", type="string", format="date-time", example="2025-11-11T10:15:00Z")
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'title' => $this->title,
|
||||||
|
'description' => $this->description,
|
||||||
|
|
||||||
|
'category_id' => $this->category_id,
|
||||||
|
'category' => new CategoryResource($this->whenLoaded('category')),
|
||||||
|
|
||||||
|
'questions' => QuestionResource::collection($this->whenLoaded('questions')),
|
||||||
|
|
||||||
|
'author_id' => $this->author_id,
|
||||||
|
'author' => new UserResource($this->whenLoaded('author')),
|
||||||
|
|
||||||
|
'closed_at' => $this->closed_at,
|
||||||
|
'created_at' => $this->created_at,
|
||||||
|
'updated_at' => $this->updated_at,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
41
app/Models/Test.php
Normal file
41
app/Models/Test.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Test extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'title',
|
||||||
|
'description',
|
||||||
|
'category_id',
|
||||||
|
'author_id',
|
||||||
|
'closed_at',
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
protected $dates = [
|
||||||
|
'closed_at',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function category()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Category::class);
|
||||||
|
}
|
||||||
|
public function author()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function questions()
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(Question::class)
|
||||||
|
->withTimestamps();
|
||||||
|
}
|
||||||
|
}
|
||||||
66
app/Policies/TestPolicy.php
Normal file
66
app/Policies/TestPolicy.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Policies;
|
||||||
|
|
||||||
|
use App\Models\Test;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\Access\Response;
|
||||||
|
|
||||||
|
class TestPolicy
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine whether the user can view any models.
|
||||||
|
*/
|
||||||
|
public function viewAny(User $user): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can view the model.
|
||||||
|
*/
|
||||||
|
public function view(User $user, Test $test): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can create models.
|
||||||
|
*/
|
||||||
|
public function create(User $user): bool
|
||||||
|
{
|
||||||
|
return $user->type == 'admin' || $user->type == 'creator';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can update the model.
|
||||||
|
*/
|
||||||
|
public function update(User $user, Test $question): bool
|
||||||
|
{
|
||||||
|
return $user->type == 'admin' || $question->user_id == $user->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can delete the model.
|
||||||
|
*/
|
||||||
|
public function delete(User $user, Test $question): bool
|
||||||
|
{
|
||||||
|
return $user->type == 'admin' || $question->user_id == $user->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can restore the model.
|
||||||
|
*/
|
||||||
|
public function restore(User $user, Test $test): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether the user can permanently delete the model.
|
||||||
|
*/
|
||||||
|
public function forceDelete(User $user, Test $test): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
42
database/migrations/2025_11_11_191436_create_tests_table.php
Normal file
42
database/migrations/2025_11_11_191436_create_tests_table.php
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<?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('tests', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('title');
|
||||||
|
$table->string('description');
|
||||||
|
$table->timestamp('closed_at')->nullable();
|
||||||
|
$table->unsignedBigInteger('category_id')->nullable();
|
||||||
|
$table->unsignedBigInteger('author_id')->nullable();
|
||||||
|
|
||||||
|
$table->foreign('author_id')->references('id')->on('users')->onDelete('set null');
|
||||||
|
$table->foreign('category_id')->references('id')->on('categories')->onDelete('set null');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('question_test', function (Blueprint $table) {
|
||||||
|
$table->foreignId('test_id')->constrained()->cascadeOnDelete();
|
||||||
|
$table->foreignId('question_id')->constrained()->cascadeOnDelete();
|
||||||
|
$table->primary(['test_id', 'question_id']);
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('tests');
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -5,9 +5,19 @@ use App\Http\Controllers\CategoryController;
|
|||||||
use App\Http\Controllers\HitcountController;
|
use App\Http\Controllers\HitcountController;
|
||||||
use App\Http\Controllers\LogController;
|
use App\Http\Controllers\LogController;
|
||||||
use App\Http\Controllers\QuestionController;
|
use App\Http\Controllers\QuestionController;
|
||||||
|
use App\Http\Controllers\TestController;
|
||||||
use App\Http\Controllers\UserController;
|
use App\Http\Controllers\UserController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
// Tests
|
||||||
|
Route::get('tests', [TestController::class, 'index']);
|
||||||
|
Route::get('tests/{test}', [TestController::class, 'show']);
|
||||||
|
Route::middleware('auth:sanctum')->group(function () {
|
||||||
|
Route::post('tests', [TestController::class, 'store']);
|
||||||
|
Route::put('tests/{test}', [TestController::class, 'update']);
|
||||||
|
Route::delete('tests/{test}', [TestController::class, 'destroy']);
|
||||||
|
});
|
||||||
|
|
||||||
// Questions
|
// Questions
|
||||||
Route::get('questions', [QuestionController::class, 'index']);
|
Route::get('questions', [QuestionController::class, 'index']);
|
||||||
Route::get('questions/{question}', [QuestionController::class, 'show']);
|
Route::get('questions/{question}', [QuestionController::class, 'show']);
|
||||||
|
|||||||
@ -1496,6 +1496,354 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/tests": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Tests"
|
||||||
|
],
|
||||||
|
"summary": "Get a list of tests (paginated)",
|
||||||
|
"description": "Retrieve a paginated list of tests. Optionally filter by category_id.",
|
||||||
|
"operationId": "5f539f69bb1d910182eb35136c5baa3a",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "category_id",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Filter tests by category ID",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Paginated list of tests",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/TestResource"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"links": {
|
||||||
|
"properties": {
|
||||||
|
"first": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "http://api.example.com/tests?page=1"
|
||||||
|
},
|
||||||
|
"last": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "http://api.example.com/tests?page=10"
|
||||||
|
},
|
||||||
|
"prev": {
|
||||||
|
"type": "string",
|
||||||
|
"example": null,
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"next": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "http://api.example.com/tests?page=2",
|
||||||
|
"nullable": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"properties": {
|
||||||
|
"current_page": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"from": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"last_page": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 10
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "http://api.example.com/tests"
|
||||||
|
},
|
||||||
|
"per_page": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 15
|
||||||
|
},
|
||||||
|
"to": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 15
|
||||||
|
},
|
||||||
|
"total": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 150
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Tests"
|
||||||
|
],
|
||||||
|
"summary": "Create a new test (only admin or creator)",
|
||||||
|
"description": "Store a new test in the system (only admin or creator).",
|
||||||
|
"operationId": "7728a2f3dd87105d6d617df9a3f231d4",
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"required": [
|
||||||
|
"title",
|
||||||
|
"closed_at",
|
||||||
|
"questions"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 255,
|
||||||
|
"example": "Sample Test"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Optional description",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"closed_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"example": "2025-12-01T23:59:59Z",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"category_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 3,
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"questions": {
|
||||||
|
"description": "Array of question IDs to attach to this test",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Test created successfully",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResource"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/tests/{id}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Tests"
|
||||||
|
],
|
||||||
|
"summary": "Get a single test",
|
||||||
|
"description": "Retrieve a single test with its questions, category, and author",
|
||||||
|
"operationId": "7e3d8428f4df82c6d4ee7bd1d4d2128c",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "Test ID",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Test retrieved successfully",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResource"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Test not found"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"tags": [
|
||||||
|
"Tests"
|
||||||
|
],
|
||||||
|
"summary": "Update a test (only admin or creator)",
|
||||||
|
"description": "Update a test's data and associated questions (only admin/creator).",
|
||||||
|
"operationId": "ca1490751234c723e0a4a708afe16dd6",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "Test ID",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"required": [
|
||||||
|
"title",
|
||||||
|
"questions"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 255,
|
||||||
|
"example": "Updated Test Title"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Optional description",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"category_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 3,
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"closed_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"example": "2025-12-01T23:59:59Z",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"questions": {
|
||||||
|
"description": "Array of question IDs to attach to this test",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Test updated successfully",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TestResource"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"tags": [
|
||||||
|
"Tests"
|
||||||
|
],
|
||||||
|
"summary": "Delete a test (only admin or creator)",
|
||||||
|
"description": "Delete a test by ID (only admin or creator).",
|
||||||
|
"operationId": "92f76a68796679554c71a4659f62b296",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"description": "Test ID",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Test deleted successfully",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "The test was deleted"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Unauthorized"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Test not found"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearerAuth": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/users": {
|
"/api/users": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@ -2011,6 +2359,64 @@
|
|||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"TestResource": {
|
||||||
|
"title": "Test",
|
||||||
|
"description": "Test resource",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Sample Test"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Optional description",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"category_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 3,
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
"$ref": "#/components/schemas/CategoryResource"
|
||||||
|
},
|
||||||
|
"questions": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/QuestionResource"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"author_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 2,
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"$ref": "#/components/schemas/UserResource"
|
||||||
|
},
|
||||||
|
"closed_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"example": "2025-11-11T10:30:00Z",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"example": "2025-11-11T10:00:00Z"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"example": "2025-11-11T10:15:00Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"UserResource": {
|
"UserResource": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
@ -2074,6 +2480,10 @@
|
|||||||
"name": "Questions",
|
"name": "Questions",
|
||||||
"description": "Questions"
|
"description": "Questions"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Tests",
|
||||||
|
"description": "Tests"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Users",
|
"name": "Users",
|
||||||
"description": "Users"
|
"description": "Users"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user