Questions
This commit is contained in:
parent
13dd3b8b6d
commit
7dd13799e5
@ -106,7 +106,7 @@ class HitcountController extends Controller
|
||||
{
|
||||
$this->authorize('delete', $hitcount);
|
||||
$hitcount->delete();
|
||||
Log::writeLog("Category '" . $hitcount->id . "' is deleted by " . $request->user()->username);
|
||||
Log::writeLog("Hitcount '" . $hitcount->id . "' is deleted by " . $request->user()->username);
|
||||
return ['message' => 'The hitcount was deleted'];
|
||||
}
|
||||
|
||||
|
||||
315
app/Http/Controllers/QuestionController.php
Normal file
315
app/Http/Controllers/QuestionController.php
Normal file
@ -0,0 +1,315 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Resources\QuestionResource;
|
||||
use App\Models\Log;
|
||||
use App\Models\Question;
|
||||
use App\Rules\ValidVariants;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class QuestionController extends Controller
|
||||
{
|
||||
const PAGINATION_COUNT = 20;
|
||||
|
||||
private static function get_field_rules(): array
|
||||
{
|
||||
return [
|
||||
'title' => 'required|string|max:255',
|
||||
'description' => 'nullable|string',
|
||||
'type' => 'required|in:single,multiple,text',
|
||||
'difficulty' => 'required|integer',
|
||||
'category_id' => 'nullable|exists:categories,id',
|
||||
'variants' => ['array', 'nullable'],
|
||||
'variants.*.id' => ['required', 'integer'],
|
||||
'variants.*.text' => ['required', 'string'],
|
||||
'correct_answers' => 'required|array',
|
||||
'is_pending_question' => 'integer'
|
||||
];
|
||||
}
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/questions",
|
||||
* summary="Get a paginated list of questions (paginated)",
|
||||
* description="Retrieve questions. Optional filter by category_id.",
|
||||
* tags={"Questions"},
|
||||
* @OA\Parameter(
|
||||
* name="category_id",
|
||||
* in="query",
|
||||
* description="Filter questions by category ID",
|
||||
* required=false,
|
||||
* @OA\Schema(type="integer")
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="page",
|
||||
* in="query",
|
||||
* description="Page number for pagination",
|
||||
* required=false,
|
||||
* @OA\Schema(type="integer", default=1)
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Paginated list of questions",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(
|
||||
* property="data",
|
||||
* type="array",
|
||||
* @OA\Items(ref="#/components/schemas/QuestionResource")
|
||||
* ),
|
||||
* @OA\Property(
|
||||
* property="links",
|
||||
* type="object",
|
||||
* example={
|
||||
* "first": "http://localhost/api/questions?page=1",
|
||||
* "last": "http://localhost/api/questions?page=10",
|
||||
* "prev": null,
|
||||
* "next": "http://localhost/api/questions?page=2"
|
||||
* }
|
||||
* ),
|
||||
* @OA\Property(
|
||||
* property="meta",
|
||||
* type="object",
|
||||
* example={
|
||||
* "current_page": 1,
|
||||
* "from": 1,
|
||||
* "last_page": 10,
|
||||
* "path": "http://localhost/api/questions",
|
||||
* "per_page": 15,
|
||||
* "to": 15,
|
||||
* "total": 150
|
||||
* }
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=401,
|
||||
* description="Unauthorized"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = Question::with(['author', 'category']);
|
||||
|
||||
if ($request->has('category_id')) {
|
||||
$query->where('category_id', $request->query('category_id'));
|
||||
}
|
||||
|
||||
$questions = $query->paginate(self::PAGINATION_COUNT);
|
||||
|
||||
return QuestionResource::collection($questions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/questions/{id}",
|
||||
* summary="Get a single question",
|
||||
* description="Retrieve a single question by its ID, including author and category",
|
||||
* tags={"Questions"},
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="ID of the question to retrieve",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Question retrieved successfully",
|
||||
* @OA\JsonContent(ref="#/components/schemas/QuestionResource")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=401,
|
||||
* description="Unauthorized"
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=404,
|
||||
* description="Question not found"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function show(Question $question)
|
||||
{
|
||||
$question->load(['author', 'category']);
|
||||
return new QuestionResource($question);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/api/questions",
|
||||
* summary="Create a new question",
|
||||
* description="Store a new question in the system (only admin or creator).",
|
||||
* tags={"Questions"},
|
||||
* security={{"bearerAuth":{}}},
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(
|
||||
* required={"title","type","difficulty","variants","correct_answers"},
|
||||
* @OA\Property(property="title", type="string", maxLength=255, example="Sample question"),
|
||||
* @OA\Property(property="description", type="string", nullable=true, example="Optional description"),
|
||||
* @OA\Property(property="type", type="string", enum={"single","multiply","text"}, example="single"),
|
||||
* @OA\Property(property="difficulty", type="integer", example=2),
|
||||
* @OA\Property(
|
||||
* property="variants",
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* type="object",
|
||||
* @OA\Property(property="id", type="integer", example=1),
|
||||
* @OA\Property(property="text", type="string", example="Option 1")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Property(
|
||||
* property="correct_answers",
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* type="integer",
|
||||
* example=1
|
||||
* )
|
||||
* ),
|
||||
* @OA\Property(property="category_id", type="integer", nullable=true, example=3),
|
||||
* @OA\Property(property="is_pending_question", type="integer", example=0)
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Question created successfully",
|
||||
* @OA\JsonContent(ref="#/components/schemas/QuestionResource")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=401,
|
||||
* description="Unauthorized"
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$this->authorize('create', Question::class);
|
||||
$fields = $request->validate(self::get_field_rules());
|
||||
$fields['author_id'] = $request->user()->id;
|
||||
|
||||
$question = Question::create($fields);
|
||||
|
||||
Log::writeLog("Question '" . $question->title . "' is created by " . $request->user()->username);
|
||||
|
||||
return new QuestionResource($question);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Put(
|
||||
* path="/api/questions/{id}",
|
||||
* summary="Update a question",
|
||||
* description="Update an existing question by ID (only admin or creator).",
|
||||
* tags={"Questions"},
|
||||
* security={{"bearerAuth":{}}},
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* description="ID of the question to update",
|
||||
* @OA\Schema(type="integer")
|
||||
* ),
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(
|
||||
* required={"title","type","difficulty","variants","correct_answers"},
|
||||
* @OA\Property(property="title", type="string", maxLength=255, example="Updated question"),
|
||||
* @OA\Property(property="description", type="string", nullable=true, example="Updated description"),
|
||||
* @OA\Property(property="type", type="string", enum={"single","multiply","text"}, example="multiply"),
|
||||
* @OA\Property(property="difficulty", type="integer", example=3),
|
||||
* @OA\Property(
|
||||
* property="variants",
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* type="object",
|
||||
* @OA\Property(property="id", type="integer", example=1),
|
||||
* @OA\Property(property="text", type="string", example="Option 1")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Property(
|
||||
* property="correct_answers",
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* type="integer",
|
||||
* example=1
|
||||
* )
|
||||
* ),
|
||||
* @OA\Property(property="category_id", type="integer", nullable=true, example=2),
|
||||
* @OA\Property(property="is_pending_question", type="integer", example=0)
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Question updated successfully",
|
||||
* @OA\JsonContent(ref="#/components/schemas/QuestionResource")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=401,
|
||||
* description="Unauthorized"
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function update(Request $request, Question $question)
|
||||
{
|
||||
$this->authorize('update', $question);
|
||||
$fields = $request->validate(self::get_field_rules());
|
||||
$fields['variants'] = json_encode($fields['variants']);
|
||||
|
||||
$question->update($fields);
|
||||
|
||||
Log::writeLog("Question '" . $question->title . "' is updated by " . $request->user()->username);
|
||||
|
||||
return new QuestionResource($question);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Delete(
|
||||
* path="/api/questions/{id}",
|
||||
* summary="Delete a question",
|
||||
* description="Delete a question by ID (only admin or creator).",
|
||||
* tags={"Questions"},
|
||||
* security={{"bearerAuth":{}}},
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* description="ID of the question to delete",
|
||||
* @OA\Schema(type="integer")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Question deleted successfully",
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(property="message", type="string", example="The hitcount was deleted")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=401,
|
||||
* description="Unauthorized"
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=403,
|
||||
* description="Forbidden"
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=404,
|
||||
* description="Question not found"
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
public function destroy(Request $request, Question $question)
|
||||
{
|
||||
$this->authorize('delete', $question);
|
||||
$question->delete();
|
||||
Log::writeLog("Question '" . $question->id . "' is deleted by " . $request->user()->username);
|
||||
|
||||
return ['message' => 'The hitcount was deleted'];
|
||||
}
|
||||
}
|
||||
67
app/Http/Resources/QuestionResource.php
Normal file
67
app/Http/Resources/QuestionResource.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class QuestionResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* @OA\Schema(
|
||||
* schema="QuestionResource",
|
||||
* type="object",
|
||||
* title="Question",
|
||||
* description="Question resource",
|
||||
* @OA\Property(property="id", type="integer", example=1),
|
||||
* @OA\Property(property="title", type="string", example="What is the capital of Japan?"),
|
||||
* @OA\Property(property="description", type="string", nullable=true, example="Choose the correct option"),
|
||||
* @OA\Property(property="type", type="string", enum={"single","multiply","text"}, example="single"),
|
||||
* @OA\Property(property="difficulty", type="integer", example=3),
|
||||
* @OA\Property(
|
||||
* property="variants",
|
||||
* type="array",
|
||||
* nullable=true,
|
||||
* @OA\Items(
|
||||
* type="object",
|
||||
* @OA\Property(property="id", type="integer", example=1),
|
||||
* @OA\Property(property="text", type="string", example="Tokyo")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Property(
|
||||
* property="correct_answers",
|
||||
* type="array",
|
||||
* @OA\Items(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Property(property="is_pending_question", type="integer", example=0),
|
||||
* @OA\Property(property="category_id", type="integer", nullable=true, example=3),
|
||||
* @OA\Property(property="category", ref="#/components/schemas/CategoryResource"),
|
||||
* @OA\Property(property="author_id", type="integer", nullable=true, example=2),
|
||||
* @OA\Property(property="author", ref="#/components/schemas/UserResource"),
|
||||
* @OA\Property(property="created_at", type="string", format="date-time", example="2025-11-11T10:00:00.000000Z"),
|
||||
* @OA\Property(property="updated_at", type="string", format="date-time", example="2025-11-11T10:05:00.000000Z")
|
||||
* )
|
||||
*/
|
||||
public function toArray(Request $request): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'title' => $this->title,
|
||||
'description' => $this->description,
|
||||
'type' => $this->type,
|
||||
'difficulty' => $this->difficulty,
|
||||
'variants' => $this->variants,
|
||||
'correct_answers' => $this->correct_answers,
|
||||
'is_pending_question' => $this->is_pending_question,
|
||||
|
||||
'category_id' => $this->category_id,
|
||||
'category' => new CategoryResource($this->whenLoaded('category')),
|
||||
|
||||
'author_id' => $this->author_id,
|
||||
'author' => new UserResource($this->whenLoaded('author')),
|
||||
|
||||
'created_at' => $this->created_at,
|
||||
'updated_at' => $this->updated_at,
|
||||
];
|
||||
}
|
||||
}
|
||||
56
app/Models/Question.php
Normal file
56
app/Models/Question.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Question extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'description',
|
||||
'type',
|
||||
'difficulty',
|
||||
'category_id',
|
||||
'author_id',
|
||||
'variants',
|
||||
'is_pending_question',
|
||||
'correct_answers',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'is_pending_question' => 'boolean',
|
||||
'correct_answers' => 'array',
|
||||
'variants' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
* Category relation (nullable, on delete set null).
|
||||
*/
|
||||
public function category()
|
||||
{
|
||||
return $this->belongsTo(Category::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Author relation (nullable, on delete set null).
|
||||
*/
|
||||
public function author()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'author_id');
|
||||
}
|
||||
|
||||
}
|
||||
66
app/Policies/QuestionPolicy.php
Normal file
66
app/Policies/QuestionPolicy.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\Question;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\Response;
|
||||
|
||||
class QuestionPolicy
|
||||
{
|
||||
/**
|
||||
* 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, Question $question): 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, Question $question): bool
|
||||
{
|
||||
return $user->type == 'admin' || $question->user_id == $user->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can delete the model.
|
||||
*/
|
||||
public function delete(User $user, Question $question): bool
|
||||
{
|
||||
return $user->type == 'admin' || $question->user_id == $user->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore the model.
|
||||
*/
|
||||
public function restore(User $user, Question $question): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete the model.
|
||||
*/
|
||||
public function forceDelete(User $user, Question $question): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,7 @@
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"darkaonline/l5-swagger": "^9.0",
|
||||
"justinrainbow/json-schema": "^6.6",
|
||||
"laravel/framework": "^12.0",
|
||||
"laravel/sanctum": "^4.0",
|
||||
"laravel/tinker": "^2.10.1",
|
||||
|
||||
150
composer.lock
generated
150
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "e96a8ca5efc08a222af10a6a43726369",
|
||||
"content-hash": "a58d7ff4f8de3898bf7fa6350013d42a",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brick/math",
|
||||
@ -1210,6 +1210,81 @@
|
||||
],
|
||||
"time": "2025-08-22T14:27:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "justinrainbow/json-schema",
|
||||
"version": "6.6.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/jsonrainbow/json-schema.git",
|
||||
"reference": "fd8e5c6b1badb998844ad34ce0abcd71a0aeb396"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/fd8e5c6b1badb998844ad34ce0abcd71a0aeb396",
|
||||
"reference": "fd8e5c6b1badb998844ad34ce0abcd71a0aeb396",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"marc-mabe/php-enum": "^4.0",
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "3.3.0",
|
||||
"json-schema/json-schema-test-suite": "^23.2",
|
||||
"marc-mabe/php-enum-phpstan": "^2.0",
|
||||
"phpspec/prophecy": "^1.19",
|
||||
"phpstan/phpstan": "^1.12",
|
||||
"phpunit/phpunit": "^8.5"
|
||||
},
|
||||
"bin": [
|
||||
"bin/validate-json"
|
||||
],
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "6.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"JsonSchema\\": "src/JsonSchema/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Bruno Prieto Reis",
|
||||
"email": "bruno.p.reis@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Justin Rainbow",
|
||||
"email": "justin.rainbow@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Igor Wiedler",
|
||||
"email": "igor@wiedler.ch"
|
||||
},
|
||||
{
|
||||
"name": "Robert Schönthal",
|
||||
"email": "seroscho@googlemail.com"
|
||||
}
|
||||
],
|
||||
"description": "A library to validate a json schema.",
|
||||
"homepage": "https://github.com/jsonrainbow/json-schema",
|
||||
"keywords": [
|
||||
"json",
|
||||
"schema"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/jsonrainbow/json-schema/issues",
|
||||
"source": "https://github.com/jsonrainbow/json-schema/tree/6.6.1"
|
||||
},
|
||||
"time": "2025-11-07T18:30:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v12.37.0",
|
||||
@ -2230,6 +2305,79 @@
|
||||
],
|
||||
"time": "2024-12-08T08:18:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "marc-mabe/php-enum",
|
||||
"version": "v4.7.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/marc-mabe/php-enum.git",
|
||||
"reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/marc-mabe/php-enum/zipball/bb426fcdd65c60fb3638ef741e8782508fda7eef",
|
||||
"reference": "bb426fcdd65c60fb3638ef741e8782508fda7eef",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-reflection": "*",
|
||||
"php": "^7.1 | ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpbench/phpbench": "^0.16.10 || ^1.0.4",
|
||||
"phpstan/phpstan": "^1.3.1",
|
||||
"phpunit/phpunit": "^7.5.20 | ^8.5.22 | ^9.5.11",
|
||||
"vimeo/psalm": "^4.17.0 | ^5.26.1"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-3.x": "3.2-dev",
|
||||
"dev-master": "4.7-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"MabeEnum\\": "src/"
|
||||
},
|
||||
"classmap": [
|
||||
"stubs/Stringable.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Marc Bennewitz",
|
||||
"email": "dev@mabe.berlin",
|
||||
"homepage": "https://mabe.berlin/",
|
||||
"role": "Lead"
|
||||
}
|
||||
],
|
||||
"description": "Simple and fast implementation of enumerations with native PHP",
|
||||
"homepage": "https://github.com/marc-mabe/php-enum",
|
||||
"keywords": [
|
||||
"enum",
|
||||
"enum-map",
|
||||
"enum-set",
|
||||
"enumeration",
|
||||
"enumerator",
|
||||
"enummap",
|
||||
"enumset",
|
||||
"map",
|
||||
"set",
|
||||
"type",
|
||||
"type-hint",
|
||||
"typehint"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/marc-mabe/php-enum/issues",
|
||||
"source": "https://github.com/marc-mabe/php-enum/tree/v4.7.2"
|
||||
},
|
||||
"time": "2025-09-14T11:18:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mobiledetect/mobiledetectlib",
|
||||
"version": "4.8.09",
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
<?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('questions', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('title', 255);
|
||||
$table->text('description')->nullable();
|
||||
$table->enum('type', ['single', 'multiple', 'text']);
|
||||
$table->integer('difficulty');
|
||||
$table->unsignedBigInteger('category_id')->nullable();
|
||||
$table->unsignedBigInteger('author_id')->nullable();
|
||||
$table->text('variants');
|
||||
$table->tinyInteger('is_pending_question')->unsigned()->default(0);
|
||||
$table->text('correct_answers')->default('[]');
|
||||
|
||||
$table->foreign('author_id')->references('id')->on('users')->onDelete('set null');
|
||||
$table->foreign('category_id')->references('id')->on('categories')->onDelete('set null');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('questions');
|
||||
}
|
||||
};
|
||||
@ -4,9 +4,20 @@ use App\Http\Controllers\AuthController;
|
||||
use App\Http\Controllers\CategoryController;
|
||||
use App\Http\Controllers\HitcountController;
|
||||
use App\Http\Controllers\LogController;
|
||||
use App\Http\Controllers\QuestionController;
|
||||
use App\Http\Controllers\UserController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
// Questions
|
||||
Route::get('questions', [QuestionController::class, 'index']);
|
||||
Route::get('questions/{question}', [QuestionController::class, 'show']);
|
||||
|
||||
Route::middleware('auth:sanctum')->group(function () {
|
||||
Route::post('questions', [QuestionController::class, 'store']);
|
||||
Route::put('questions/{question}', [QuestionController::class, 'update']);
|
||||
Route::delete('questions/{question}', [QuestionController::class, 'destroy']);
|
||||
});
|
||||
|
||||
// HitcountController
|
||||
Route::post('hit', [HitcountController::class, 'callHit']);
|
||||
Route::apiResource('hitcounts', HitcountController::class)->middleware('auth:sanctum');
|
||||
|
||||
@ -1110,6 +1110,392 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/questions": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Questions"
|
||||
],
|
||||
"summary": "Get a paginated list of questions (paginated)",
|
||||
"description": "Retrieve questions. Optional filter by category_id.",
|
||||
"operationId": "e64d3c7a745fc05662a4a1e1eb3d96ab",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "category_id",
|
||||
"in": "query",
|
||||
"description": "Filter questions by category ID",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "page",
|
||||
"in": "query",
|
||||
"description": "Page number for pagination",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"default": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Paginated list of questions",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/QuestionResource"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"type": "object",
|
||||
"example": {
|
||||
"first": "http://localhost/api/questions?page=1",
|
||||
"last": "http://localhost/api/questions?page=10",
|
||||
"prev": null,
|
||||
"next": "http://localhost/api/questions?page=2"
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
"type": "object",
|
||||
"example": {
|
||||
"current_page": 1,
|
||||
"from": 1,
|
||||
"last_page": 10,
|
||||
"path": "http://localhost/api/questions",
|
||||
"per_page": 15,
|
||||
"to": 15,
|
||||
"total": 150
|
||||
}
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"Questions"
|
||||
],
|
||||
"summary": "Create a new question",
|
||||
"description": "Store a new question in the system (only admin or creator).",
|
||||
"operationId": "788d85763184ddf1b557afb040547f32",
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"required": [
|
||||
"title",
|
||||
"type",
|
||||
"difficulty",
|
||||
"variants",
|
||||
"correct_answers"
|
||||
],
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"maxLength": 255,
|
||||
"example": "Sample question"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"example": "Optional description",
|
||||
"nullable": true
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"single",
|
||||
"multiply",
|
||||
"text"
|
||||
],
|
||||
"example": "single"
|
||||
},
|
||||
"difficulty": {
|
||||
"type": "integer",
|
||||
"example": 2
|
||||
},
|
||||
"variants": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"example": "Option 1"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"correct_answers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
}
|
||||
},
|
||||
"category_id": {
|
||||
"type": "integer",
|
||||
"example": 3,
|
||||
"nullable": true
|
||||
},
|
||||
"is_pending_question": {
|
||||
"type": "integer",
|
||||
"example": 0
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Question created successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/QuestionResource"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation error"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearerAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/questions/{id}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Questions"
|
||||
],
|
||||
"summary": "Get a single question",
|
||||
"description": "Retrieve a single question by its ID, including author and category",
|
||||
"operationId": "bea45702e58c27163e9dc928d8984dfa",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"description": "ID of the question to retrieve",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Question retrieved successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/QuestionResource"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"404": {
|
||||
"description": "Question not found"
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"tags": [
|
||||
"Questions"
|
||||
],
|
||||
"summary": "Update a question",
|
||||
"description": "Update an existing question by ID (only admin or creator).",
|
||||
"operationId": "795b02e5ecdc23fd74f20e0671a40f8c",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"description": "ID of the question to update",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"required": [
|
||||
"title",
|
||||
"type",
|
||||
"difficulty",
|
||||
"variants",
|
||||
"correct_answers"
|
||||
],
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"maxLength": 255,
|
||||
"example": "Updated question"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"example": "Updated description",
|
||||
"nullable": true
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"single",
|
||||
"multiply",
|
||||
"text"
|
||||
],
|
||||
"example": "multiply"
|
||||
},
|
||||
"difficulty": {
|
||||
"type": "integer",
|
||||
"example": 3
|
||||
},
|
||||
"variants": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"example": "Option 1"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"correct_answers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
}
|
||||
},
|
||||
"category_id": {
|
||||
"type": "integer",
|
||||
"example": 2,
|
||||
"nullable": true
|
||||
},
|
||||
"is_pending_question": {
|
||||
"type": "integer",
|
||||
"example": 0
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Question updated successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/QuestionResource"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation error"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearerAuth": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"tags": [
|
||||
"Questions"
|
||||
],
|
||||
"summary": "Delete a question",
|
||||
"description": "Delete a question by ID (only admin or creator).",
|
||||
"operationId": "b2cbd34337a604c75c8a94f1a6e2f252",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"description": "ID of the question to delete",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Question deleted successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"example": "The hitcount was deleted"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden"
|
||||
},
|
||||
"404": {
|
||||
"description": "Question not found"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearerAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/api/users": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@ -1538,6 +1924,93 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"QuestionResource": {
|
||||
"title": "Question",
|
||||
"description": "Question resource",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"example": "What is the capital of Japan?"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"example": "Choose the correct option",
|
||||
"nullable": true
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"single",
|
||||
"multiply",
|
||||
"text"
|
||||
],
|
||||
"example": "single"
|
||||
},
|
||||
"difficulty": {
|
||||
"type": "integer",
|
||||
"example": 3
|
||||
},
|
||||
"variants": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"example": "Tokyo"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"nullable": true
|
||||
},
|
||||
"correct_answers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"example": 1
|
||||
}
|
||||
},
|
||||
"is_pending_question": {
|
||||
"type": "integer",
|
||||
"example": 0
|
||||
},
|
||||
"category_id": {
|
||||
"type": "integer",
|
||||
"example": 3,
|
||||
"nullable": true
|
||||
},
|
||||
"category": {
|
||||
"$ref": "#/components/schemas/CategoryResource"
|
||||
},
|
||||
"author_id": {
|
||||
"type": "integer",
|
||||
"example": 2,
|
||||
"nullable": true
|
||||
},
|
||||
"author": {
|
||||
"$ref": "#/components/schemas/UserResource"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"example": "2025-11-11T10:00:00.000000Z"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"example": "2025-11-11T10:05:00.000000Z"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"UserResource": {
|
||||
"properties": {
|
||||
"id": {
|
||||
@ -1597,6 +2070,10 @@
|
||||
"name": "Logs",
|
||||
"description": "Logs"
|
||||
},
|
||||
{
|
||||
"name": "Questions",
|
||||
"description": "Questions"
|
||||
},
|
||||
{
|
||||
"name": "Users",
|
||||
"description": "Users"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user