← Назад к вопросам

Сервис управления задачами с 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 документацией.