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)
|
||||
* [mobiledetect/mobiledetectlib](https://packagist.org/packages/mobiledetect/mobiledetectlib)
|
||||
|
||||
## External API
|
||||
* [ip-api.com](http://ip-api.com/)
|
||||
* [openai.com](https://openai.com/)
|
||||
|
||||
## Installation
|
||||
|
||||
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\LogController;
|
||||
use App\Http\Controllers\QuestionController;
|
||||
use App\Http\Controllers\TestController;
|
||||
use App\Http\Controllers\UserController;
|
||||
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
|
||||
Route::get('questions', [QuestionController::class, 'index']);
|
||||
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": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@ -2011,6 +2359,64 @@
|
||||
},
|
||||
"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": {
|
||||
"properties": {
|
||||
"id": {
|
||||
@ -2074,6 +2480,10 @@
|
||||
"name": "Questions",
|
||||
"description": "Questions"
|
||||
},
|
||||
{
|
||||
"name": "Tests",
|
||||
"description": "Tests"
|
||||
},
|
||||
{
|
||||
"name": "Users",
|
||||
"description": "Users"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user