first commit

This commit is contained in:
2026-02-18 19:54:52 +07:00
commit 8e070562cb
101 changed files with 13462 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Routing\Controller as BaseController;
abstract class Controller extends BaseController
{
use AuthorizesRequests;
}

View File

@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Http\Requests\StoreTagRequest;
use App\Http\Requests\UpdateTagRequest;
use App\Models\Tag;
class TagController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*/
public function store(StoreTagRequest $request)
{
//
}
/**
* Display the specified resource.
*/
public function show(Tag $tag)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Tag $tag)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(UpdateTagRequest $request, Tag $tag)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Tag $tag)
{
//
}
}

View File

@@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Contracts\TaskServiceInterface;
use App\Http\Requests\IndexTaskRequest;
use App\Http\Requests\StoreTaskRequest;
use App\Http\Requests\UpdateTaskRequest;
use App\Http\Resources\TaskResource;
use App\Models\Task;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;
use Throwable;
class TaskController extends Controller
{
public function __construct(private readonly TaskServiceInterface $service)
{
}
/**
* Display a listing of the resource.
*/
public function index(IndexTaskRequest $request): JsonResponse
{
try {
$tasks = $this->service->list($request);
return TaskResource::collection($tasks)->response();
} catch (Throwable $e) {
return response()->json([
'error' => 'Service unavailable',
'message' => $e->getMessage(),
], 503);
}
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*/
public function store(StoreTaskRequest $request): JsonResponse
{
try {
$task = $this->service->create($request->validated());
return new TaskResource($task->load('tags'))
->response()
->setStatusCode(201);
} catch (Throwable $e) {
return response()->json([
'error' => 'Service unavailable',
'message' => $e->getMessage(),
], 503);
}
}
/**
* Display the specified resource.
*/
public function show(Task $task): JsonResponse
{
$task = $this->service->show($task);
return new TaskResource($task->load('tags'))->response();
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Task $task)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(UpdateTaskRequest $request, Task $task): JsonResponse
{
try {
$task = $this->service->update($task, $request->validated());
return new TaskResource($task->load('tags'))->response();
} catch (Throwable $e) {
return response()->json([
'error' => 'Service unavailable',
'message' => $e->getMessage(),
], 503);
}
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Task $task): Response
{
$this->service->delete($task);
return response()->noContent();
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests;
use App\Contracts\RequestFilterInterface;
use Illuminate\Foundation\Http\FormRequest;
class IndexTaskRequest extends FormRequest implements RequestFilterInterface
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'is_done' => ['nullable', 'boolean'],
'tag' => ['nullable', 'string', 'max:50'],
'q' => ['nullable', 'string', 'max:200'],
'sort' => ['nullable', 'in:created_at,due_at'],
'order' => ['nullable', 'in:asc,desc'],
'page' => ['nullable', 'integer', 'min:1'],
'per_page' => ['nullable', 'integer', 'min:1', 'max:' . config('filters.pagination.max_per_page')],
];
}
public function filters(): array
{
return [
'is_done',
'tag',
'search',
'sort',
];
}
public function values(): array
{
return [
'is_done' => $this->filled('is_done') ? (bool)$this->input('is_done') : null,
'tag' => $this->input('tag'),
'search' => $this->input('q'),
'sort' => [
'column' => $this->input('sort'),
'direction' => $this->input('order'),
],
];
}
public function perPage(): ?int
{
return (int)$this->input('per_page');
//?? config('filters.pagination.per_page')
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreTagRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreTaskRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:200'],
'is_done' => ['nullable', 'boolean'],
'due_at' => ['nullable', 'date', 'date_format:Y-m-d\TH:i:s\Z'],
'tags' => ['nullable', 'array'],
'tags.*' => ['string', 'max:50'],
];
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateTagRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return false;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
//
];
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdateTaskRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
*/
public function rules(): array
{
return [
'title' => ['sometimes', 'required', 'string', 'max:200'],
'is_done' => ['sometimes', 'boolean'],
'due_at' => ['nullable', 'date', 'date_format:Y-m-d\TH:i:s\Z'],
'tags' => ['nullable', 'array'],
'tags.*' => ['string', 'max:50'],
];
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace App\Http\Resources;
use App\Models\Tag;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @mixin Tag
*/
class TagResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'name' => $this->name,
];
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace App\Http\Resources;
use App\Models\Task;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @mixin Task
*/
class TaskResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'title' => $this->title,
'is_done' => $this->is_done,
'due_at' => optional($this->due_at)?->toISOString(),
'created_at' => $this->created_at->toISOString(),
'updated_at' => $this->updated_at->toISOString(),
'tags' => TagResource::collection(
$this->whenLoaded('tags')
),
];
}
}